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

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;

import de.ivu.elect.business.gebietsbaum.boundary.WahlverwaltungGebietInformation;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Verwaltungsart;
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.WaehlerBenannteKandidaturen;
import de.ivu.elect.business.system.control.CustomContextParameter;
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 static java.util.stream.Collectors.toList;

public class StimmzettelDaten implements CustomContextParameter {

  private static final long POSITION_EMPTY = 0L;

  private static final String POSITION_FORMATTED_EMPTY = "";

  private static final String KENNZAHL_FORMATTED_EMPTY = "";

  private static final String NAME_EMPTY = "";

  private final WahlverwaltungGebietInformation wahlgebietInformation;

  private final boolean mehrheitswahl;

  private final long anzahlStimmenProWaehlerMaximal;

  private final long anzahlStimmenProWaehlerProDirektkandidaturMaximal;

  private final long anzahlStimmenProWaehlerProListenkandidaturMaximal;

  private final List<Direktkandidat> direktkandidatenSorted;

  private final List<Direktkandidat> direktkandidatenAufgestelltSorted;

  private final Map<Direktkandidat, Long> direktkandidatenPositionen;

  private final Map<Direktkandidat, String> direktkandidatenPositionenFormatted;

  private final Map<Direktkandidat, String> direktkandidatenKennzahlenFormatted;

  private final List<Liste> listenSorted;

  private final List<Liste> listenAufgestelltSorted;

  private final Map<Liste, Long> listenPositionen;

  private final Map<Liste, String> listenPositionenFormatted;

  private final Map<Liste, String> listenKennzahlenFormatted;

  private final Map<Liste, String> listenKennzahlenAufStimmzettelFormatted;

  private final Map<Liste, List<Listenkandidatur>> listenkandidaturenSorted;

  private final Map<Listenkandidatur, Long> listenkandidaturenPositionen;

  private final Map<Listenkandidatur, String> listenkandidaturenPositionenFormatted;

  private final Map<Listenkandidatur, String> listenkandidaturenKennzahlenFormatted;

  private final Map<Listenkandidatur, String> listenkandidaturenKennzahlenAufStimmzettelFormatted;

  private final Map<Listenkandidatur, String> listenkandidaturenNamen;

  private final List<Gruppe> referendumVorlagenSorted;

  private final Map<Gruppe, Long> referendumVorlagenPositionen;

  private final Map<Gruppe, List<ReferendumOption>> referendumOptionenSorted;

  private final Map<ReferendumOption, Long> referendumOptionenPositionen;

  private final Map<String, Object> contextParameters = new HashMap<>();

  public StimmzettelDaten(Gebiet wahlgebiet,
                          boolean mehrheitswahl,
                          long anzahlStimmenProWaehlerMaximal,
                          long anzahlStimmenProWaehlerProDirektkandidaturMaximal,
                          long anzahlStimmenProWaehlerProListenkandidaturMaximal,
                          WaehlerBenannteKandidaturKonfiguration waehlerBenannteKandidaturKonfiguration,
                          List<Direktkandidat> direktkandidatenSorted,
                          Map<Direktkandidat, Long> direktkandidatenPositionen,
                          Map<Direktkandidat, String> direktkandidatenPositionenFormatted,
                          Map<Direktkandidat, String> direktkandidatenKennzahlenFormatted,
                          List<Liste> listenSorted,
                          Map<Liste, Long> listenPositionen,
                          Map<Liste, String> listenPositionenFormatted,
                          Map<Liste, String> listenKennzahlenFormatted,
                          Map<Liste, String> listenKennzahlenAufStimmzettelFormatted,
                          Map<Liste, List<Listenkandidatur>> listenkandidaturenSorted,
                          Map<Listenkandidatur, Long> listenkandidaturenPositionen,
                          Map<Listenkandidatur, String> listenkandidaturenPositionenFormatted,
                          Map<Listenkandidatur, String> listenkandidaturenKennzahlenFormatted,
                          Map<Listenkandidatur, String> listenkandidaturenKennzahlenAufStimmzettelFormatted,
                          Map<Listenkandidatur, String> listenkandidaturenNamen,
                          List<Gruppe> referendumVorlagenSorted,
                          Map<Gruppe, Long> referendumVorlagenPositionen,
                          Map<Gruppe, List<ReferendumOption>> referendumOptionenSorted,
                          Map<ReferendumOption, Long> referendumOptionenPositionen
  ) {
    super();

    this.wahlgebietInformation = wahlgebiet == null ? new WahlverwaltungGebietInformation(Verwaltungsart.OHNE, "", "",
        "") : new WahlverwaltungGebietInformation(wahlgebiet);
    this.mehrheitswahl = mehrheitswahl;

    this.anzahlStimmenProWaehlerMaximal = anzahlStimmenProWaehlerMaximal;
    this.anzahlStimmenProWaehlerProDirektkandidaturMaximal = anzahlStimmenProWaehlerProDirektkandidaturMaximal;
    this.anzahlStimmenProWaehlerProListenkandidaturMaximal = anzahlStimmenProWaehlerProListenkandidaturMaximal;

    this.direktkandidatenSorted = direktkandidatenSorted;
    this.direktkandidatenAufgestelltSorted = direktkandidatenSorted.stream().filter(wahlvorschlagAufgestellt()).collect(toList());
    this.direktkandidatenPositionen = direktkandidatenPositionen;
    this.direktkandidatenPositionenFormatted = direktkandidatenPositionenFormatted;
    this.direktkandidatenKennzahlenFormatted = direktkandidatenKennzahlenFormatted;

    this.listenSorted = listenSorted;
    this.listenAufgestelltSorted = listenSorted.stream().filter(wahlvorschlagAufgestellt()).collect(toList());
    this.listenPositionen = listenPositionen;
    this.listenPositionenFormatted = listenPositionenFormatted;
    this.listenKennzahlenFormatted = listenKennzahlenFormatted;
    this.listenKennzahlenAufStimmzettelFormatted = listenKennzahlenAufStimmzettelFormatted;

    this.listenkandidaturenSorted = listenkandidaturenSorted;
    this.listenkandidaturenPositionen = listenkandidaturenPositionen;
    this.listenkandidaturenPositionenFormatted = listenkandidaturenPositionenFormatted;
    this.listenkandidaturenKennzahlenFormatted = listenkandidaturenKennzahlenFormatted;
    this.listenkandidaturenKennzahlenAufStimmzettelFormatted = listenkandidaturenKennzahlenAufStimmzettelFormatted;
    this.listenkandidaturenNamen = listenkandidaturenNamen;

    this.referendumVorlagenSorted = referendumVorlagenSorted;
    this.referendumVorlagenPositionen = referendumVorlagenPositionen;

    this.referendumOptionenSorted = referendumOptionenSorted;
    this.referendumOptionenPositionen = referendumOptionenPositionen;
  }

  private Predicate<? super Wahlvorschlag> wahlvorschlagAufgestellt() {
    return wahlvorschlag -> !WaehlerBenannteKandidaturen.isWaehlerBenannterWahlvorschlag(wahlvorschlag);
  }

  public WahlverwaltungGebietInformation getWahlgebietInformation() {
    return wahlgebietInformation;
  }

  public long getAnzahlStimmenProWaehlerMaximal() {
    return anzahlStimmenProWaehlerMaximal;
  }

  public long getAnzahlStimmenProWaehlerProDirektkandidaturMaximal() {
    return anzahlStimmenProWaehlerProDirektkandidaturMaximal;
  }

  public long getAnzahlStimmenProWaehlerProListenkandidaturMaximal() {
    return anzahlStimmenProWaehlerProListenkandidaturMaximal;
  }

  /**
   * @return Die zugelassenen Direktkandidaten im System. Das heißt, alle ggf. vom Wähler benannten Personen SIND ENTHALTEN!
   * @see StimmzettelDaten#getDirektkandidatenAufgestellt
   */
  public List<Direktkandidat> getDirektkandidaten() {
    return direktkandidatenSorted;
  }

  /**
   * @return Die zugelassenen, initial aufgestellten Direktkandidaten auf dem GEDRUCKTEN Stimmzettel. Das heißt, alle ggf. vom Wähler benannten Personen SIND NICHT ENTHALTEN!
   * @see StimmzettelDaten#getDirektkandidaten
   */
  public List<Direktkandidat> getDirektkandidatenAufgestellt() {
    return direktkandidatenAufgestelltSorted;
  }

  public Long getDirektkandidatPosition(Direktkandidat direktkandidat) {
    return direktkandidatenPositionen.getOrDefault(direktkandidat, POSITION_EMPTY);
  }

  public String getDirektkandidatPositionFormatted(Direktkandidat direktkandidat) {
    return direktkandidatenPositionenFormatted.getOrDefault(direktkandidat, POSITION_FORMATTED_EMPTY);
  }

  public String getDirektkandidatKennzahlFormatted(Direktkandidat direktkandidat) {
    return direktkandidatenKennzahlenFormatted.getOrDefault(direktkandidat, KENNZAHL_FORMATTED_EMPTY);
  }

  /**
   * @return Die zugelassenen Listen im System. Das heißt, eine ggf. vorhandene Liste für vom Wähler benannten Personen IST ENTHALTEN!
   * @see StimmzettelDaten#getListenAufgestellt
   */
  public List<Liste> getListen() {
    return listenSorted;
  }

  /**
   * @return Die zugelassenen, initial aufgestellten Listen auf dem GEDRUCKTEN Stimmzettel. Das heißt, eine ggf. vorhandene Liste für vom Wähler benannten Personen IST NICHT ENTHALTEN!
   * @see StimmzettelDaten#getListen
   */
  public List<Liste> getListenAufgestellt() {
    return listenAufgestelltSorted;
  }

  public Long getListePosition(Liste liste) {
    return listenPositionen.getOrDefault(liste, 0L);
  }

  public String getListePositionFormatted(Liste liste) {
    return listenPositionenFormatted.getOrDefault(liste, POSITION_FORMATTED_EMPTY);
  }

  public String getListeKennzahlFormatted(Liste liste) {
    return listenKennzahlenFormatted.getOrDefault(liste, KENNZAHL_FORMATTED_EMPTY);
  }

  public String getListeKennzahlAufStimmzettelFormatted(Liste liste) {
    return listenKennzahlenAufStimmzettelFormatted.getOrDefault(liste, KENNZAHL_FORMATTED_EMPTY);
  }

  public List<Listenkandidatur> getListenkandidaturen(Liste liste) {
    return listenkandidaturenSorted.getOrDefault(liste, new ArrayList<>());
  }

  public Long getListenkandidaturPosition(Listenkandidatur listenkandidatur) {
    return listenkandidaturenPositionen.getOrDefault(listenkandidatur, POSITION_EMPTY);
  }

  public String getListenkandidaturPositionFormatted(Listenkandidatur listenkandidatur) {
    return listenkandidaturenPositionenFormatted.getOrDefault(listenkandidatur, POSITION_FORMATTED_EMPTY);
  }

  public String getListenkandidaturKennzahlFormatted(Listenkandidatur listenkandidatur) {
    return listenkandidaturenKennzahlenFormatted.getOrDefault(listenkandidatur, KENNZAHL_FORMATTED_EMPTY);
  }

  public String getListenkandidaturKennzahlAufStimmzettelFormatted(Listenkandidatur listenkandidatur) {
    return listenkandidaturenKennzahlenAufStimmzettelFormatted.getOrDefault(listenkandidatur, KENNZAHL_FORMATTED_EMPTY);
  }

  public String getListenkandidaturName(Listenkandidatur listenkandidatur) {
    return listenkandidaturenNamen.getOrDefault(listenkandidatur, NAME_EMPTY);
  }

  public Long getAnzahlStimmenBeiUnveraenderterListe(Listenkandidatur listenkandidatur) {
    return Math.min(listenkandidatur.getAnzahlNennungen(), getAnzahlStimmenProWaehlerProListenkandidaturMaximal()); // ELECT-14282
  }

  public List<Gruppe> getReferendumVorlagen() {
    return referendumVorlagenSorted;
  }

  public Long getReferendumVorlagePosition(Gruppe referendumVorlage) {
    return referendumVorlagenPositionen.getOrDefault(referendumVorlage, 0L);
  }

  public List<ReferendumOption> getReferendumOptionen(Gruppe referendumVorlage) {
    return referendumOptionenSorted.getOrDefault(referendumVorlage, new ArrayList<>());
  }

  public Long getReferendumOptionPosition(ReferendumOption referendumOption) {
    return referendumOptionenPositionen.getOrDefault(referendumOption, 0L);
  }

  private transient Map<Gruppe, Liste> listenNachGruppe;

  private transient Map<String, Liste> listenNachKennzahlAufStimmzettelFormatted;

  public Map<Gruppe, Liste> getListenNachGruppe() {
    initListenNach();
    return listenNachGruppe;
  }

  public Map<String, Liste> getListenNachKennzahlAufStimmzettelFormatted() {
    initListenNach();
    return listenNachKennzahlAufStimmzettelFormatted;
  }

  private void initListenNach() {
    if (listenNachGruppe == null || listenNachKennzahlAufStimmzettelFormatted == null) {
      listenNachGruppe = new LinkedHashMap<>();
      listenNachKennzahlAufStimmzettelFormatted = new LinkedHashMap<>();
      for (Liste liste : listenSorted) {
        listenNachGruppe.put(liste.getGruppe(), liste);
        listenNachKennzahlAufStimmzettelFormatted.put(getListeKennzahlAufStimmzettelFormatted(liste), liste);
      }
    }
  }

  @Override
  public Map<String, Object> getContextParameters() {
    return contextParameters;
  }

  public boolean isMehrheitswahl() {
    return mehrheitswahl;
  }
}
