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

import static java.util.Collections.emptySet;

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

import com.google.common.math.BigIntegerMath;

import de.ivu.elect.business.ergebniskonflikt.control.util.SublistList;
import de.ivu.elect.business.gebietsbaum.boundary.AbstractWahldurchfuehrungsgebietDAO;
import de.ivu.elect.business.gebietsbaum.entity.GebietPK;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.boundary.AbstractPolitischeGruppeDAO;
import de.ivu.elect.business.gruppe.entity.GruppePK;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.kandidatur.boundary.AbstractKandidaturDAO;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;
import de.ivu.elect.business.person.entity.PersonendatenPK;
import de.ivu.elect.business.wahlvorschlag.boundary.AbstractListenverbindungDAO;
import de.ivu.elect.business.wahlvorschlag.boundary.AbstractWahlvorschlagDAO;
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.EntityLongPK;

public abstract class AbstractPolitischeGruppenUndListenverbindungenAuswaehlenErgebnisKonfliktCustomData extends AbstractErgebnisKonfliktLosoptionenCustomData {

  private static final long serialVersionUID = 1L;

  private final Set<GruppePK> politischeGruppePKsZurAuswahl = new LinkedHashSet<>();

  private final Set<GruppePK> politischeGruppePKsGelost = new LinkedHashSet<>();

  private final Set<ListenverbindungPK> listenverbindungPKsZurAuswahl = new LinkedHashSet<>();

  private final Set<ListenverbindungPK> listenverbindungPKsGelost = new LinkedHashSet<>();

  @Override
  protected Collection<PersonendatenPK> getBeteiligteKandidaturenPKs() {
    return emptySet();
  }

  @Override
  protected Collection<PersonendatenPK> getGelosteKandidaturenPKs() {
    return emptySet();
  }

  @Override
  protected Collection<GruppePK> getBeteiligtePolitischeGruppenPKs() {
    return politischeGruppePKsZurAuswahl;
  }

  @Override
  protected Collection<GruppePK> getGelostePolitischeGruppenPKs() {
    return politischeGruppePKsGelost;
  }

  @Override
  protected Collection<ListenverbindungPK> getBeteiligteListenverbindungenPKs() {
    return listenverbindungPKsZurAuswahl;
  }

  @Override
  protected Collection<ListenverbindungPK> getGelosteListenverbindungenPKs() {
    return listenverbindungPKsGelost;
  }

  @Override
  protected Collection<WahlvorschlagPK> getBeteiligteListenPKs() {
    return emptySet();
  }

  @Override
  protected Collection<WahlvorschlagPK> getGelosteListenPKs() {
    return emptySet();
  }

  @Override
  protected Collection<GebietPK> getBeteiligteGebietPKs() {
    return emptySet();
  }

  @Override
  protected Collection<GebietPK> getGelosteGebietPKs() {
    return emptySet();
  }

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

  public Set<GruppePK> getPolitischeGruppePKsZurAuswahl() {
    return politischeGruppePKsZurAuswahl;
  }

  public void setPolitischeGruppePKsZurAuswahl(Collection<GruppePK> politischeGruppePKsZurAuswahl) {
    this.politischeGruppePKsZurAuswahl.clear();
    this.politischeGruppePKsZurAuswahl.addAll(politischeGruppePKsZurAuswahl);

    notifyEntity();
  }

  public void setPolitischeGruppenZurAuswahl(Collection<PolitischeGruppe> politischeGruppenZurAuswahl) {
    this.politischeGruppePKsZurAuswahl.clear();
    for (PolitischeGruppe politischeGruppe : politischeGruppenZurAuswahl) {
      politischeGruppePKsZurAuswahl.add(politischeGruppe.getPk());
    }

    notifyEntity();
  }

  public Set<GruppePK> getPolitischeGruppePKsGelost() {
    return politischeGruppePKsGelost;
  }

  public void setPolitischeGruppePKsGelost(Collection<GruppePK> politischeGruppePKsGelost) {
    this.politischeGruppePKsGelost.clear();
    this.politischeGruppePKsGelost.addAll(politischeGruppePKsGelost);

    notifyEntity();
  }

  public void setPolitischeGruppenGelost(Collection<PolitischeGruppe> politischeGruppenGelost) {
    this.politischeGruppePKsGelost.clear();
    for (PolitischeGruppe politischeGruppe : politischeGruppenGelost) {
      politischeGruppePKsGelost.add(politischeGruppe.getPk());
    }

    notifyEntity();
  }

  public Set<ListenverbindungPK> getListenverbindungPKsZurAuswahl() {
    return listenverbindungPKsZurAuswahl;
  }

  public void setListenverbindungPKsZurAuswahl(Collection<ListenverbindungPK> listenverbindungPKsZurAuswahl) {
    this.listenverbindungPKsZurAuswahl.clear();
    this.listenverbindungPKsZurAuswahl.addAll(listenverbindungPKsZurAuswahl);

    notifyEntity();
  }

  public Set<ListenverbindungPK> getListenverbindungPKsGelost() {
    return listenverbindungPKsGelost;
  }

  public void setListenverbindungPKsGelost(Collection<ListenverbindungPK> listenverbindungPKsGelost) {
    this.listenverbindungPKsGelost.clear();
    this.listenverbindungPKsGelost.addAll(listenverbindungPKsGelost);

    notifyEntity();
  }

  @Override
  public List<ErgebnisKonfliktLosoption> getLosoptionenVerfuegbar(ErgebnisKonfliktCustomDataFormatter ergebnisKonfliktCustomDataFormatter, AbstractWahlvorschlagDAO wahlvorschlagDAO,
      AbstractKandidaturDAO kandidaturDAO, AbstractListenverbindungDAO listenverbindungDAO, AbstractPolitischeGruppeDAO politischeGruppeDAO,
      AbstractWahldurchfuehrungsgebietDAO wahldurchfuehrungsgebietDAO) {
    return createLosoptionen(ergebnisKonfliktCustomDataFormatter, politischeGruppeDAO, getPolitischeGruppePKsZurAuswahl(), listenverbindungDAO, getListenverbindungPKsZurAuswahl());
  }

  @Override
  public List<ErgebnisKonfliktLosoption> getLosoptionenGelost(ErgebnisKonfliktCustomDataFormatter ergebnisKonfliktCustomDataFormatter, AbstractWahlvorschlagDAO wahlvorschlagDAO,
      AbstractKandidaturDAO kandidaturDAO, AbstractListenverbindungDAO listenverbindungDAO, AbstractPolitischeGruppeDAO politischeGruppeDAO,
      AbstractWahldurchfuehrungsgebietDAO wahldurchfuehrungsgebietDAO) {
    return createLosoptionen(ergebnisKonfliktCustomDataFormatter, politischeGruppeDAO, getPolitischeGruppePKsGelost(), listenverbindungDAO, getListenverbindungPKsGelost());
  }

  @Override
  public void setLosoptionenGelost(List<ErgebnisKonfliktLosoption> losoptionenGelost) {
    Set<GruppePK> politischeGruppePKs = new LinkedHashSet<>();
    Set<ListenverbindungPK> listenverbindungPKs = new LinkedHashSet<>();

    for (ErgebnisKonfliktLosoption losoption : losoptionenGelost) {
      if (losoption.isInstanceOf(PolitischeGruppe.class)) {
        politischeGruppePKs.add((GruppePK) losoption.getPk());
      } else if (losoption.isInstanceOf(Listenverbindung.class)) {
        listenverbindungPKs.add((ListenverbindungPK) losoption.getPk());
      }
    }

    setPolitischeGruppePKsGelost(politischeGruppePKs);
    setListenverbindungPKsGelost(listenverbindungPKs);
  }

  /**
   * equals() bezieht sich nur auf die Attribute, die die zu entscheidene Situation beschreiben, aber nicht auf die Attribute, die die Lösung / Auswahl / Entscheidung betreffen.
   *
   * @see java.lang.Object#equals(java.lang.Object)
   */
  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }

    if (!(o instanceof AbstractPolitischeGruppenUndListenverbindungenAuswaehlenErgebnisKonfliktCustomData)) {
      return false;
    }

    AbstractPolitischeGruppenUndListenverbindungenAuswaehlenErgebnisKonfliktCustomData that = (AbstractPolitischeGruppenUndListenverbindungenAuswaehlenErgebnisKonfliktCustomData) o;

    if (getAnzahlZuLosen() != that.getAnzahlZuLosen()) {
      return false;
    }

    if (!politischeGruppePKsZurAuswahl.equals(that.politischeGruppePKsZurAuswahl)) {
      return false;
    }

    if (!listenverbindungPKsZurAuswahl.equals(that.listenverbindungPKsZurAuswahl)) {
      return false;
    }

    return true;
  }

  @Override
  public int hashCode() {
    int result = (int) (getAnzahlZuLosen() ^ getAnzahlZuLosen() >>> 32);

    result = 31 * result + politischeGruppePKsZurAuswahl.hashCode();
    result = 31 * result + listenverbindungPKsZurAuswahl.hashCode();

    return result;
  }

  @Override
  public void loeseKonfliktAutomatisch() {
    long anzahl = getAnzahlZuLosen();
    Set<ListenverbindungPK> listenverbindungPKsGelost = waehleDieErstenAus(anzahl, getBeteiligteListenverbindungenPKs());
    setListenverbindungPKsGelost(listenverbindungPKsGelost);

    anzahl -= listenverbindungPKsGelost.size();
    Set<GruppePK> politischeGruppePKsGelost = waehleDieErstenAus(anzahl, getBeteiligtePolitischeGruppenPKs());
    setPolitischeGruppePKsGelost(politischeGruppePKsGelost);
  }

  @Override
  public void loeseKonfliktAutomatisch(int indexDerLoesung) {
    List<EntityLongPK> allePKs = getGelosteListenverbindungPKsUndGruppePKs();
    List<EntityLongPK> gelost = new SublistList<>(allePKs, (int) getAnzahlZuLosen()).get(indexDerLoesung);
    setGelosteListenverbindungPKsUndGruppePKs(gelost);
  }

  @Override
  public void loeseKonfliktAutomatischZyklisch(int indexDerLoesung) {
    List<EntityLongPK> allePKs = getGelosteListenverbindungPKsUndGruppePKs();
    List<EntityLongPK> gelost = permutiereZyklisch(indexDerLoesung, allePKs);
    setGelosteListenverbindungPKsUndGruppePKs(gelost);
  }

  private List<EntityLongPK> getGelosteListenverbindungPKsUndGruppePKs() {
    List<EntityLongPK> allePKs = new ArrayList<>();
    allePKs.addAll(asSortedList(getBeteiligteListenverbindungenPKs()));
    allePKs.addAll(asSortedList(getBeteiligtePolitischeGruppenPKs()));
    return allePKs;
  }

  private void setGelosteListenverbindungPKsUndGruppePKs(List<EntityLongPK> allePKs) {
    Set<ListenverbindungPK> listenverbindungPKsGelost = new LinkedHashSet<>();
    Set<GruppePK> politischeGruppePKsGelost = new LinkedHashSet<>();
    for (EntityLongPK pk : allePKs) {
      if (pk instanceof ListenverbindungPK) {
        listenverbindungPKsGelost.add((ListenverbindungPK) pk);
      } else if (pk instanceof GruppePK) {
        politischeGruppePKsGelost.add((GruppePK) pk);
      }
    }

    setListenverbindungPKsGelost(listenverbindungPKsGelost);
    setPolitischeGruppePKsGelost(politischeGruppePKsGelost);
  }

  @Override
  public BigInteger getAnzahlLoesungen() {
    return BigIntegerMath.binomial(getAnzahlZyklischerLoesungen(), (int) getAnzahlZuLosen());
  }

  @Override
  public int getAnzahlZyklischerLoesungen() {
    return getBeteiligteListenverbindungenPKs().size() + getBeteiligtePolitischeGruppenPKs().size();
  }

  @Override
  public String createLoesungBeschreibung(ErgebnisKonfliktCustomDataFormatter ergebnisKonfliktCustomDataFormatter, Collection<Wahlvorschlag> ignore1, Collection<Kandidatur> ignore2,
      Collection<Listenverbindung> listenverbindungen, Collection<PolitischeGruppe> politischeGruppen, Collection<Wahldurchfuehrungsgebiet> ignore3) {
    return createLoesungBeschreibungFuerPolitischeGruppenUndListenverbindungen(ergebnisKonfliktCustomDataFormatter, politischeGruppen, getGelostePolitischeGruppenPKs(), listenverbindungen,
        getGelosteListenverbindungenPKs());
  }

  public String createErgebnisKonfliktBeschreibung(ErgebnisKonfliktCustomDataFormatter ergebnisKonfliktCustomDataFormatter, String typBeschreibung, Collection<PolitischeGruppe> politischeGruppen,
      Collection<Listenverbindung> listenverbindungen) {
    return typBeschreibung + ": " + createErgebnisKonfliktBeschreibung(ergebnisKonfliktCustomDataFormatter, politischeGruppen, listenverbindungen);
  }

  public String createErgebnisKonfliktBeschreibung(ErgebnisKonfliktCustomDataFormatter ergebnisKonfliktCustomDataFormatter, Collection<PolitischeGruppe> politischeGruppen,
      Collection<Listenverbindung> listenverbindungen) {
    Collection<String> result = new ArrayList<>();

    for (PolitischeGruppe politischeGruppe : politischeGruppen) {
      result.add(createPolitischeGruppeDisplayName(ergebnisKonfliktCustomDataFormatter, politischeGruppe));
    }

    for (Listenverbindung listenverbindung : listenverbindungen) {
      result.add(createListenverbindungDisplayName(ergebnisKonfliktCustomDataFormatter, listenverbindung));
    }

    return joinErgebnisKonfliktBeschreibungItems(result);
  }
}
