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

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.ToLongFunction;

import javax.enterprise.inject.Specializes;
import javax.inject.Inject;

import de.ivu.elect.business.ergebnis.control.NlStimmenGewichtetKumulierer;
import de.ivu.elect.business.ergebnis.control.StimmenPool;
import de.ivu.elect.business.ergebnis.entity.Stimmabgabeart;
import de.ivu.elect.business.gebietsbaum.boundary.GebietExtendedDAO;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.internet.control.GebietUndStimmabgabeart;
import de.ivu.elect.business.kandidatur.entity.Direktkandidatur;
import de.ivu.elect.business.kandidatur.entity.KandidaturKumulierteStimmen;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.kandidatur.entity.NlKandidaturKumulierteStimmen;
import de.ivu.elect.business.stimmzettel.entity.EntityAufStimmzettelDaten;
import de.ivu.elect.business.wahl.entity.Wahl;

@Specializes
public class NlKandidaturKumulierteStimmenBuilder extends KandidaturKumulierteStimmenBuilder {

  @Inject
  private transient GebietExtendedDAO gebietExtendedDAO;

  @Override
  public List<KandidaturKumulierteStimmen> erzeugeKandidaturKumulierteStimmenFuerGruppeAufGebiet(Gebiet gebiet, Gruppe gruppe, boolean gruppeListenstimmenSummenErzeugen, List<Listenkandidatur> listenkandidaturenDerGruppe, List<Direktkandidatur> direktkandidaturenDerGruppe, EntityAufStimmzettelDaten entityAufStimmzettelDaten, StimmenPool stimmenDerGruppe) {
    List<KandidaturKumulierteStimmen> kandidaturKumulierteStimmen = super.erzeugeKandidaturKumulierteStimmenFuerGruppeAufGebiet(gebiet, gruppe, gruppeListenstimmenSummenErzeugen, listenkandidaturenDerGruppe, direktkandidaturenDerGruppe, entityAufStimmzettelDaten, stimmenDerGruppe);

    kandidaturKumulierteStimmen = erzeugeNlKandidaturKumulierteStimmen(gebiet, stimmenDerGruppe, kandidaturKumulierteStimmen);

    if (gebiet.isListenwahlgebiet() || kandidaturKumulierteStimmen.isEmpty()) {
      kandidaturKumulierteStimmen.sort(super.getDefaultComparator()); // ELECT-11343

      return kandidaturKumulierteStimmen;
    }

    Map<Listenkandidatur, Collection<KandidaturKumulierteStimmen>> gruppiertNachKandidatur = new LinkedHashMap<>(); // Reihenfolge behalten?

    for (KandidaturKumulierteStimmen kandidaturKumulierteStimman : kandidaturKumulierteStimmen) {
      findOrCreateGruppe(kandidaturKumulierteStimman.getListenkandidatur(), gruppiertNachKandidatur).add(kandidaturKumulierteStimman);
    }

    List<KandidaturKumulierteStimmen> result = new ArrayList<>(gruppiertNachKandidatur.size());

    // Zusammenfassen ELECT-11331
    for (Entry<Listenkandidatur, Collection<KandidaturKumulierteStimmen>> e : gruppiertNachKandidatur.entrySet()) {
      Listenkandidatur listenkandidatur = e.getKey();
      Collection<KandidaturKumulierteStimmen> listenkandidaturKumulierteStimmen = e.getValue();

      Long anzahlListenstimmen = null;
      Long anzahlListenstimmenGewichtet = null;
      Long anzahlListenstimmenAnteilAusEinzelstimmvergabe = null;
      Long anzahlListenstimmenAnteilAusWahlvorschlagstimmvergabe = null;
      Long anzahlListenstimmenNichtErgebnisrelevant = null;

      for (KandidaturKumulierteStimmen kumulierteStimmen : listenkandidaturKumulierteStimmen) {
        anzahlListenstimmen = add(anzahlListenstimmen, kumulierteStimmen.getAnzahlListenstimmen());
        anzahlListenstimmenGewichtet = add(anzahlListenstimmenGewichtet, ((NlKandidaturKumulierteStimmen)kumulierteStimmen).getAnzahlListenstimmenGewichtet());
        anzahlListenstimmenAnteilAusEinzelstimmvergabe = add(anzahlListenstimmenAnteilAusEinzelstimmvergabe, kumulierteStimmen.getAnzahlListenstimmenAnteilAusEinzelstimmvergabe());
        anzahlListenstimmenAnteilAusWahlvorschlagstimmvergabe = add(anzahlListenstimmenAnteilAusWahlvorschlagstimmvergabe,
            kumulierteStimmen.getAnzahlListenstimmenAnteilAusWahlvorschlagstimmvergabe());
        anzahlListenstimmenNichtErgebnisrelevant = add(anzahlListenstimmenNichtErgebnisrelevant,
            kumulierteStimmen.getAnzahlListenstimmenNichtErgebnisrelevant());
      }

      result.add(new NlKandidaturKumulierteStimmen(entityAufStimmzettelDaten, gebiet, listenkandidatur,
          anzahlListenstimmen, anzahlListenstimmenGewichtet, anzahlListenstimmenAnteilAusEinzelstimmvergabe,
          anzahlListenstimmenAnteilAusWahlvorschlagstimmvergabe, anzahlListenstimmenNichtErgebnisrelevant));

    }

    // ELECT-11343 Sortierung
    result.sort(this::sortResult);

    return result;
  }

  private List<KandidaturKumulierteStimmen> erzeugeNlKandidaturKumulierteStimmen(Gebiet gebiet, StimmenPool stimmenDerGruppe, List<KandidaturKumulierteStimmen> kandidaturKumulierteStimmen) {
    Wahl wahl = gebiet.getWahl();
    NlStimmenGewichtetKumulierer nlStimmenGewichtetKumulierer = new NlStimmenGewichtetKumulierer(wahl, () -> gebietExtendedDAO.findByWahl(wahl));
    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = gebiet.castTo(Wahldurchfuehrungsgebiet.class).getEntity();
    GebietUndStimmabgabeart gebietUndStimmabgabeart = new GebietUndStimmabgabeart(wahldurchfuehrungsgebiet, Stimmabgabeart.GESAMT); // Stimmabgabeart wird hier nicht verwendet und ist deshalb egal

    List<KandidaturKumulierteStimmen> nlKandidaturKumulierteStimmen = new ArrayList<>(kandidaturKumulierteStimmen.size());
    for (KandidaturKumulierteStimmen kumulierteStimmen : kandidaturKumulierteStimmen) {
      nlKandidaturKumulierteStimmen.add(new NlKandidaturKumulierteStimmen(kumulierteStimmen, nlStimmenGewichtetKumulierer.getSummeListenstimmenGewichtetMitVoteValues(gebietUndStimmabgabeart, (voteValueGebietUndStimmabgabeart, voteValueFct) -> wichteListenstimmen(kumulierteStimmen, voteValueGebietUndStimmabgabeart, voteValueFct, stimmenDerGruppe))));
    }

    return nlKandidaturKumulierteStimmen;
  }

  private long wichteListenstimmen(KandidaturKumulierteStimmen kumulierteStimmen, GebietUndStimmabgabeart voteValueGebietUndStimmabgabeart, ToLongFunction<Wahldurchfuehrungsgebiet> voteValueFct, StimmenPool stimmenDerGruppe) {
    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = voteValueGebietUndStimmabgabeart.getWahldurchfuehrungsgebiet();

    Long summeListenstimmen = stimmenKumulierer.getSummeStimmenFuerListenkandidaturAufGebietUndDarunter(kumulierteStimmen.getListenkandidatur(), wahldurchfuehrungsgebiet, stimmenDerGruppe);

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

    long wichtung = voteValueFct.applyAsLong(wahldurchfuehrungsgebiet);

    return wichtung * summeListenstimmen;
  }

  @Override
  public Comparator<? super KandidaturKumulierteStimmen> getDefaultComparator() {
    return (u,v)->0; // nicht sortieren // ELECT-11343
  }

  private int sortResult(KandidaturKumulierteStimmen k1, KandidaturKumulierteStimmen k2) {

    Long stimmen1 = k1.getAnzahlListenstimmen();
    Long stimmen2 = k2.getAnzahlListenstimmen();

    if (stimmen1 == null && stimmen2 == null) {
      return 0;
    }
    if (stimmen1 == null) {
      return 1;
    }
    if (stimmen2 == null) {
      return -1;
    }

    return stimmen2.compareTo(stimmen1); // absteigend sortieren
  }

  private Long add(Long vorher, Long summe) {
    if (summe == null) {
      return vorher;
    }
    if (vorher == null) {
      return summe;
    }
    return vorher + summe;

  }

  private Collection<KandidaturKumulierteStimmen> findOrCreateGruppe(Listenkandidatur listenkandidatur, Map<Listenkandidatur, Collection<KandidaturKumulierteStimmen>> gruppiertNachKandidatur) {
    for (Map.Entry<Listenkandidatur, Collection<KandidaturKumulierteStimmen>> entry : gruppiertNachKandidatur.entrySet()) {
      if (entry.getKey().getIdentischVerknuepfteKandidaturen().contains(listenkandidatur)) {
        return entry.getValue();
      }
    }

    Collection<KandidaturKumulierteStimmen> result = new HashSet<>();
    gruppiertNachKandidatur.put(listenkandidatur, result);
    return result;
  }
}
