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

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

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

import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.kandidatur.boundary.AbstractListenkandidaturDAO;
import de.ivu.elect.business.kandidatur.control.ListenkandidaturByRelativePositionComparator;
import de.ivu.elect.business.kandidatur.control.ListenkandidaturByStimmzettelPositionComparator;
import de.ivu.elect.business.kandidatur.entity.AbstractKandidatur;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.stimmzettel.entity.EntityAufStimmzettelDaten;
import de.ivu.elect.business.wahlvorschlag.control.ListenkandidaturLoader;
import de.ivu.elect.util.StringListUtil;

public abstract class AbstractListe extends Wahlvorschlag implements ListeBase {

  private static final long serialVersionUID = 1L;

  public static final Predicate<Listenkandidatur> LISTENKANDIDATUR_ANZAHL_NENNUNGEN_RELEVANT = listenkandidatur -> listenkandidatur != null && !listenkandidatur.isNichtZugelassenImSinneAbgelehnt()
      && listenkandidatur.getListenkandidaturArt().isListenkandidatur();

  private static final String LISTENKANDIDATUREN_PRELOAD = "LISTENKANDIDATUREN_PRELOAD";

  public static final String LISTENKANDIDATUREN_PRELOADER = "LISTENKANDIDATUREN_PRELOADER";

  protected AbstractListe() {
    super();
  }

  @Override
  public boolean hasSameWahlvorschlagArt(AbstractWahlvorschlag wahlvorschlag) {
    return wahlvorschlag != null && wahlvorschlag.isListe();
  }

  public Collection<Listenkandidatur> getListenkandidaturen() {// ELECT-11466
    Collection listenkandidaturenPreload = (Collection) getTransientContext().get(LISTENKANDIDATUREN_PRELOAD);
    if (listenkandidaturenPreload == null) {
      ListenkandidaturLoader listenkandidaturLoader = (ListenkandidaturLoader) getTransientContext().get(LISTENKANDIDATUREN_PRELOADER);

      if (listenkandidaturLoader == null) {
        return getListenkandidatursByListe();
      }
      listenkandidaturenPreload = listenkandidaturLoader.ladeListenkandidaturen((Liste) this);
      getTransientContext().put(LISTENKANDIDATUREN_PRELOAD, listenkandidaturenPreload);
    }

    return listenkandidaturenPreload;
  }

  public boolean hasListenkandidaturenPreloader() {
    return getTransientContext().get(LISTENKANDIDATUREN_PRELOADER) != null;
  }

  public void setListenkandidaturenPreloader(ListenkandidaturLoader listenkandidaturLoader) {
    getTransientContext().put(LISTENKANDIDATUREN_PRELOADER, listenkandidaturLoader);
  }

  public void setListenkandidaturenPreloader(AbstractListenkandidaturDAO listenkandidaturDAO) {
    if (listenkandidaturDAO != null) {
      setListenkandidaturenPreloader(x -> listenkandidaturDAO.findByListeOrderByRelativePosition((Liste) this));
    }
  }

  public void addListenkandidatur(Listenkandidatur kandidatur) {
    addListenkandidaturByListe(kandidatur);
  }

  public void removeListenkandidatur(Listenkandidatur kandidatur) {
    removeListenkandidaturByListe(kandidatur);
  }

  public void removeAllListenkandidaturen() {
    removeAllListenkandidaturByListes();
  }

  @Override
  public boolean isListe() {
    return true;
  }

  @Override
  public Liste getListe() {
    return (Liste) this;
  }

  @Override
  public boolean isDirektkandidat() {
    return false;
  }

  @Override
  public Direktkandidat getDirektkandidat() {
    throw new IllegalStateException("Ist Liste!");
  }

  @Override
  public boolean isReferendumOption() {
    return false;
  }

  @Override
  public ReferendumOption getReferendumOption() {
    throw new IllegalStateException("Ist Liste!");
  }

  private static final String TRENNZEICHEN_ZW_PARTEIEN = ";";

  @Transient
  private transient List<String> listenverbindungEingreichtAsList;

  protected void preSetListenverbindungEingereicht(String alterWert, String neuerWert) {
    listenverbindungEingreichtAsList = null;
  }

  protected void postSetListenverbindungEingereicht(String alterWert, String neuerWert) {
    listenverbindungEingreichtAsList = null;
  }

  public List<String> getListenverbindungEingreichtAsList() {
    if (listenverbindungEingreichtAsList == null) {
      listenverbindungEingreichtAsList = StringListUtil.getStringListeByString(getListenverbindungEingereicht(), TRENNZEICHEN_ZW_PARTEIEN);
    }

    return listenverbindungEingreichtAsList;
  }

  public void setListenverbindungEingreichtFromList(List<String> namen) {
    setListenverbindungEingereicht(StringListUtil.getStringByStringListe(namen, TRENNZEICHEN_ZW_PARTEIEN));
  }

  public List<Listenkandidatur> getListenkandidaturenSortiertNachRelativerPosition() {
    List<Listenkandidatur> result = new ArrayList<>(getListenkandidaturen());
    result.sort(ListenkandidaturByRelativePositionComparator.ASCENDING);
    return result;
  }

  protected String preProcessListenverbindungEingereicht(String neuerWert) {
    return neuerWert;
  }

  /*
   * NOTE: Listenverbindungen werden IN REIHENFOLGE des Auftretens zurückgegeben!
   */
  public static List<Listenverbindung> getListenverbindungen(List<Liste> listen) {
    List<Listenverbindung> result = new ArrayList<>();

    for (Liste liste : listen) {
      Listenverbindung listenverbindung = liste.getListenverbindung();
      if (listenverbindung != null && !result.contains(listenverbindung)) {
        result.add(listenverbindung);
      }
    }

    return result;
  }

  /**
   * NOTE, dass dies NICHT Kandidaturen berücksichtigt, die zwar auf dem Stimmzettel stehen, jedoch nicht mehr zugelassen sind!
   */
  public long getAnzahlZugelassenerListenkandidaturenInsgesamt() {
    return getListenkandidaturen().stream().filter(listenkandidatur -> !listenkandidatur.isListenkandidaturMarkiertAlsErsatzkandidatur() && listenkandidatur.isZugelassen()).count();
  }

  public long getAnzahlNennungenVonListenkandidaturenAufStimmzettel() {
    return getListenkandidaturen().stream().filter(LISTENKANDIDATUR_ANZAHL_NENNUNGEN_RELEVANT)
        .mapToLong(listenkandidatur -> listenkandidatur.getAnzahlNennungen() == null ? 1L : listenkandidatur.getAnzahlNennungen()).sum();
  }

  public List<Listenkandidatur> getListenkandidaturenAufStimmzettelSortiertNachPosition(EntityAufStimmzettelDaten entityAufStimmzettelDaten) {
    return getListenkandidaturen().stream().filter(AbstractKandidatur::isAufStimmzettel)
        .sorted(new ListenkandidaturByStimmzettelPositionComparator(getGebiet(), entityAufStimmzettelDaten))
        .collect(Collectors.toCollection(() -> new ArrayList<>(getListenkandidaturen().size())));
  }

  /**
   * ELECT-3911 - Sonderbehandlung Europawahl Destatis...
   */
  public boolean isAusgeschert() {
    return getListenverbindung() != null && getListenverbindung().isListenverbindungAusgeschert();
  }

  /**
   * ELECT-3911 - Sonderbehandlung Europawahl Destatis...
   */
  public void setAusgeschert(boolean ausgeschert) {
    if (ausgeschert) {
      setListenverbindung(AbstractListenverbindung.createListenverbindungAusgeschert());
    } else {
      setListenverbindung(null);
    }
  }

  @Transient
  private transient Map<Gebiet, Gebiet> gebietStimmzettelPositionBerechenbar = new HashMap<>();

  @Nullable
  public Gebiet getGebietStimmzettelPositionBerechenbar(Gebiet gebiet) {
    if (!gebietStimmzettelPositionBerechenbar.containsKey(gebiet)) {
      gebietStimmzettelPositionBerechenbar.put(gebiet, bestimmeGebietStimmzettelPositionBerechenbar(gebiet));
    }

    return gebietStimmzettelPositionBerechenbar.get(gebiet);
  }

  // Super-Spezialfall Europawahl, nur für diese wird das Gebiet überhaupt benötigt...großes Kino...Hier gibt es auf dem Bund Listen und auf den einzelnen Ländern, es muss in diesem Fall das Bundesland betrachtet werden, um die Stimmzettelposition zu berechnen
  @Nullable
  private Gebiet bestimmeGebietStimmzettelPositionBerechenbar(Gebiet gebiet) {
    // wenn es oberhalb des betrachteten Gebietes zwei Listenwahlgebiete gibt
    int anzahlListenwahlgebiete = 0;

    Gebiet obergebiet = gebiet;

    while (obergebiet != null) {
      if (obergebiet.isListenwahlgebiet()) {
        anzahlListenwahlgebiete++;
      }
      obergebiet = obergebiet.getUebergeordnetesGebiet();
    }

    if (anzahlListenwahlgebiete > 1) {
      // unterstes Listenwahlgebiet zurück
      obergebiet = gebiet;
      while (obergebiet != null) {
        if (obergebiet.isListenwahlgebiet()) {
          return obergebiet;
        }
        obergebiet = obergebiet.getUebergeordnetesGebiet();
      }
    }

    return null;
  }

  @Override
  protected void postSetWahlvorschlagStatus(WahlvorschlagStatus alterWert, WahlvorschlagStatus neuerWert) {
    if (neuerWert != null && wertHatSichGeaendert(alterWert, neuerWert) && isEinzelbewerber()) { // ELECT-12034
      Collection<Listenkandidatur> listenkandidaturen = getListenkandidatursByListe(); // NICHT vorgeladene Entitäten aus AbstractListe#getListenkandidaturen verwenden!!!
      listenkandidaturen.stream().forEach(einzelkandidatur -> einzelkandidatur.setKandidaturStatus(neuerWert.toKandidaturStatus()));
    }
  }

  @Override
  public Set<Kandidatur> getKandidaturen() {
    return new HashSet<>(getListenkandidaturen());
  }
}
