package de.ivu.elect.business.ergebnis.control;

import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.NlGebiet;
import de.ivu.elect.business.gebietsbaum.entity.NlGebietCustomData;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.internet.control.GebietUndStimmabgabeart;
import de.ivu.elect.business.wahl.entity.Wahl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.function.ToLongBiFunction;
import java.util.function.ToLongFunction;

import static de.ivu.elect.jee.entity.EntityUtil.castTo;
import static java.util.stream.Collectors.toList;

public class NlStimmenGewichtetKumulierer {

  private static final Logger LOG = LoggerFactory.getLogger(NlStimmenGewichtetKumulierer.class);

  private static final long VOTE_VALUE_UNBEKANNT = 0L;

  private final Wahl wahl;

  private final List<Wahldurchfuehrungsgebiet> voteValueGebiete;

  private final Map<Long, Long> voteValuePerInGebietshierarchieUntergeordnetesGebietId;

  public NlStimmenGewichtetKumulierer(Wahl wahl, Supplier<Collection< ? extends Gebiet>> gebieteFct) {

    this.wahl = wahl;

    this.voteValueGebiete = NlGebiet.collectVoteValueGebiete(wahl, gebieteFct).stream() // ELECT-13536
        .map(gebiet -> castTo(gebiet, Wahldurchfuehrungsgebiet.class)) //
        .collect(toList());

    this.voteValuePerInGebietshierarchieUntergeordnetesGebietId = new HashMap<>();

    for (Wahldurchfuehrungsgebiet voteValueGebiet : this.voteValueGebiete) {
      Long voteValue = voteValueGebiet.getCustomData(NlGebietCustomData.class).getVoteValue();

      this.voteValuePerInGebietshierarchieUntergeordnetesGebietId.put(voteValueGebiet.getId(), voteValue);

      for (Gebiet gebiet : voteValueGebiet.getInGebietshierarchieUntergeordneteGebiete()) {
        this.voteValuePerInGebietshierarchieUntergeordnetesGebietId.put(gebiet.getId(), voteValue);
      }
    }

  }

  private Wahl getWahl() {
    return wahl;
  }

  public List<Wahldurchfuehrungsgebiet> getVoteValueGebiete() {
    return voteValueGebiete;
  }

  private long getVoteValue(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    if (getVoteValueGebiete().isEmpty()) { // VoteValues NICHT unterstützt...
      return 1L;
    }

    if (wahldurchfuehrungsgebiet == null) {
      return VOTE_VALUE_UNBEKANNT;
    }

    Long result = voteValuePerInGebietshierarchieUntergeordnetesGebietId.get(wahldurchfuehrungsgebiet.getId());

    if (result == null) {
      LOG.error("VoteValue kann NICHT bestimmt werden [wahl = {}, gebiet = {}, voteValueGebiete = {}]", getWahl(), wahldurchfuehrungsgebiet, getVoteValueGebiete());

      return VOTE_VALUE_UNBEKANNT;
    }

    return result;
  }

  public long getSummeListenstimmenGewichtetMitVoteValues(GebietUndStimmabgabeart gebietUndStimmabgabeart,
                                                           ToLongBiFunction<GebietUndStimmabgabeart, ToLongFunction<Wahldurchfuehrungsgebiet>> summeListenstimmenGewichtetFct) {
    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet();

    if (wahldurchfuehrungsgebiet == null) {
      return 0L;
    }

    /*
     * Momentan nur für Eerste Kamer relevant, in der nur zwei Gebietsebenen (Wurzelgebiet -> VoteValue-Gebiete) existieren!!!
     */

    ToLongFunction<Wahldurchfuehrungsgebiet> voteValueFct = this::getVoteValue;

    if (wahldurchfuehrungsgebiet.isWurzelgebiet() && !getVoteValueGebiete().isEmpty()) {
      long result = 0L;

      for (Wahldurchfuehrungsgebiet voteValueGebiet : getVoteValueGebiete()) {
        GebietUndStimmabgabeart voteValueGebietUndStimmabgabeart = gebietUndStimmabgabeart.copy(voteValueGebiet);

        result += summeListenstimmenGewichtetFct.applyAsLong(voteValueGebietUndStimmabgabeart, voteValueFct);
      }

      return result;
    }

    return summeListenstimmenGewichtetFct.applyAsLong(gebietUndStimmabgabeart, voteValueFct);
  }

}
