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

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import javax.inject.Inject;

import de.ivu.elect.business.ergebnis.control.StimmenKumulierer;
import de.ivu.elect.business.ergebnis.control.StimmenPool;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.kandidatur.entity.AbstractKandidatur;
import de.ivu.elect.business.kandidatur.entity.Direktkandidatur;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;
import de.ivu.elect.business.kandidatur.entity.KandidaturKumulierteStimmen;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.stimmzettel.entity.EntityAufStimmzettelDaten;
import de.ivu.elect.business.wahlvorschlag.entity.Direktkandidat;

public class KandidaturKumulierteStimmenBuilder {

  @Inject
  private transient KandidaturFilter kandidaturFilter;

  @Inject
  private transient StimmenKumulierer stimmenKumulierer;

  public KandidaturKumulierteStimmenBuilder() {
    super();
  }

  public List<KandidaturKumulierteStimmen> erzeugeKandidaturKumulierteStimmenFuerGruppeAufGebiet(Gebiet gebiet, List<Listenkandidatur> listenkandidaturenDerGruppe,
      List<Direktkandidatur> direktkandidaturenDerGruppe, EntityAufStimmzettelDaten entityAufStimmzettelDaten, StimmenPool stimmenDerGruppe) {
    List<Direktkandidatur> direktkandidaturenDerGruppeImGebiet = bestimmeDirektkandidaturenDerGruppeImGebiet(direktkandidaturenDerGruppe, gebiet);
    List<Listenkandidatur> listenkandidaturenDerGruppeImGebiet = bestimmeListenkandidaturenDerGruppeImGebiet(listenkandidaturenDerGruppe, gebiet);

    Map<Direktkandidatur, Listenkandidatur> listenkandidaturNachDirektKandidaturMap = mappeListenkandidaturenNachDirektkandidaturen(direktkandidaturenDerGruppeImGebiet,
        listenkandidaturenDerGruppeImGebiet);

    List<KandidaturKumulierteStimmen> result = new ArrayList<>();
    Set<Kandidatur> betrachteteKandidaturen = new HashSet<>();

    Map<Gruppe, List<Direktkandidat>> direktKandidatenNachGruppe = direktkandidaturenDerGruppe.stream().collect(Collectors.groupingBy(AbstractKandidatur::getGruppe, Collectors.mapping(Direktkandidatur::getDirektkandidat, Collectors.toList())));

    for (Direktkandidatur direktkandidatur : direktkandidaturenDerGruppeImGebiet) {
      betrachteteKandidaturen.add(direktkandidatur);

      Listenkandidatur listenkandidatur = listenkandidaturNachDirektKandidaturMap.get(direktkandidatur);

      Long anzahlDirektstimmen = bestimmeAnzahlDirektstimmen(direktkandidatur, gebiet, stimmenDerGruppe, direktKandidatenNachGruppe);

      Long anzahlListenstimmen = bestimmeAnzahlListenstimmen(listenkandidatur, gebiet, stimmenDerGruppe);
      Long anzahlListenstimmenAnteilAusWahlvorschlagstimmvergabe = bestimmeAnzahlListenstimmenAnteilAusWahlvorschlagstimmvergabe(listenkandidatur, gebiet, stimmenDerGruppe, anzahlListenstimmen);
      Long anzahlListenstimmenAnteilAusEinzelstimmvergabe = bestimmeAnzahlListenstimmenAnteilAusEinzelstimmvergabe(listenkandidatur, gebiet, stimmenDerGruppe, anzahlListenstimmen);

      Long anzahlListenstimmenNichtErgebnisrelevant = null;

      if (listenkandidatur != null) {
        betrachteteKandidaturen.add(listenkandidatur);
      }

      result.add(new KandidaturKumulierteStimmen(entityAufStimmzettelDaten, gebiet, direktkandidatur, anzahlDirektstimmen, listenkandidatur, anzahlListenstimmen,
          anzahlListenstimmenAnteilAusEinzelstimmvergabe, anzahlListenstimmenAnteilAusWahlvorschlagstimmvergabe, anzahlListenstimmenNichtErgebnisrelevant));
    }

    for (Listenkandidatur listenkandidatur : listenkandidaturenDerGruppeImGebiet) {
      if (!betrachteteKandidaturen.contains(listenkandidatur)) {
        Long anzahlListenstimmen = bestimmeAnzahlListenstimmen(listenkandidatur, gebiet, stimmenDerGruppe);
        Long anzahlListenstimmenAnteilAusWahlvorschlagstimmvergabe = bestimmeAnzahlListenstimmenAnteilAusWahlvorschlagstimmvergabe(listenkandidatur, gebiet, stimmenDerGruppe, anzahlListenstimmen);
        Long anzahlListenstimmenAnteilAusEinzelstimmvergabe = bestimmeAnzahlListenstimmenAnteilAusEinzelstimmvergabe(listenkandidatur, gebiet, stimmenDerGruppe, anzahlListenstimmen);

        Long anzahlListenstimmenNichtErgebnisrelevant = null;

        if (anzahlListenstimmen == null || anzahlListenstimmen == 0) {
          anzahlListenstimmenNichtErgebnisrelevant = bestimmeAnzahlListenstimmenNichtErgebnisrelevant(listenkandidatur, gebiet, stimmenDerGruppe);
        }

        result.add(new KandidaturKumulierteStimmen(entityAufStimmzettelDaten, gebiet, null, 0L, listenkandidatur, anzahlListenstimmen, anzahlListenstimmenAnteilAusEinzelstimmvergabe,
            anzahlListenstimmenAnteilAusWahlvorschlagstimmvergabe, anzahlListenstimmenNichtErgebnisrelevant));
      }
    }

    return result;
  }

  public Comparator< ? super KandidaturKumulierteStimmen> getDefaultComparator() {
    return new KandidaturKumulierteStimmenByStimmzettelPositionComparator();
  }

  private List<Listenkandidatur> bestimmeListenkandidaturenDerGruppeImGebiet(List<Listenkandidatur> listenkandidaturenDerGruppe, Gebiet gebietSelected) {
    return kandidaturFilter.filterAufStimmzettelUndInGebiet(listenkandidaturenDerGruppe, gebietSelected);
  }

  private List<Direktkandidatur> bestimmeDirektkandidaturenDerGruppeImGebiet(List<Direktkandidatur> direktkandidaturenDerGruppe, Gebiet gebietSelected) {
    return kandidaturFilter.filterAufStimmzettelUndInGebiet(direktkandidaturenDerGruppe, gebietSelected);
  }

  private Map<Direktkandidatur, Listenkandidatur> mappeListenkandidaturenNachDirektkandidaturen(List<Direktkandidatur> direktkandidaturenDerGruppe,
      List<Listenkandidatur> listenkandidaturenDerGruppe) {
    Map<Direktkandidatur, Listenkandidatur> result = new HashMap<>(direktkandidaturenDerGruppe.size() * 2);

    for (Direktkandidatur direktkandidatur : direktkandidaturenDerGruppe) {
      Listenkandidatur verknuepfteListenKandidatur = bestimmeVerknuepfteListenkandidatur(direktkandidatur, listenkandidaturenDerGruppe);
      if (verknuepfteListenKandidatur != null) {
        result.put(direktkandidatur, verknuepfteListenKandidatur);
      }
    }

    return result;
  }

  private Listenkandidatur bestimmeVerknuepfteListenkandidatur(Direktkandidatur direktkandidatur, List<Listenkandidatur> listenkandidaturen) {
    return listenkandidaturen.stream().filter(listenkandidatur -> listenkandidatur.hasIdentischePersonenVerknuepfung(direktkandidatur)).findFirst().orElse(null);
  }

  private Long bestimmeAnzahlDirektstimmen(Direktkandidatur direktkandidatur, Gebiet gebiet, StimmenPool stimmenDerGruppe, Map<Gruppe, List<Direktkandidat>> direktKandidatenNachGruppe) {
    return stimmenKumulierer.getSummeStimmenFuerDirektkandidaturAufGebietUndDarunter(direktkandidatur, gebiet, stimmenDerGruppe,
        direktKandidatenNachGruppe::get);
  }

  private Long bestimmeAnzahlListenstimmen(Listenkandidatur listenkandidatur, Gebiet gebietSelected, StimmenPool stimmenDerGruppe) {
    return stimmenKumulierer.getSummeStimmenFuerListenkandidaturAufGebietUndDarunter(listenkandidatur, gebietSelected, stimmenDerGruppe);
  }

  private Long bestimmeAnzahlListenstimmenAnteilAusWahlvorschlagstimmvergabe(Listenkandidatur listenkandidatur, Gebiet gebiet, StimmenPool stimmenDerGruppe, Long anzahlListenstimmen) {
    if (listenkandidatur != null && anzahlListenstimmen != null && anzahlListenstimmen > 0) { // ELECT-9434
      Long listeAnzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert = stimmenKumulierer
          .getSummeListenstimmenEinzelnerWahlvorschlagUnveraendertFuerPolitischeGruppeAufGebietUndDarunter(listenkandidatur.getGruppe(), gebiet, stimmenDerGruppe);
      Long listenkandidaturAnzahlNennungen = Math.max(listenkandidatur.getAnzahlNennungen() == null ? 0L : listenkandidatur.getAnzahlNennungen(), 1L);

      return listeAnzahlStimmzettelNurEinzelnerWahlvorschlagUnveraendert * listenkandidaturAnzahlNennungen;
    }

    return 0L;
  }

  private Long bestimmeAnzahlListenstimmenAnteilAusEinzelstimmvergabe(Listenkandidatur listenkandidatur, Gebiet gebiet, StimmenPool stimmenDerGruppe, Long anzahlListenstimmen) {
    if (listenkandidatur != null && anzahlListenstimmen != null && anzahlListenstimmen > 0) { // ELECT-9434
      Long anzahlListenstimmenAnteilAusWahlvorschlagstimmvergabe = bestimmeAnzahlListenstimmenAnteilAusWahlvorschlagstimmvergabe(listenkandidatur, gebiet, stimmenDerGruppe, anzahlListenstimmen);

      return Math.max(0L, anzahlListenstimmen - anzahlListenstimmenAnteilAusWahlvorschlagstimmvergabe);
    }

    return 0L;
  }

  private Long bestimmeAnzahlListenstimmenNichtErgebnisrelevant(Listenkandidatur listenkandidatur, Gebiet gebiet, StimmenPool stimmenDerGruppe) {
    return stimmenKumulierer.getSummeStimmenFuerListenkandidaturNichtErgebnisrelevantAufGebietUndDarunter(listenkandidatur, gebiet, stimmenDerGruppe);
  }
}
