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

import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import javax.annotation.Nullable;
import javax.persistence.Transient;

import org.apache.commons.lang3.StringUtils;

import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Gebietsart;
import de.ivu.elect.business.gebietsbaum.entity.GebietsartPK;
import de.ivu.elect.business.gebietsbaum.entity.Gebietsarteigenschaft;
import de.ivu.elect.business.wahl.boundary.WahlartCreateTemplate;
import de.ivu.elect.business.wahl.boundary.WahlartTemplate;

public abstract class AbstractWahlart implements WahlartBase {

  private static final long serialVersionUID = 1L;

  /**
   * Bezieht sich auf NACHRÜCKER! (Das sind Listenkandidaturen mit der Art <code>ERSATZKANDIDATUR</code>.)
   */
  public static final long BELIEBIGE_ANZAHL_LISTENKANDIDATUREN_DER_ART_ERSATZKANDIDATUR_PRO_LISTE = -1L;

  public static final long BELIEBIGE_ANZAHL_UNTERGEORDNETE_ERSATZKANDIDATUREN_PRO_DIREKTKANDIDATUR = 9999999999L;

  // NOTE ELECT-8924, dass momentan nicht zwischen untergeordneten Ersatzkandidaturen und Listenkandidaturen der Art Ersatzkandidatur unterschieden wird!
  public static final long BELIEBIGE_ANZAHL_UNTERGEORDNETE_ERSATZKANDIDATUREN_PRO_LISTENKANDIDATUR = BELIEBIGE_ANZAHL_LISTENKANDIDATUREN_DER_ART_ERSATZKANDIDATUR_PRO_LISTE;

  public static final String BELIEBIG_VIELE_ERSATZKANDIDATUREN_MSG_KEY = "entity.WAHLART.ANZAHL_ERSATZKANDIDATUREN.BELIEBIG";

  public static final String DELIMITER_WAHLART_SYSTEMGEBIETSEBENE = ",";

  protected AbstractWahlart() {
    super();
  }

  public boolean hasCommonWahlart() {
    return getCommonWahlart() != null;
  }

  public List<Gebietsart> getGebietsartenDerWahlgebiete() {
    return getGebietsarteigenschaften().stream().filter(Gebietsarteigenschaft::getWahlgebiet).map(Gebietsarteigenschaft::getGebietsart)
        .collect(Collectors.toList());
  }

  public List<Gebietsart> getGebietsartenDerDirektkandidatengebiete() {
    return getGebietsarteigenschaften().stream().filter(Gebietsarteigenschaft::getDirektwahlgebiet).map(Gebietsarteigenschaft::getGebietsart)
        .collect(Collectors.toList());
  }

  public List<Gebietsart> getGebietsartenDerListengebiete() {
    return getGebietsarteigenschaften().stream().filter(Gebietsarteigenschaft::getListenwahlgebiet).map(Gebietsarteigenschaft::getGebietsart)
        .collect(Collectors.toList());
  }

  public List<Gebietsart> getGebietsartenDerReferendengebiete() {
    return getGebietsarteigenschaften().stream().filter(Gebietsarteigenschaft::getReferendumgebiet).map(Gebietsarteigenschaft::getGebietsart)
        .collect(Collectors.toList());
  }

  public List<Gebietsart> getGebietsartenMitListenkandidatenStimmen() {
    return getGebietsarteigenschaften().stream().filter(Gebietsarteigenschaft::getListenkandidatenWaehlbar).map(Gebietsarteigenschaft::getGebietsart)
        .collect(Collectors.toList());
  }

  public List<Gebietsart> getGebietsartenDerErfassungsgebiete() {
    return getGebietsarteigenschaften().stream().filter(Gebietsarteigenschaft::getErfassungsgebiet).map(Gebietsarteigenschaft::getGebietsart)
        .collect(Collectors.toList());
  }

  public boolean isGueltigeGebietsart(Gebietsart gebietsart) {
    return getGebietsarteigenschaften().stream()
        .anyMatch(gebietsarteigenschaft -> gebietsarteigenschaft.getGebietsart().getName().equals(gebietsart.getName()));
  }

  @Transient
  private transient Collection<Gebietsarteigenschaft> gebietsarteigenschaftenPreFetched;

  public void setGebietsarteigenschaftenPreFetched(Collection<Gebietsarteigenschaft> gebietsarteigenschaftenPreFetched) {
    this.gebietsarteigenschaftenPreFetched = gebietsarteigenschaftenPreFetched;
  }

  public Collection<Gebietsarteigenschaft> getGebietsarteigenschaften() {
    if (gebietsarteigenschaftenPreFetched != null) {
      return gebietsarteigenschaftenPreFetched;
    }
    return getGebietsarteigenschaftsByWahlart();
  }

  public void addGebietsarteigenschaft(Gebietsarteigenschaft gebietsarteigenschaft) {
    addGebietsarteigenschaftByWahlart(gebietsarteigenschaft);
  }

  public void removeGebietsarteigenschaft(Gebietsarteigenschaft gebietsarteigenschaft) {
    removeGebietsarteigenschaftByWahlart(gebietsarteigenschaft);
  }

  public void removeAllGebietsarteigenschaften() {
    removeAllGebietsarteigenschaftByWahlarts();
  }

  @Deprecated // Nicht mehr verwenden, Informationen an Entität Wahlart nutzen, wenn nicht vorhanden elect fragen...
  public <CustomWahlartTemplate extends WahlartCreateTemplate> CustomWahlartTemplate getCustomWahlartCreateTemplate(Collection<CustomWahlartTemplate> customWahlartTemplates) {
    for (CustomWahlartTemplate customWahlartTemplate : customWahlartTemplates) {
      if (customWahlartTemplate.equalsWahlartName(getName())) {
        return customWahlartTemplate;
      }
    }

    throw new IllegalStateException("Keine Enum für Wert gefunden: '" + getName() + "', " + customWahlartTemplates);
  }

  @Transient
  private transient Map<GebietsartPK, Gebietsarteigenschaft> gebietsarteigenschaftFuerGebietsartCache;

  @Nullable
  public Gebietsarteigenschaft getGebietsarteigenschaftFuerGebietsart(Gebietsart gebietsart) {
    if (gebietsart == null) {
      return null;
    }
    if (gebietsarteigenschaftFuerGebietsartCache == null) {
      gebietsarteigenschaftFuerGebietsartCache = new HashMap<>();
    }

    return gebietsarteigenschaftFuerGebietsartCache.computeIfAbsent(gebietsart.getPk(),
        x -> getGebietsarteigenschaften().stream().filter(gebietsarteigenschaft -> gebietsarteigenschaft.getGebietsart().equals(gebietsart)).findFirst()
            .orElse(null));
  }

  public static Wahlart createWahlartFromTemplate(WahlartCreateTemplate wahlartTemplate) {
    Wahlart wahlart = new Wahlart();
    fillWahlartFromTemplate(wahlart, wahlartTemplate);
    return wahlart;
  }

  public static void fillWahlartFromTemplate(Wahlart wahlart, WahlartCreateTemplate wahlartTemplate) {
    wahlart.setName(wahlartTemplate.getWahlartName());
    wahlart.setTitel(wahlartTemplate.getWahlartTitel());
    wahlart.setKurzname(wahlartTemplate.getKurzname());
    wahlart.setKennzeichen(wahlartTemplate.getKennzeichen());
    wahlart.setAnzahlStimmzettel(wahlartTemplate.getAnzahlStimmzettel());
    wahlart.setCommonWahlart(wahlartTemplate.getCommonWahlart());
    wahlart.setGruppenvereinigungenErlaubt(wahlartTemplate.isGruppenvereinigungenErlaubt());
    wahlart.setLeereListenErlaubt(wahlartTemplate.isLeereListenErlaubt());
    wahlart.setListenverbindungenErlaubt(wahlartTemplate.isListenverbindungenErlaubt());
    wahlart.setMaxAnzahlNennungen(wahlartTemplate.getMaxAnzahlNennungen());
    wahlart.setFarbe(wahlartTemplate.getFarbe());
    wahlart.setAnzahlStimmenProWaehlerArt(wahlartTemplate.getAnzahlStimmenProWaehlerArt());
    wahlart.setAnzahlStimmenProWaehler(wahlartTemplate.getAnzahlStimmenProWaehler());
    wahlart.setMaxAnzahlErsatzkandidaturenArt(wahlartTemplate.getMaxAnzahlErsatzkandidaturenArt());
    wahlart.setMaxAnzahlErsatzkandidaturen(wahlartTemplate.getMaxAnzahlErsatzkandidaturen());
    wahlart.setGruppenreihenfolgeVorgegeben(wahlartTemplate.isGruppenreihenfolgeVorgegeben());

    wahlart.setUmgangAPruefkennzeichenVorlaeufig(wahlartTemplate.getUmgangAPruefkennzeichenVorlaeufig());
    wahlart.setUmgangAPruefkennzeichenEndgueltig(wahlartTemplate.getUmgangAPruefkennzeichenEndgueltig());
    wahlart.setUmgangAOhneWahlscheinEndgueltig(wahlartTemplate.getUmgangAOhneWahlscheinEndgueltig());
    wahlart.setUmgangAOhneWahlscheinVorlaeufig(wahlartTemplate.getUmgangAOhneWahlscheinVorlaeufig());
    wahlart.setUmgangAMitWahlscheinVorlaeufig(wahlartTemplate.getUmgangAMitWahlscheinVorlaeufig());
    wahlart.setUmgangAMitWahlscheinEndgueltig(wahlartTemplate.getUmgangAMitWahlscheinEndgueltig());
    wahlart.setUmgangANichtImWaehlerverzeichnisVorlaeufig(wahlartTemplate.getUmgangANichtImWaehlerverzeichnisVorlaeufig());
    wahlart.setUmgangANichtImWaehlerverzeichnisEndgueltig(wahlartTemplate.getUmgangANichtImWaehlerverzeichnisEndgueltig());
    wahlart.setUmgangAGesamtVorlaeufig(wahlartTemplate.getUmgangAGesamtVorlaeufig());
    wahlart.setUmgangAGesamtEndgueltig(wahlartTemplate.getUmgangAGesamtEndgueltig());
    wahlart.setUmgangBOhneWahlscheinEndgueltig(wahlartTemplate.getUmgangBOhneWahlscheinEndgueltig());
    wahlart.setUmgangBOhneWahlscheinVorlaeufig(wahlartTemplate.getUmgangBOhneWahlscheinVorlaeufig());
    wahlart.setUmgangBMitWahlscheinVorlaeufig(wahlartTemplate.getUmgangBMitWahlscheinVorlaeufig());
    wahlart.setUmgangBMitWahlscheinEndgueltig(wahlartTemplate.getUmgangBMitWahlscheinEndgueltig());
    wahlart.setUmgangBGesamtVorlaeufig(wahlartTemplate.getUmgangBGesamtVorlaeufig());
    wahlart.setUmgangBGesamtEndgueltig(wahlartTemplate.getUmgangBGesamtEndgueltig());
    wahlart.setUmgangGueltigeVorlaeufig(wahlartTemplate.getUmgangGueltigeVorlaeufig());
    wahlart.setUmgangGueltigeEndgueltig(wahlartTemplate.getUmgangGueltigeEndgueltig());
    wahlart.setUmgangUngueltigeVorlaeufig(wahlartTemplate.getUmgangUngueltigeVorlaeufig());
    wahlart.setUmgangUngueltigeEndgueltig(wahlartTemplate.getUmgangUngueltigeEndgueltig());
    wahlart.setZulassungspruefung(wahlartTemplate.getZulassungspruefung());

    wahlart.setSitzberechnungsalgorithmus(wahlartTemplate.getSitzberechnungsalgorithmus());
    wahlart.setSystemGebietsebenen(wahlartTemplate.getSystemGebietsebenen());
    wahlart.setDokumentvorlagen(wahlartTemplate.getDokumentvorlagen());
    wahlart.setWahlberechtigungImAusland(wahlartTemplate.getWahlberechtigungImAusland());

    wahlart.setWahlbezeichnungLang(wahlartTemplate.getWahlbezeichnungLang());
    wahlart.setWahlausschussbezeichnung(wahlartTemplate.getWahlausschussbezeichnung());
    wahlart.setZuWaehlendesGremium(wahlartTemplate.getZuWaehlendesGremium());

    wahlart.setErgebnisartUmschaltbar(wahlartTemplate.getErgebnisartUmschaltbar());

    wahlart.setErfassungUebernahmeArt(wahlartTemplate.getErfassungUebernahmeArt());
    wahlart.setErfassungUebernahmeManuell(wahlartTemplate.getErfassungUebernahmeManuell());
  }

  public boolean isWahlMitQuorum() {
    // TODO ELECTKOM-2002, sollte als Parameter (bspw. Enum WahlartArtQuorumProzenthuerdeArt) in der Wahlkonfiguration konfiguriert werden...
    return isReferendum();
  }

  public boolean isWahlMitProzenthuerde() {
    // TODO ELECTKOM-2002, sollte als Parameter (bspw. Enum WahlartArtQuorumProzenthuerdeArt) in der Wahlkonfiguration konfiguriert werden...
    return false;
  }

  public boolean isWahlMitAnzahlStimmenProWaehlerPerWahlart() {
    return getAnzahlStimmenProWaehlerArt().isAnzahlStimmenProWaehlerPerWahlartVorgegeben() || getAnzahlStimmenProWaehler() != null && getAnzahlStimmenProWaehler() > 0L;
  }

  public boolean isWahlMitAnzahlStimmenProWaehlerPerWahlgebiet() {
    return getAnzahlStimmenProWaehlerArt().isAnzahlStimmenProWaehlerPerWahlgebietVorgegeben();
  }

  public boolean isWahlMitErsatzkandidaturen() {
    return isWahlMitBeliebigVielenErsatzkandidaturen() || isWahlMitMaxAnzahlErsatzkandidaturenPerWahlgebiet() || isWahlMitMaxAnzahlErsatzkandidaturenPerWahlart();
  }

  public boolean isWahlMitMaxAnzahlErsatzkandidaturenPerWahlgebiet() {
    return getMaxAnzahlErsatzkandidaturenArt().isAnzahlPerWahlgebietVorgegeben();
  }

  public boolean isWahlMitMaxAnzahlErsatzkandidaturenPerWahlart() {
    return getMaxAnzahlErsatzkandidaturenArt().isAnzahlPerWahlartVorgegeben() && getMaxAnzahlErsatzkandidaturen() != null && getMaxAnzahlErsatzkandidaturen() > 0L;
  }

  public boolean isWahlMitBeliebigVielenErsatzkandidaturen() {
    return isWahlMitBeliebigVielenUntergeordnetenErsatzkandidaturenProDirektkandidatur() || isWahlMitBeliebigVielenUntergeordnetenErsatzkandidaturenProListenkandidatur()
        || isWahlMitBeliebigVielenListenkandidaturenMarkiertAlsErsatzkandidaturProListe();
  }

  public boolean isWahlMitBeliebigVielenUntergeordnetenErsatzkandidaturenProDirektkandidatur() {
    return getMaxAnzahlErsatzkandidaturenArt().isAnzahlPerWahlartVorgegeben() && getMaxAnzahlErsatzkandidaturen() != null
        && getMaxAnzahlErsatzkandidaturen() == BELIEBIGE_ANZAHL_UNTERGEORDNETE_ERSATZKANDIDATUREN_PRO_DIREKTKANDIDATUR;
  }

  public boolean isWahlMitBeliebigVielenUntergeordnetenErsatzkandidaturenProListenkandidatur() {
    return getMaxAnzahlErsatzkandidaturenArt().isAnzahlPerWahlartVorgegeben() && getMaxAnzahlErsatzkandidaturen() != null
        && getMaxAnzahlErsatzkandidaturen() == BELIEBIGE_ANZAHL_UNTERGEORDNETE_ERSATZKANDIDATUREN_PRO_LISTENKANDIDATUR;
  }

  public boolean isWahlMitBeliebigVielenListenkandidaturenMarkiertAlsErsatzkandidaturProListe() {
    return getMaxAnzahlErsatzkandidaturenArt().isAnzahlPerWahlartVorgegeben() && getMaxAnzahlErsatzkandidaturen() != null
        && getMaxAnzahlErsatzkandidaturen() == BELIEBIGE_ANZAHL_LISTENKANDIDATUREN_DER_ART_ERSATZKANDIDATUR_PRO_LISTE;
  }

  public long getMaxAnzahlErsatzkandidaturen(Gebiet wahlgebietOderUntergeordnetesGebiet) {
    return getMaxAnzahlErsatzkandidaturenArt().getAnzahl(this, wahlgebietOderUntergeordnetesGebiet);
  }

  public boolean isBegehren() {
    return CommonWahlart.BEGEHREN.equalsWahlart((Wahlart) this);
  }

  public boolean isReferendum() {
    return CommonWahlart.BUERGERENTSCHEID.equalsWahlart((Wahlart) this) || CommonWahlart.VOLKSENTSCHEID.equalsWahlart((Wahlart) this);
  }

  public boolean isEuropawahl() {
    return hasCommonWahlart() && getCommonWahlart().isEuropawahl();
  }

  public boolean isBundestagswahl() {
    return hasCommonWahlart() && getCommonWahlart().isBundestagswahl();
  }

  public boolean isWahlMitUnterstuetzerAufEintragungslisten() {
    return isBegehren();
  }

  public boolean isLeereListenErlaubt() {
    return getLeereListenErlaubt();
  }

  public String getWahlartName() {
    return getName();
  }

  public String getWahlartNameNormalized() {
    String wahlartName = getWahlartName();

    if (hasCommonWahlart() && !CommonWahlart.UNBEKANNT.equals(getCommonWahlart())) {
      wahlartName = getCommonWahlart().getWahlartNameNormalized();
    }

    return CommonWahlart.normalizeWahlartName(wahlartName);
  }

  public boolean isGruppenreihenfolgeManuellAendernErlaubt() {
    return !getGruppenreihenfolgeVorgegeben();
  }

  public long getAnzahlStimmenProWaehler(Gebiet wahlgebietOderUntergeordnetesGebiet) {
    return getAnzahlStimmenProWaehlerArt().getAnzahl(this, wahlgebietOderUntergeordnetesGebiet);
  }

  public boolean equalsWahlart(Wahl wahl) {
    return wahl != null && equalsWahlart(wahl.getWahlart());
  }

  public boolean equalsWahlart(Wahlart wahlart) {
    if (equals(wahlart)) {
      return true;
    }

    return wahlart != null && equalsWahlartName(wahlart.getWahlartName());
  }

  public boolean equalsWahlart(WahlartTemplate wahlartTemplate) {
    return equalsWahlartName(wahlartTemplate.getWahlartName());
  }

  public boolean equalsWahlartName(String another) {
    return WahlartTemplate.equalsWahlartName(getWahlartName(), another) || WahlartTemplate.equalsWahlartName(getWahlartNameNormalized(), another);
  }

  public String getNameGui() {
    if (StringUtils.isBlank(getTitel())) {
      return getName();
    }

    return getTitel();
  }

  public String getNameGuiEindeutig() {
    String result = getNameGui();

    if (getName() != null && !getName().equalsIgnoreCase(result)) {
      result += " [" + getName() + "]";
    }

    return result;
  }

  @Transient
  private transient Set<WahlartSystemGebietsebene> systemGebietsebenenCache;

  public Collection<WahlartSystemGebietsebene> getSystemGebietsebenen() {
    if (systemGebietsebenenCache == null) {
      systemGebietsebenenCache = Arrays.stream(getSystemGebietsebeneUnformatiert().split(DELIMITER_WAHLART_SYSTEMGEBIETSEBENE))
          .map(WahlartSystemGebietsebene::valueOfFallback)
          .collect(Collectors.toSet());

    }

    return systemGebietsebenenCache;
  }

  public void setSystemGebietsebenen(Collection<WahlartSystemGebietsebene> systemGebietsebenen) {
    if (systemGebietsebenen == null || systemGebietsebenen.isEmpty()) {
      setSystemGebietsebeneUnformatiert(WahlartSystemGebietsebene.OHNE.name()); // not nullable
    } else {
      setSystemGebietsebeneUnformatiert(
          systemGebietsebenen.stream().sorted().map(Enum::name).collect(Collectors.joining(DELIMITER_WAHLART_SYSTEMGEBIETSEBENE)));
    }
    this.systemGebietsebenenCache = null;
  }

}
