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

import static org.apache.commons.lang3.StringUtils.join;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.GebietPK;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.entity.GruppePK;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;
import de.ivu.elect.business.person.entity.PersonendatenPK;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.business.wahlvorschlag.entity.Listenverbindung;
import de.ivu.elect.business.wahlvorschlag.entity.ListenverbindungPK;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;
import de.ivu.elect.business.wahlvorschlag.entity.WahlvorschlagPK;
import de.ivu.elect.jee.entity.Entity;
import de.ivu.elect.jee.entity.EntityLongPK;
import de.ivu.elect.jee.entity.customdata.DefaultCustomData;
import de.ivu.elect.jee.faces.Messages;

public abstract class AbstractErgebnisKonfliktCustomData extends DefaultCustomData implements ErgebnisKonfliktCustomData {

  private static final long serialVersionUID = 1L;

  public static final String DEFAULT_ITEM_SEPARATOR = ErgebnisKonfliktCustomDataFormatter.DEFAULT_ITEM_SEPARATOR;

  protected AbstractErgebnisKonfliktCustomData() {
    super();
  }

  protected abstract Collection<PersonendatenPK> getBeteiligteKandidaturenPKs();

  protected abstract Collection<PersonendatenPK> getGelosteKandidaturenPKs();

  protected abstract Collection<WahlvorschlagPK> getBeteiligteListenPKs();

  protected abstract Collection<WahlvorschlagPK> getGelosteListenPKs();

  protected abstract Collection<ListenverbindungPK> getBeteiligteListenverbindungenPKs();

  protected abstract Collection<ListenverbindungPK> getGelosteListenverbindungenPKs();

  protected abstract Collection<GruppePK> getBeteiligtePolitischeGruppenPKs();

  protected abstract Collection<GruppePK> getGelostePolitischeGruppenPKs();

  protected abstract Collection<GebietPK> getBeteiligteGebietPKs();

  protected abstract Collection<GebietPK> getGelosteGebietPKs();

  public abstract void loeseKonfliktAutomatisch();

  @Deprecated // unklar wofür und was das bedeutet
  public abstract void loeseKonfliktAutomatisch(int indexDerLoesung);

  @Deprecated // unklar wofür und was das bedeutet
  public abstract void loeseKonfliktAutomatischZyklisch(int indexDerLoesung);

  public abstract BigInteger getAnzahlLoesungen();

  public abstract int getAnzahlZyklischerLoesungen();

  @Override
  public boolean isFachlichGleich(ErgebnisKonfliktCustomData other) {
    return equals(other);
  }

  public abstract String createLoesungBeschreibung(Collection<Wahlvorschlag> wahlvorschlaege, Collection<Kandidatur> kandidaturen, Collection<Listenverbindung> listenverbindungen,
      Collection<PolitischeGruppe> politischeGruppen, Collection<Wahldurchfuehrungsgebiet> gebiete, Messages messages);

  public String createLoesungBeschreibungFuerListenUndListenverbindungen(Collection<Wahlvorschlag> wahlvorschlaege, Collection<WahlvorschlagPK> wahlvorschlagPks,
      Collection<Listenverbindung> listenverbindungen, Collection<ListenverbindungPK> listenverbindungPks, Messages messages) {
    return createLoesungBeschreibungFuerListenUndListenverbindungen(mapEntityPerPk(wahlvorschlaege), wahlvorschlagPks, mapEntityPerPk(listenverbindungen), listenverbindungPks, messages);
  }

  public String createLoesungBeschreibungFuerListenUndListenverbindungen(Map<WahlvorschlagPK, Wahlvorschlag> wahlvorschlagPerPK, Collection<WahlvorschlagPK> wahlvorschlagPKs,
      Map<ListenverbindungPK, Listenverbindung> listenverbindungPerPK, Collection<ListenverbindungPK> listenverbindungPKs, Messages messages) {
    Collection<String> result = new ArrayList<>();

    Map<WahlvorschlagPK, String> displayNamePerWahlvorschlag = ErgebnisKonfliktCustomDataFormatter.collectDisplayNamePerWahlvorschlag(messages, wahlvorschlagPerPK, wahlvorschlagPKs);

    // NOTE, dass die Reihenfolge der PKs ggf. eine Rolle spielt!
    for (WahlvorschlagPK wahlvorschlagPK : wahlvorschlagPKs) {
      result.add(createWahlvorschlagDisplayName(displayNamePerWahlvorschlag, wahlvorschlagPerPK.get(wahlvorschlagPK)));
    }

    // NOTE, dass die Reihenfolge der PKs ggf. eine Rolle spielt!
    for (ListenverbindungPK listenverbindungPK : listenverbindungPKs) {
      result.add(createListenverbindungDisplayName(messages, listenverbindungPerPK.get(listenverbindungPK)));
    }

    return joinErgebnisKonfliktLoesungItems(result);
  }

  public String createLoesungBeschreibungFuerListenUndPolitischeGruppen(Collection<Wahlvorschlag> wahlvorschlaege, Collection<WahlvorschlagPK> wahlvorschlagPks,
      Collection<PolitischeGruppe> politischeGruppen, Collection<GruppePK> politischeGruppePks, Messages messages) {
    return createLoesungBeschreibungFuerListenUndPolitischeGruppen(mapEntityPerPk(wahlvorschlaege), wahlvorschlagPks, mapEntityPerPk(politischeGruppen), politischeGruppePks, messages);
  }

  public String createLoesungBeschreibungFuerListenUndPolitischeGruppen(Map<WahlvorschlagPK, Wahlvorschlag> wahlvorschlagPerPK, Collection<WahlvorschlagPK> wahlvorschlagPKs,
      Map<GruppePK, PolitischeGruppe> gruppePerPK, Collection<GruppePK> gruppePKs, Messages messages) {
    Collection<String> result = new ArrayList<>();

    Map<WahlvorschlagPK, String> displayNamePerWahlvorschlag = ErgebnisKonfliktCustomDataFormatter.collectDisplayNamePerWahlvorschlag(messages, wahlvorschlagPerPK, wahlvorschlagPKs);

    // NOTE, dass die Reihenfolge der PKs ggf. eine Rolle spielt!
    for (WahlvorschlagPK wahlvorschlagPK : wahlvorschlagPKs) {
      result.add(createWahlvorschlagDisplayName(displayNamePerWahlvorschlag, wahlvorschlagPerPK.get(wahlvorschlagPK)));
    }

    // NOTE, dass die Reihenfolge der PKs ggf. eine Rolle spielt!
    for (GruppePK gruppePK : gruppePKs) {
      result.add(createPolitischeGruppeDisplayName(messages, gruppePerPK.get(gruppePK)));
    }

    return joinErgebnisKonfliktLoesungItems(result);
  }

  protected String createKandidaturDisplayName(Map<PersonendatenPK, String> displayNamePerKandidatur, Kandidatur kandidatur) {
    if (kandidatur == null) {
      return "";
    }

    return displayNamePerKandidatur.getOrDefault(kandidatur.getPk(), "");
  }

  protected String createWahlvorschlagDisplayName(Map<WahlvorschlagPK, String> displayNamePerWahlvorschlag, Wahlvorschlag wahlvorschlag) {
    if (wahlvorschlag == null) {
      return "";
    }

    return displayNamePerWahlvorschlag.getOrDefault(wahlvorschlag.getPk(), "");
  }

  protected String createListenverbindungDisplayName(Messages messages, Listenverbindung listenverbindung) {
    return ErgebnisKonfliktCustomDataFormatter.formatListenverbindungDisplayName(listenverbindung, messages);
  }

  protected String createPolitischeGruppeDisplayName(Messages messages, PolitischeGruppe politischeGruppe) {
    return ErgebnisKonfliktCustomDataFormatter.formatPolitischeGruppeDisplayName(politischeGruppe, messages);
  }

  protected String createGebietDisplayName(Messages messages, Gebiet gebiet) {
    return ErgebnisKonfliktCustomDataFormatter.formatGebietDisplayName(gebiet, messages);
  }

  protected String joinErgebnisKonfliktLoesungItems(Collection<String> items) {
    return join(items, DEFAULT_ITEM_SEPARATOR);
  }

  protected String joinErgebnisKonfliktBeschreibungItems(Collection<String> items) {
    return join(items, DEFAULT_ITEM_SEPARATOR);
  }

  protected static <E extends Entity<P, ? >, P extends EntityLongPK> Map<P, E> mapEntityPerPk(Collection<E> entities) {
    Map<P, E> result = new HashMap<>();
    for (E entity : entities) {
      result.put(entity.getPk(), entity);
    }

    return result;
  }

  protected <T extends Comparable< ? super T>> Set<T> waehleDieErstenAus(long anzahl, Collection<T> pksZurAuswahl) {
    List<T> auswahl = asSortedList(pksZurAuswahl);
    int sichereAnzahl = (int) Math.min(anzahl, auswahl.size());
    Set<T> gelost = new LinkedHashSet<>(auswahl.subList(0, sichereAnzahl));
    return gelost;
  }

  protected <T extends Comparable< ? super T>> Set<T> waehleZyklischAus(int indexDerLoesung, long anzahl, Collection<T> pksZurAuswahl) {
    List<T> auswahl = permutiereZyklisch(indexDerLoesung, asSortedList(pksZurAuswahl));
    int sichereAnzahl = (int) Math.min(anzahl, auswahl.size());
    Set<T> gelost = new LinkedHashSet<>(auswahl.subList(0, sichereAnzahl));
    return gelost;
  }

  protected <T> List<T> permutiereZyklisch(int indexDerLoesung, Collection<T> pks) {
    List<T> pksZurAuswahl = new ArrayList<>(pks);
    int size = pksZurAuswahl.size();
    List<T> permutiertePKs = new ArrayList<>();
    for (int i = 0; i < size; i++) {
      permutiertePKs.add(pksZurAuswahl.get((indexDerLoesung + i) % size));
    }
    return permutiertePKs;
  }

  protected <T extends Comparable< ? super T>> List<T> asSortedList(Collection<T> pks) {
    List<T> result = new ArrayList<>(pks);
    Collections.sort(result);
    return result;
  }

  @Override
  public Gebiet getGebiet(Collection<Kandidatur> alleKandidaturen, Collection<Liste> alleListen, Collection<Listenverbindung> alleListenverbindungen) {
    Gebiet gebiet = null;

    for (Kandidatur kandidatur : getBeteiligteKandidaturen(alleKandidaturen)) {
      gebiet = kandidatur.getWahlvorschlag().getGebiet();
      break;
    }

    if (gebiet == null) {
      for (Liste liste : getBeteiligteListen(alleListen)) {
        gebiet = liste.getGebiet();
        break;
      }
    }
    if (gebiet == null) {
      for (Listenverbindung listenverbindung : getBeteiligteListenverbindungen(alleListenverbindungen)) {
        gebiet = listenverbindung.getErstesGebiet();
        break;
      }
    }

    return gebiet;
  }

  protected <K extends Kandidatur> List<K> getKandidaturenAusPks(Collection<K> alleKandidaturen, Collection<PersonendatenPK> personendatenPKs) {
    List<K> result = new ArrayList<>();
    Map<PersonendatenPK, K> kandididaturenNachPks = Kandidatur.mappeNachPk(alleKandidaturen);
    for (PersonendatenPK personendatenPK : personendatenPKs) {
      K kandidatur = kandididaturenNachPks.get(personendatenPK);
      if (kandidatur == null) {
        throw new IllegalStateException("Kandidatur nicht gefunden!: Suche personendatenPK=" + personendatenPK + ", habe: " + alleKandidaturen);
      }
      result.add(kandidatur);
    }

    return result;
  }

  protected List<Listenverbindung> getListenverbindungAusPKs(Collection<Listenverbindung> alleListenverbindungen, Collection<ListenverbindungPK> listenverbindungPKs) {
    List<Listenverbindung> result = new ArrayList<>();

    Map<ListenverbindungPK, Listenverbindung> listenVerbindungNachPk = Listenverbindung.mappeNachPk(alleListenverbindungen);
    for (ListenverbindungPK listenverbindungPK : listenverbindungPKs) {
      Listenverbindung listenverbindung = listenVerbindungNachPk.get(listenverbindungPK);
      if (listenverbindung == null) {
        throw new IllegalStateException("Listenverbindung nicht gefunden!: Suche listenverbindungPK=" + listenverbindungPK + ", habe: " + alleListenverbindungen);
      }

      result.add(listenverbindung);
    }

    return result;
  }

  protected List<Liste> getListenAusPKs(Collection<Liste> alleListen, Collection<WahlvorschlagPK> listenPKs) {
    List<Liste> result = new ArrayList<>();

    Map<WahlvorschlagPK, Liste> listeNachPk = Liste.mappeNachPk(alleListen);
    for (WahlvorschlagPK wahlvorschlagPK : listenPKs) {
      Liste liste = listeNachPk.get(wahlvorschlagPK);
      if (liste == null) {
        throw new IllegalStateException("Liste nicht gefunden!: Suche wahlvorschlagPK=" + wahlvorschlagPK + ", habe: " + alleListen);
      }
      result.add(liste);
    }

    return result;
  }

  protected List<PolitischeGruppe> getPolitischeGruppenAusPKs(Collection<PolitischeGruppe> allePolitischenGruppen, Collection<GruppePK> politischeGruppenPKs) {
    List<PolitischeGruppe> result = new ArrayList<>();

    Map<GruppePK, PolitischeGruppe> listenVerbindungNachPk = PolitischeGruppe.mappeNachPk(allePolitischenGruppen);
    for (GruppePK politischeGruppePK : politischeGruppenPKs) {
      PolitischeGruppe politischeGruppe = listenVerbindungNachPk.get(politischeGruppePK);
      if (politischeGruppe == null) {
        throw new IllegalStateException("PolitischeGruppe nicht gefunden!: Suche politischeGruppePK=" + politischeGruppePK + ", habe: " + allePolitischenGruppen);
      }

      result.add(politischeGruppe);
    }

    return result;
  }

  protected List<Wahldurchfuehrungsgebiet> getGelosteGebieteAusPKs(Collection<Wahldurchfuehrungsgebiet> alleGebiete, Collection<GebietPK> gebietPKs) {
    List<Wahldurchfuehrungsgebiet> result = new ArrayList<>();

    Map<GebietPK, Wahldurchfuehrungsgebiet> gebieteNachPk = Wahldurchfuehrungsgebiet.mappeNachPk(alleGebiete);
    for (GebietPK gebietPK : gebietPKs) {
      Wahldurchfuehrungsgebiet gebiet = gebieteNachPk.get(gebietPK);
      if (gebiet == null) {
        throw new IllegalStateException("Wahldurchfuehrungsgebiet nicht gefunden!: Suche gebietPK=" + gebietPK + ", habe: " + alleGebiete);
      }

      result.add(gebiet);
    }

    return result;
  }

  @Override
  public <K extends Kandidatur> List<K> getBeteiligteKandidaturen(Collection<K> alleKandidaturen) {
    return getKandidaturenAusPks(alleKandidaturen, getBeteiligteKandidaturenPKs());
  }

  @Override
  public List<Liste> getBeteiligteListen(Collection<Liste> alleListen) {
    return getListenAusPKs(alleListen, getBeteiligteListenPKs());
  }

  @Override
  public List<Listenverbindung> getBeteiligteListenverbindungen(Collection<Listenverbindung> alleListenverbindungen) {
    return getListenverbindungAusPKs(alleListenverbindungen, getBeteiligteListenverbindungenPKs());
  }

  @Override
  public List<PolitischeGruppe> getBeteiligtePolitischeGruppen(Collection<PolitischeGruppe> allePolitischenGruppe) {
    return getPolitischeGruppenAusPKs(allePolitischenGruppe, getBeteiligtePolitischeGruppenPKs());
  }

  @Override
  public boolean isKandidaturenBeteiligt() {
    return !getBeteiligteKandidaturenPKs().isEmpty();
  }

  @Override
  public boolean isListenBeteiligt() {
    return !getBeteiligteListenPKs().isEmpty();
  }

  @Override
  public boolean isListenverbindungenBeteiligt() {
    return !getBeteiligteListenverbindungenPKs().isEmpty();
  }

  @Override
  public boolean isPolitischeGruppenBeteiligt() {
    return !getBeteiligtePolitischeGruppenPKs().isEmpty();
  }

  @Override
  public <K extends Kandidatur> List<K> getGelosteKandidaturen(Collection<K> alleKandidaturen) {
    return getKandidaturenAusPks(alleKandidaturen, getGelosteKandidaturenPKs());
  }

  @Override
  public List<Liste> getGelosteListen(Collection<Liste> alleListen) {
    return getListenAusPKs(alleListen, getGelosteListenPKs());
  }

  @Override
  public List<Listenverbindung> getGelosteListenverbindungen(Collection<Listenverbindung> alleListenverbindungen) {
    return getListenverbindungAusPKs(alleListenverbindungen, getGelosteListenverbindungenPKs());
  }

  @Override
  public List<PolitischeGruppe> getGelostePolitischeGruppen(Collection<PolitischeGruppe> allePolitischenGruppen) {
    return getPolitischeGruppenAusPKs(allePolitischenGruppen, getGelostePolitischeGruppenPKs());
  }

  @Override
  public List<Wahldurchfuehrungsgebiet> getGelosteGebiete(Collection<Wahldurchfuehrungsgebiet> alleGebiete) {
    return getGelosteGebieteAusPKs(alleGebiete, getGelosteGebietPKs());
  }

  @Override
  public boolean isKandidaturenGelost() {
    return !getGelosteKandidaturenPKs().isEmpty();
  }

  @Override
  public boolean isListenGelost() {
    return !getGelosteListenPKs().isEmpty();
  }

  @Override
  public boolean isListenverbindungenGelost() {
    return !getGelosteListenPKs().isEmpty();
  }

  @Override
  public boolean isPolitischeGruppenGelost() {
    return !getGelostePolitischeGruppenPKs().isEmpty();
  }
}
