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

import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singleton;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
import static org.apache.commons.lang3.StringUtils.isAllBlank;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.split;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;

import javax.annotation.Nullable;

import org.apache.commons.lang3.StringUtils;

import de.ivu.elect.business.kandidatur.entity.Kandidatur;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.kandidatur.entity.NlListenkandidatur;
import de.ivu.elect.business.pruefung.boundary.ZulassungPruefer;
import de.ivu.elect.business.stimmzettel.entity.EntityAufStimmzettelDaten;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahlrechtspruefung.control.PruefkriteriumByPositionComparator;
import de.ivu.elect.business.wahlrechtspruefung.entity.NlWahlrechtspruefung;
import de.ivu.elect.business.wahlrechtspruefung.entity.Wahlrechtspruefung;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.business.wahlvorschlag.entity.NlListe;
import de.ivu.elect.business.wahlvorschlag.entity.StelInformationen;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;
import de.ivu.elect.business.wahlvorschlagsperson.boundary.AbstractWahlvorschlagsPersonDAO;
import de.ivu.elect.business.wahlvorschlagsperson.entity.WahlvorschlagsPerson;
import de.ivu.elect.business.wahlvorschlagsperson.entity.WahlvorschlagsPersonTyp;

public class NlPolitischeGruppe {

  public static final GruppeArt GRUPPE_ART_DEFAULT = GruppeArt.PARTEI;

  public static final String ZULASSUNG_ABGELEHNT_BEGRUENDUNG_SEPARATOR = System.lineSeparator();

  private static final String BLANCO_LISTE_NAME = "Blanco";

  private static final GruppeArt BLANCO_LISTE_GRUPPE_ART = GruppeArt.WAEHLERGRUPPE;

  private final PolitischeGruppe politischeGruppe;

  public NlPolitischeGruppe(Wahlvorschlag wahlvorschlag) {
    this(wahlvorschlag.getGruppe());
  }

  public NlPolitischeGruppe(PolitischeGruppe politischeGruppe) {
    super();

    this.politischeGruppe = politischeGruppe;
  }

  public PolitischeGruppe getGruppe() {
    return politischeGruppe;
  }

  public GruppePK getPk() {
    return politischeGruppe.getPk();
  }

  public boolean isPersistent() {
    return politischeGruppe.isPersistent();
  }

  public Wahl getWahl() {
    return politischeGruppe.getWahl();
  }

  public void setWahl(Wahl wahl) {
    politischeGruppe.setWahl(wahl);
  }

  public String formatNameBlancoListe(EntityAufStimmzettelDaten entityAufStimmzettelDaten) {
    return formatNameBlancoListe(getErsteListenkandidaturRepraesentativ(entityAufStimmzettelDaten).map(NlListenkandidatur::new));
  }

  public String formatNameBlancoListe(Optional<NlListenkandidatur> ersteListenkandidaturRepraesentativIfPresent) {
    return ersteListenkandidaturRepraesentativIfPresent.map(nlListenkandidatur -> formatNameBlancoListe( //
        nlListenkandidatur.getFamilienname(), //
        nlListenkandidatur.getFamiliennameBestandteil(), //
        nlListenkandidatur.getInitialen(), //
        nlListenkandidatur.getVorname() //
    )).orElse(getName());
  }

  public static String formatNameBlancoListe(String familienname, String familiennameBestandteil, String initialen, String vorname) {
    if (!isAllBlank(familienname, initialen)) {
      String namen = familienname + ", " + initialen;

      if (isNotBlank(familiennameBestandteil)) { // ELECT-11645
        namen = familiennameBestandteil + " " + namen;
      }

      return BLANCO_LISTE_NAME + " (" + namen + ")"; // ELECT-10637, keine Messages verwenden!!!
    }

    return BLANCO_LISTE_NAME; // ELECT-10637, keine Messages verwenden!!!
  }

  public boolean isBlancoListe() {
    return BLANCO_LISTE_GRUPPE_ART.equals(getGruppeArt());
  }

  public void setBlancoListe(String name) {
    setGruppeArt(NlPolitischeGruppe.BLANCO_LISTE_GRUPPE_ART);
    setName(name);
  }

  public GruppeArt getGruppeArt() {
    return politischeGruppe.getGruppeArt();
  }

  public void setGruppeArt(GruppeArt gruppeArt) {
    politischeGruppe.setGruppeArt(gruppeArt);
  }

  // ELECT-10160
  public void markNameAbgelehnt(EntityAufStimmzettelDaten entityAufStimmzettelDaten, String nameAbgelehntBemerkung) {
    politischeGruppe.setMinderheit(true);
    setNameAbgelehntBemerkung(nameAbgelehntBemerkung);

    setBlancoListe(formatNameBlancoListe(entityAufStimmzettelDaten));
  }

  // ELECT-10160
  public void markNameZulaessig() {
    politischeGruppe.setMinderheit(false);
    setNameAbgelehntBemerkung("");

    setName(getNameInitial());
    setGruppeArt(GRUPPE_ART_DEFAULT);
  }

  public String getNameInitial() {
    return politischeGruppe.getFarbeExtern();
  }

  public void setNameInitial(String name) {
    politischeGruppe.setFarbeExtern(name);

    if (isNotBlank(name) && isBlank(getName())) {
      setName(name);
    }

    politischeGruppe.setPrivilegiert(!isNameAbgelehnt() && !Objects.equals(name, getName())); // ELECT-10129
  }

  public boolean isNameGeaendert() {
    return Boolean.TRUE.equals(politischeGruppe.getPrivilegiert());
  }

  public boolean isNameAbgelehnt() {
    return Boolean.TRUE.equals(politischeGruppe.getMinderheit());
  }

  public String getNameAbgelehntBemerkung() {
    return getCustomData().getNameAbgelehntBemerkung();
  }

  public void setNameAbgelehntBemerkung(String nameAbgelehntBemerkung) {
    getCustomData().setNameAbgelehntBemerkung(nameAbgelehntBemerkung);
  }

  public String getName() {
    String result = defaultIfBlank(politischeGruppe.getName(), defaultIfBlank(politischeGruppe.getSchluessel(), politischeGruppe.getKurzname()));

    setName(result); // Nochmals synchronisieren!

    return result;
  }

  public void setName(String name) {
    // ELECT-9751 Langname/Kurzname/Schlüssel auf selben Wert setzen
    politischeGruppe.setName(name);
    politischeGruppe.setSchluessel(name);
    politischeGruppe.setKurzname(name);

    if (isNotBlank(name) && isBlank(getNameInitial())) {
      setNameInitial(name);
    }

    politischeGruppe.setPrivilegiert(!isNameAbgelehnt() && !Objects.equals(name, getNameInitial())); // ELECT-10129
  }

  public Long getRelativePosition() {
    return politischeGruppe.getRelativePosition();
  }

  public void setRelativePosition(Long position) {
    politischeGruppe.setRelativePosition(position);
  }

  public Long getVorgegebenePosition() {
    return politischeGruppe.getVorgegebenePosition();
  }

  public void setVorgegebenePosition(Long position) {
    politischeGruppe.setVorgegebenePosition(position);
  }

  public Optional<Listenkandidatur> getErsteListenkandidaturRepraesentativ(EntityAufStimmzettelDaten entityAufStimmzettelDaten) {
    for (Liste liste : collectListenByGruppe(entityAufStimmzettelDaten)) {
      List<Listenkandidatur> listenkandidaturen = liste.getListenkandidaturenAufStimmzettelSortiertNachPosition(entityAufStimmzettelDaten);

      if (!listenkandidaturen.isEmpty()) {
        return Optional.of(listenkandidaturen.get(0));
      }
    }

    return Optional.empty();
  }

  public Optional<WahlvorschlagsPerson> getWahlvorschlagsPersonBeauftragter(EntityAufStimmzettelDaten entityAufStimmzettelDaten, AbstractWahlvorschlagsPersonDAO wahlvorschlagsPersonDAO) {
    WahlvorschlagsPersonTyp wahlvorschlagsPersonTyp = WahlvorschlagsPersonTyp.BEAUFTRAGTER;

    for (Liste liste : collectListenByGruppe(entityAufStimmzettelDaten)) {
      Collection<WahlvorschlagsPerson> wahlvorschlagsPersonen = wahlvorschlagsPersonDAO.findByWahlvorschlagOhneUnterstuetzer(liste).stream()
          .filter(wahlvorschlagsPerson -> Objects.equals(wahlvorschlagsPerson.getWahlvorschlagsPersonTyp(), wahlvorschlagsPersonTyp)).collect(toSet());

      if (!wahlvorschlagsPersonen.isEmpty()) {
        return Optional.of(wahlvorschlagsPersonen.iterator().next());
      }
    }

    return Optional.empty();
  }

  private List<Liste> collectListenByGruppe(EntityAufStimmzettelDaten entityAufStimmzettelDaten) {
    return entityAufStimmzettelDaten.collectListenInWahl(getWahl()).stream().collect(groupingBy(Liste::getGruppe)).getOrDefault(politischeGruppe, emptyList());
  }

  private NlGruppeCustomData getCustomData() {
    return politischeGruppe.getCustomData(NlGruppeCustomData.class);
  }

  @Deprecated // veraltet, beachtet abgelehnte Listen nicht
  public static NlPolitischeGruppeArt bestimmeEinreichungsart(Collection<Liste> alleListenDerGruppe, EntityAufStimmzettelDaten entityAufStimmzettelDaten) {
    if (alleListenDerGruppe.isEmpty()) {
      return NlPolitischeGruppeArt.UNBEKANNT;
    }

    Gruppe gruppe = bestimmeGruppe(alleListenDerGruppe);

    if (gruppe != null) {
      NlPolitischeGruppeArt nlPolitischeGruppeArt = gruppe.getCustomData(NlGruppeCustomData.class).getNlPolitischeGruppeArt();

      if (nlPolitischeGruppeArt != null) { // nur im WAS der Fall
        return nlPolitischeGruppeArt;
      }
    }

    for (StelInformationen stelInformationen : bestimmeStelInformationen(alleListenDerGruppe, entityAufStimmzettelDaten)) {
      return stelInformationen.getNlPolitischeGruppeArt(); // ein Repräsentant reicht
    }

    return NlPolitischeGruppeArt.UNBEKANNT;
  }

  @Deprecated // veraltet, beachtet abgelehnte Listen nicht
  public static Set<StelInformationen> bestimmeStelInformationen(Collection<Liste> alleListenDerGruppe, EntityAufStimmzettelDaten entityAufStimmzettelDaten) {
    Set<StelInformationen> einzelneStels = new HashSet<>();

    for (Liste liste : alleListenDerGruppe) {
      if (NlListe.isBetrachteBeiStelErmittlung(liste)) {
        StelInformationen stelInformationen = NlListe.getStelInformationen(liste, alleListenDerGruppe, entityAufStimmzettelDaten);

        if (stelInformationen.getNlPolitischeGruppeArt().isEinzelliste() || stelInformationen.getNlPolitischeGruppeArt().isIdentischeListe()) {
          return singleton(stelInformationen);
        } else {
          einzelneStels.add(stelInformationen);
        }
      }
    }

    return einzelneStels;
  }

  @Nullable
  private static Gruppe bestimmeGruppe(Collection<Liste> alleListenDerGruppe) {
    for (Liste liste : alleListenDerGruppe) {
      return liste.getGruppe();
    }

    return null;
  }

  public List<String> getZulassungAbgelehntBegruendung(Wahlvorschlag wahlvorschlag) {
    return splitZulassungAbgelehntBegruendung(getCustomData().getZulassungAbgelehntBegruendung(wahlvorschlag));
  }

  public void setZulassungAbgelehntBegruendung(Wahlvorschlag wahlvorschlag, String begruendung) {
    getCustomData().setZulassungAbgelehntBegruendung(wahlvorschlag, begruendung);
  }

  public List<String> getZulassungAbgelehntBegruendung(Kandidatur kandidatur) {
    return splitZulassungAbgelehntBegruendung(getCustomData().getZulassungAbgelehntBegruendung(kandidatur));
  }

  public void setZulassungAbgelehntBegruendung(Kandidatur kandidatur, String begruendung) {
    getCustomData().setZulassungAbgelehntBegruendung(kandidatur, begruendung);
  }

  private List<String> splitZulassungAbgelehntBegruendung(String begruendung) {
    if (isNotBlank(begruendung)) {
      return asList(split(begruendung, ZULASSUNG_ABGELEHNT_BEGRUENDUNG_SEPARATOR));
    }

    return emptyList();
  }

  public List<String> formatPruefungenZuBehebenLangtexteExcludingZulassungPrueferExtraInformation(ZulassungPruefer zulassungPruefer, Wahlvorschlag wahlvorschlag) {
    if (wahlvorschlag != null) {
      return formatPruefungenZuBehebenLangtexteExcludingZulassungPrueferExtraInformation(zulassungPruefer.collectWahlrechtspruefungen(wahlvorschlag));
    }

    return emptyList();
  }

  public List<String> formatPruefungenZuBehebenLangtexteIncludingZulassungPrueferExtraInformation(ZulassungPruefer zulassungPruefer, Wahlvorschlag wahlvorschlag) {
    if (wahlvorschlag != null) {
      return formatPruefungenZuBehebenLangtexteIncludingZulassungPrueferExtraInformation(zulassungPruefer.collectWahlrechtspruefungen(wahlvorschlag),
          formatPruefungenZuBehebenKurztexteIncludingZulassungPrueferExtraInformation(zulassungPruefer, wahlvorschlag));
    }

    return emptyList();
  }

  public List<String> formatPruefungenZuBehebenKurztexteIncludingZulassungPrueferExtraInformation(ZulassungPruefer zulassungPruefer, Wahlvorschlag wahlvorschlag) {
    if (wahlvorschlag != null) {
      return zulassungPruefer.getGruendeFuerVerweigerungDerZulassung(wahlvorschlag);
    }

    return emptyList();
  }

  public List<String> formatPruefungenBehobenLangtexte(ZulassungPruefer zulassungPruefer, Wahlvorschlag wahlvorschlag) {
    if (wahlvorschlag != null) {
      return formatPruefungenBehobenLangtexte(zulassungPruefer.collectWahlrechtspruefungen(wahlvorschlag));
    }

    return emptyList();
  }

  public List<String> formatPruefungenBehobenKurztexte(ZulassungPruefer zulassungPruefer, Wahlvorschlag wahlvorschlag) {
    if (wahlvorschlag != null) {
      return formatPruefungenBehobenKurztexte(zulassungPruefer.collectWahlrechtspruefungen(wahlvorschlag));
    }

    return emptyList();
  }

  public List<String> formatPruefungenZuBehebenLangtexteExcludingZulassungPrueferExtraInformation(ZulassungPruefer zulassungPruefer, Kandidatur kandidatur) {
    if (kandidatur != null) {
      return formatPruefungenZuBehebenLangtexteExcludingZulassungPrueferExtraInformation(zulassungPruefer.collectWahlrechtspruefungen(kandidatur));
    }

    return emptyList();
  }

  public List<String> formatPruefungenZuBehebenLangtexteIncludingZulassungPrueferExtraInformation(ZulassungPruefer zulassungPruefer, Kandidatur kandidatur) {
    if (kandidatur != null) {
      return formatPruefungenZuBehebenLangtexteIncludingZulassungPrueferExtraInformation(zulassungPruefer.collectWahlrechtspruefungen(kandidatur),
          formatPruefungenZuBehebenKurztexteIncludingZulassungPrueferExtraInformation(zulassungPruefer, kandidatur));
    }

    return emptyList();
  }

  public List<String> formatPruefungenZuBehebenKurztexteIncludingZulassungPrueferExtraInformation(ZulassungPruefer zulassungPruefer, Kandidatur kandidatur) {
    if (kandidatur != null) {
      return zulassungPruefer.getGruendeFuerVerweigerungDerZulassung(kandidatur);
    }

    return emptyList();
  }

  public List<String> formatPruefungenBehobenLangtexte(ZulassungPruefer zulassungPruefer, Kandidatur kandidatur) {
    if (kandidatur != null) {
      return formatPruefungenBehobenLangtexte(zulassungPruefer.collectWahlrechtspruefungen(kandidatur));
    }

    return emptyList();
  }

  public List<String> formatPruefungenBehobenKurztexte(ZulassungPruefer zulassungPruefer, Kandidatur kandidatur) {
    if (kandidatur != null) {
      return formatPruefungenBehobenKurztexte(zulassungPruefer.collectWahlrechtspruefungen(kandidatur));
    }

    return emptyList();
  }

  private List<String> formatPruefungenZuBehebenLangtexteExcludingZulassungPrueferExtraInformation(List<Wahlrechtspruefung> wahlrechtspruefungen) {
    return formatPruefungenZuBehebenLangtexte(wahlrechtspruefungen);
  }

  private List<String> formatPruefungenZuBehebenLangtexteIncludingZulassungPrueferExtraInformation(List<Wahlrechtspruefung> wahlrechtspruefungen,
      List<String> kurztexteIncludingZulassungPrueferExtraInformation) {
    List<String> kurztexteExcludingZulassungPrueferExtraInformation = formatPruefungenZuBehebenKurztexte(wahlrechtspruefungen);

    List<String> kurztexteZulassungPrueferExtraInformation = new ArrayList<>(kurztexteIncludingZulassungPrueferExtraInformation);
    kurztexteZulassungPrueferExtraInformation.removeAll(kurztexteExcludingZulassungPrueferExtraInformation);

    List<String> result = new ArrayList<>(formatPruefungenZuBehebenLangtexte(wahlrechtspruefungen));
    result.addAll(kurztexteZulassungPrueferExtraInformation);

    return result;
  }

  private List<String> formatPruefungenZuBehebenLangtexte(List<Wahlrechtspruefung> wahlrechtspruefungen) {
    return formatPruefungenTexte(collectWahlrechtspruefungenZuBeheben(wahlrechtspruefungen), NlWahlrechtspruefung::formatLangtext);
  }

  private List<String> formatPruefungenZuBehebenKurztexte(List<Wahlrechtspruefung> wahlrechtspruefungen) {
    return formatPruefungenTexte(collectWahlrechtspruefungenZuBeheben(wahlrechtspruefungen), NlWahlrechtspruefung::formatKurztext);
  }

  private List<String> formatPruefungenBehobenLangtexte(List<Wahlrechtspruefung> wahlrechtspruefungen) {
    return formatPruefungenTexte(collectWahlrechtspruefungenBehoben(wahlrechtspruefungen), NlWahlrechtspruefung::formatLangtext);
  }

  private List<String> formatPruefungenBehobenKurztexte(List<Wahlrechtspruefung> wahlrechtspruefungen) {
    return formatPruefungenTexte(collectWahlrechtspruefungenBehoben(wahlrechtspruefungen), NlWahlrechtspruefung::formatKurztext);
  }

  private List<String> formatPruefungenTexte(List<NlWahlrechtspruefung> wahlrechtspruefungen, Function<NlWahlrechtspruefung, String> textFct) {
    return wahlrechtspruefungen.stream() //
        .map(textFct) //
        .filter(StringUtils::isNotBlank) //
        .collect(toList());
  }

  private List<NlWahlrechtspruefung> collectWahlrechtspruefungenZuBeheben(List<Wahlrechtspruefung> wahlrechtspruefungen) {
    return collectWahlrechtspruefungen(wahlrechtspruefungen, NlWahlrechtspruefung::isZuBeheben);
  }

  private List<NlWahlrechtspruefung> collectWahlrechtspruefungenBehoben(List<Wahlrechtspruefung> wahlrechtspruefungen) {
    return collectWahlrechtspruefungen(wahlrechtspruefungen, NlWahlrechtspruefung::isBehoben);
  }

  private List<NlWahlrechtspruefung> collectWahlrechtspruefungen(List<Wahlrechtspruefung> wahlrechtspruefungen, Predicate<NlWahlrechtspruefung> predicate) {
    return wahlrechtspruefungen.stream() //
        .map(NlWahlrechtspruefung::from) //
        .filter(predicate) //
        .sorted((one, another) -> PruefkriteriumByPositionComparator.ASCENDING.compare(one.getPruefkriterium(), another.getPruefkriterium())) //
        .collect(toList());
  }
}
