
package de.ivu.elect.business.stimmzettel.entity;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;

import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gruppe.control.GruppeBySchluesselComparator;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.kandidatur.boundary.WaehlerBenannteKandidaturKonfiguration;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.kandidatur.entity.ListenkandidaturStimmzettelPosition;
import de.ivu.elect.business.sitzberechnung.control.ListenwahlMehrheitswahlPrueferFactory;
import de.ivu.elect.business.stimmzettel.boundary.AnzahlStimmenProWaehlerBestimmer;
import de.ivu.elect.business.wahlvorschlag.entity.Direktkandidat;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.business.wahlvorschlag.entity.ReferendumOption;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;
import de.ivu.elect.business.wahlvorschlag.entity.WahlvorschlagStimmzettelPosition;


import static de.ivu.elect.jee.entity.EntityUtil.castTo;

public class StimmzettelDatenFactory {

  @Inject
  private transient AnzahlStimmenProWaehlerBestimmer anzahlStimmenProWaehlerBestimmer;

  @Inject
  private transient WaehlerBenannteKandidaturKonfiguration waehlerBenannteKandidaturKonfiguration;

  @Inject
  private transient ListenwahlMehrheitswahlPrueferFactory listenwahlMehrheitswahlPrueferFactory;

  public StimmzettelDatenFactory() {
    super();
  }

  public StimmzettelDaten create(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Gebiet gebiet, Collection<? extends Wahlvorschlag> wahlvorschlaege) {
    Map<Gebiet, Collection<Direktkandidat>> direktkandidatenPerGebiet = new HashMap<>();
    direktkandidatenPerGebiet.put(gebiet, new HashSet<>());

    List<Direktkandidat> direktkandidatenSorted = new ArrayList<>();
    Map<Direktkandidat, Long> direktkandidatenPositionen = new HashMap<>();
    Map<Direktkandidat, String> direktkandidatenPositionenFormatted = new HashMap<>();
    Map<Direktkandidat, String> direktkandidatenKennzahlenFormatted = new HashMap<>();

    Map<Gebiet, Collection<Liste>> listenPerGebiet = new HashMap<>();
    listenPerGebiet.put(gebiet, new HashSet<>());

    List<Liste> listenSorted = new ArrayList<>();
    Map<Liste, Long> listenPositionen = new HashMap<>();
    Map<Liste, String> listenPositionenFormatted = new HashMap<>();
    Map<Liste, String> listenKennzahlenFormatted = new HashMap<>();
    Map<Liste, String> listenKennzahlenAufStimmzettelFormatted = new HashMap<>();

    Map<Liste, List<Listenkandidatur>> listenkandidaturenSorted = new HashMap<>();
    Map<Listenkandidatur, Long> listenkandidaturenPositionen = new HashMap<>();
    Map<Listenkandidatur, String> listenkandidaturenPositionenFormatted = new HashMap<>();
    Map<Listenkandidatur, String> listenkandidaturenKennzahlenFormatted = new HashMap<>();
    Map<Listenkandidatur, String> listenkandidaturenKennzahlenAufStimmzettelFormatted = new HashMap<>();
    Map<Listenkandidatur, String> listenkandidaturenNamen = new HashMap<>();

    List<Gruppe> referendumVorlagenSorted = new ArrayList<>();
    Map<Gruppe, Long> referendumVorlagenPositionen = new HashMap<>();

    Map<Gebiet, Collection<ReferendumOption>> referendumOptionenPerGebiet = new HashMap<>();
    referendumOptionenPerGebiet.put(gebiet, new HashSet<>());

    Map<Gruppe, List<ReferendumOption>> referendumOptionenSorted = new HashMap<>();
    Map<ReferendumOption, Long> referendumOptionenPositionen = new HashMap<>();

    for (Wahlvorschlag wahlvorschlag : wahlvorschlaege) {
      var wahlvorschlagGebiet = wahlvorschlag.getGebiet();

      if (wahlvorschlagGebiet.isInGebietshierarchieUebergeordnetesGebietVon(gebiet)) {
        var wahlvorschlagStimmzettelPosition = wahlvorschlag.getStimmzettelPosition(gebiet, entityAufStimmzettelDaten);

        if (isWahlvorschlagAufStimmzettelAusgeben(wahlvorschlag, wahlvorschlagStimmzettelPosition)) {
          if (wahlvorschlag.isDirektkandidat()) {
            var direktkandidat = castTo(wahlvorschlag, Direktkandidat.class);

            direktkandidatenPerGebiet.putIfAbsent(wahlvorschlagGebiet, new HashSet<>());
            direktkandidatenPerGebiet.get(wahlvorschlagGebiet).add(direktkandidat);
            direktkandidatenPerGebiet.get(gebiet).add(direktkandidat);

            direktkandidatenSorted.add(direktkandidat);
            direktkandidatenPositionen.put(direktkandidat, wahlvorschlagStimmzettelPosition.getPosition());
            direktkandidatenPositionenFormatted.put(direktkandidat, wahlvorschlagStimmzettelPosition.formatPosition());
            direktkandidatenKennzahlenFormatted.put(direktkandidat, wahlvorschlagStimmzettelPosition.formatKennzahl());
          }

          if (wahlvorschlag.isListe()) {
            var liste = castTo(wahlvorschlag, Liste.class);

            listenPerGebiet.putIfAbsent(wahlvorschlagGebiet, new HashSet<>());
            listenPerGebiet.get(wahlvorschlagGebiet).add(liste);
            listenPerGebiet.get(gebiet).add(liste);

            listenSorted.add(liste);
            listenPositionen.put(liste, wahlvorschlagStimmzettelPosition.getPosition());
            listenPositionenFormatted.put(liste, wahlvorschlagStimmzettelPosition.formatPosition());
            listenKennzahlenFormatted.put(liste, wahlvorschlagStimmzettelPosition.formatKennzahl());
            listenKennzahlenAufStimmzettelFormatted.put(liste, wahlvorschlagStimmzettelPosition.formatKennzahlAufStimmzettel());

            listenkandidaturenSorted.putIfAbsent(liste, new ArrayList<>());

            for (var listenkandidatur : entityAufStimmzettelDaten.collectListenkandidaturenAufStimmzettelSortedByDefault(liste)) {
              var listenkandidaturStimmzettelPosition = listenkandidatur.getStimmzettelPosition(gebiet,
                  entityAufStimmzettelDaten);
              var listenkandidaturStimmzettelPersonendaten = listenkandidatur.getStimmzettelPersonendaten(gebiet,
                  entityAufStimmzettelDaten);

              if (isListenkandidaturAufStimmzettelAusgeben(listenkandidatur, listenkandidaturStimmzettelPosition)) {
                listenkandidaturenSorted.get(liste).add(listenkandidatur);

                listenkandidaturenPositionen.put(listenkandidatur, listenkandidaturStimmzettelPosition.getPosition());
                listenkandidaturenPositionenFormatted.put(listenkandidatur,
                    listenkandidaturStimmzettelPosition.formatPosition(wahlvorschlagStimmzettelPosition));
                listenkandidaturenKennzahlenFormatted.put(listenkandidatur,
                    listenkandidaturStimmzettelPosition.formatKennzahl(wahlvorschlagStimmzettelPosition));
                listenkandidaturenKennzahlenAufStimmzettelFormatted.put(listenkandidatur,
                    listenkandidaturStimmzettelPosition.formatKennzahlAufStimmzettel(wahlvorschlagStimmzettelPosition));
                listenkandidaturenNamen.put(listenkandidatur, listenkandidaturStimmzettelPersonendaten.getName());
              }
            }

            listenkandidaturenSorted.get(liste).sort(Comparator.comparing(listenkandidaturenPositionen::get));
          }

          if (wahlvorschlag.isReferendumOption()) {
            var referendumOption = castTo(wahlvorschlag, ReferendumOption.class);

            referendumOptionenPerGebiet.putIfAbsent(wahlvorschlagGebiet, new HashSet<>());
            referendumOptionenPerGebiet.get(wahlvorschlagGebiet).add(referendumOption);
            referendumOptionenPerGebiet.get(gebiet).add(referendumOption);

            var referendumVorlageImGebiet = referendumOption.getGruppeImGebiet();
            var referendumVorlage = referendumVorlageImGebiet.getGruppe();

            if (!referendumVorlagenSorted.contains(referendumVorlage)) {
              referendumVorlagenSorted.add(referendumVorlage);
              referendumVorlagenPositionen.put(referendumVorlage, referendumVorlageImGebiet.getRelativePosition());
            }

            referendumOptionenSorted.putIfAbsent(referendumVorlage, new ArrayList<>());
            referendumOptionenPositionen.put(referendumOption, wahlvorschlagStimmzettelPosition.getPosition());
          }
        }
      }
    }

    var anzahlStimmenProWaehlerMaximal = anzahlStimmenProWaehlerBestimmer.getAnzahlStimmenProWaehlerMaximal(gebiet);
    var anzahlStimmenProWaehlerProDirektkandidaturMaximal = anzahlStimmenProWaehlerBestimmer.getAnzahlStimmenProWaehlerProDirektkandidaturMaximal(gebiet,
        direktkandidatenPerGebiet::get);
    var anzahlStimmenProWaehlerProListenkandidaturMaximal = anzahlStimmenProWaehlerBestimmer.getAnzahlStimmenProWaehlerProListenkandidaturMaximal(gebiet,
        listenPerGebiet::get);

    direktkandidatenSorted.sort(Comparator.comparing(direktkandidatenPositionen::get));

    listenSorted.sort(Comparator.comparing(listenPositionen::get));

    referendumVorlagenSorted.sort(GruppeBySchluesselComparator.ASCENDING);

    return new StimmzettelDaten( //
        gebiet.getUebergeordnetesWahlgebiet(),
        listenwahlMehrheitswahlPrueferFactory.createMehrheitswahlPruefer(gebiet.getWahl()).isMehrheitswahl(gebiet, () -> new ArrayList<>(wahlvorschlaege)),
        anzahlStimmenProWaehlerMaximal, //
        anzahlStimmenProWaehlerProDirektkandidaturMaximal, //
        anzahlStimmenProWaehlerProListenkandidaturMaximal, //

        waehlerBenannteKandidaturKonfiguration, //

        direktkandidatenSorted, //
        direktkandidatenPositionen, //
        direktkandidatenPositionenFormatted, //
        direktkandidatenKennzahlenFormatted, //

        listenSorted, //
        listenPositionen, //
        listenPositionenFormatted, //
        listenKennzahlenFormatted, //
        listenKennzahlenAufStimmzettelFormatted, //

        listenkandidaturenSorted, //
        listenkandidaturenPositionen, //
        listenkandidaturenPositionenFormatted, //
        listenkandidaturenKennzahlenFormatted, //
        listenkandidaturenKennzahlenAufStimmzettelFormatted, //
        listenkandidaturenNamen, //

        referendumVorlagenSorted, //
        referendumVorlagenPositionen, //

        referendumOptionenSorted, //
        referendumOptionenPositionen //
    );
  }

  protected boolean isWahlvorschlagAufStimmzettelAusgeben(Wahlvorschlag wahlvorschlag, WahlvorschlagStimmzettelPosition wahlvorschlagStimmzettelPosition) {
    return wahlvorschlagStimmzettelPosition.hasPosition();
  }

  protected boolean isListenkandidaturAufStimmzettelAusgeben(Listenkandidatur listenkandidatur, ListenkandidaturStimmzettelPosition listenkandidaturStimmzettelPosition) {
    return listenkandidaturStimmzettelPosition.hasPosition();
  }
}
