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

import static java.util.stream.Collectors.toList;

import java.util.ArrayList;
import java.util.HashMap;
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.gruppe.entity.Gruppe;
import de.ivu.elect.business.kandidatur.boundary.WaehlerBenannteKandidaturKonfiguration;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
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;

public class StimmzettelDaten {

  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 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, 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> 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;

  public StimmzettelDaten(Gebiet wahlgebiet, 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, List<Listenkandidatur>> listenkandidaturenSorted,
      Map<Listenkandidatur, Long> listenkandidaturenPositionen, Map<Listenkandidatur, String> listenkandidaturenPositionenFormatted,
      Map<Listenkandidatur, String> listenkandidaturenKennzahlenFormatted, 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("", "", "", "") : new WahlverwaltungGebietInformation(wahlgebiet);

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

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

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

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

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

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

  private Predicate< ? super Wahlvorschlag> wahlvorschlagAufgestellt(WaehlerBenannteKandidaturKonfiguration waehlerBenannteKandidaturKonfiguration) {
    return wahlvorschlag -> !waehlerBenannteKandidaturKonfiguration.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 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 getListenkandidaturName(Listenkandidatur listenkandidatur) {
    return listenkandidaturenNamen.getOrDefault(listenkandidatur, NAME_EMPTY);
  }

  public Long getAnzahlStimmenBeiUnveraenderterListe(Listenkandidatur listenkandidatur) {
    return listenkandidatur.getAnzahlNennungen();
  }

  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> listenNachKennzahlFormatted;

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

  public Map<String, Liste> getListenNachKennzahlFormatted() {
    initListenNach();
    return listenNachKennzahlFormatted;
  }

  private void initListenNach() {
    if (listenNachGruppe == null || listenNachKennzahlFormatted == null) {
      listenNachGruppe = new HashMap<>();
      listenNachKennzahlFormatted = new HashMap<>();
      for (Liste liste : listenSorted) {

        listenNachGruppe.put(liste.getGruppe(), liste);
        listenNachKennzahlFormatted.put(getListeKennzahlFormatted(liste), liste);
      }
    }
  }
}
