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

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import de.ivu.elect.business.ergebniskonflikt.entity.AbstractErgebnisKonfliktLosoptionenCustomData;
import de.ivu.elect.business.ergebniskonflikt.entity.ErgebnisKonfliktCustomDataFormatter;
import de.ivu.elect.business.ergebniskonflikt.entity.ErgebnisKonfliktLosoption;
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.kandidatur.entity.Listenkandidatur;
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.Liste;
import de.ivu.elect.business.wahlvorschlag.entity.Listenverbindung;
import de.ivu.elect.business.wahlvorschlag.entity.ListenverbindungPK;
import de.ivu.elect.business.wahlvorschlag.entity.NlListe;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;
import de.ivu.elect.business.wahlvorschlag.entity.WahlvorschlagPK;
import de.ivu.elect.jee.faces.Messages;

public class OsvLegacyKonfliktCustomData extends AbstractErgebnisKonfliktLosoptionenCustomData {
  private static final long serialVersionUID = 1L;

  private final OsvLegacyErgebnisKonfliktArt osvLegacyErgebnisKonfliktArt;
  private final Long position;
  private final OsvLegacyArtikelnummer artikelnummer;

  private final List<GruppePK> gruppenZurAuswahl = new ArrayList<>();
  private final List<PersonendatenPK> listenkandidaturenZurAuswahl = new ArrayList<>();
  private final List<P2Liste> p2ListeZurAuswahl = new ArrayList<>(); //um die Reihenfolge der P2Listen einzuhalten.

  private OsvLegacyKonfliktLoesung konfliktLoesung = new OsvLegacyKonfliktLoesung();

  public OsvLegacyKonfliktCustomData(OsvLegacyErgebnisKonfliktArt osvLegacyErgebnisKonfliktArt, Long position, OsvLegacyArtikelnummer artikelnummer) {
    this.osvLegacyErgebnisKonfliktArt = osvLegacyErgebnisKonfliktArt;
    this.position = position;
    this.artikelnummer = artikelnummer;
    setAnzahlZuLosen(1);
  }

  public OsvLegacyErgebnisKonfliktArt getOsvLegacyErgebnisKonfliktArt() {
    return osvLegacyErgebnisKonfliktArt;
  }

  public OsvLegacyArtikelnummer getArtikelnummer() {
    return artikelnummer;
  }

  public Long getPosition() {
    return position;
  }

  public List<GruppePK> getGruppenZurAuswahl() {
    return gruppenZurAuswahl;
  }

  public List<WahlvorschlagPK> getListenZurAuswahl() {
    return p2ListeZurAuswahl.stream().filter(P2Liste::isListe).map(P2Liste::getListe).collect(Collectors.toList());
  }



  public void addGruppeZurAuswahl(GruppePK gruppePk) {
    gruppenZurAuswahl.add(gruppePk);
  }

  public List<PersonendatenPK> getListenkandidaturenZurAuswahl() {
    return listenkandidaturenZurAuswahl;
  }

  public List<OsvLegacyStel> getStelsZurAuswahl() {
    return p2ListeZurAuswahl.stream().filter(P2Liste::isStel).map(P2Liste::getStel).collect(Collectors.toList());
  }

  public OsvLegacyKonfliktLoesung getKonfliktLoesung() {
    return konfliktLoesung;
  }

  public void setKonfliktLoesung(OsvLegacyKonfliktLoesung konfliktLoesung) {
    this.konfliktLoesung = konfliktLoesung;
  }

  public List<P2Liste> getP2ListeZurAuswahl() {
    return p2ListeZurAuswahl;
  }

  @Override
  public String toString() {
    return "OsvLegacyKonfliktCustomData{" +
            "osvLegacyErgebnisKonfliktArt=" + osvLegacyErgebnisKonfliktArt +
            ", position=" + position +
            ", gruppenZurAuswahl=" + gruppenZurAuswahl +
            ", listenZurAuswahl=" + getListenZurAuswahl() +
            ", stelsZurAuswahl=" + getStelsZurAuswahl() +
            ", listenkandidaturenZurAuswahl=" + listenkandidaturenZurAuswahl +
            ", konfliktLoesung=" + konfliktLoesung +
            '}';
  }

  public void addStelZurAuswahl(OsvLegacyStel stel) {
    p2ListeZurAuswahl.add(new P2Liste(stel));
  }

  public void addListeZurAuswahl(WahlvorschlagPK pk) {
    p2ListeZurAuswahl.add(new P2Liste(pk));
  }

  public void addListenkandidaturZurAuswahl(PersonendatenPK pk) {
    listenkandidaturenZurAuswahl.add(pk);
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof OsvLegacyKonfliktCustomData)) return false;

    OsvLegacyKonfliktCustomData that = (OsvLegacyKonfliktCustomData) o;

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

    // ELECT-10627
    if (getPosition() != null ? !getPosition().equals(that.getPosition()) : that.getPosition() != null) {
      return false;
    }

    if (getGruppenZurAuswahl() != null ? !getGruppenZurAuswahl().equals(that.getGruppenZurAuswahl()) : that.getGruppenZurAuswahl() != null) {
      return false;
    }
    if (getListenkandidaturenZurAuswahl() != null ? !getListenkandidaturenZurAuswahl().equals(that.getListenkandidaturenZurAuswahl()) : that.getListenkandidaturenZurAuswahl() != null) {
      return false;
    }
    return getP2ListeZurAuswahl() != null ? getP2ListeZurAuswahl().equals(that.getP2ListeZurAuswahl()) : that.getP2ListeZurAuswahl() != null;
  }

  @Override
  public int hashCode() {
    int result = getOsvLegacyErgebnisKonfliktArt() != null ? getOsvLegacyErgebnisKonfliktArt().hashCode() : 0;
    // ELECT-10627
    result = 31 * result + (getPosition() != null ? getPosition().hashCode() : 0);
    result = 31 * result + (getGruppenZurAuswahl() != null ? getGruppenZurAuswahl().hashCode() : 0);
    result = 31 * result + (getListenkandidaturenZurAuswahl() != null ? getListenkandidaturenZurAuswahl().hashCode() : 0);
    result = 31 * result + (getP2ListeZurAuswahl() != null ? getP2ListeZurAuswahl().hashCode() : 0);
    return result;
  }

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

  @Override
  protected Collection<PersonendatenPK> getGelosteKandidaturenPKs() {
    if (konfliktLoesung.isListenKandidaturEntscheidung()) {
      return Collections.singleton(konfliktLoesung.getListenkandidatur());
    }
    return Collections.emptyList();
  }

  @Override
  protected Collection<WahlvorschlagPK> getBeteiligteListenPKs() {
    return p2ListeZurAuswahl.stream()
            .flatMap(p2 -> p2.isStel() ? p2.getStel().getListenDesStels().stream() : Stream.of(p2.getListe()))
            .collect(Collectors.toList());
  }

  @Override
  protected Collection<WahlvorschlagPK> getGelosteListenPKs() {
    return getKonfliktLoesung().isListeEntscheidung() ? Collections.singleton(getKonfliktLoesung().getListe()) :
            getKonfliktLoesung().isStelEntscheidung() ? getKonfliktLoesung().getStel().getListenDesStels() : Collections.emptyList();
  }

  @Override
  protected Collection<ListenverbindungPK> getBeteiligteListenverbindungenPKs() {
    return Collections.emptyList();
  }

  @Override
  protected Collection<ListenverbindungPK> getGelosteListenverbindungenPKs() {
    return Collections.emptyList();
  }

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

  @Override
  protected Collection<GruppePK> getGelostePolitischeGruppenPKs() {
    return getKonfliktLoesung().isGruppeEntscheidung() ? Collections.singleton(getKonfliktLoesung().getGruppe()) :
            Collections.emptyList();
  }

  @Override
  protected Collection<GebietPK> getBeteiligteGebietPKs() {
    return Collections.emptyList();
  }

  @Override
  protected Collection<GebietPK> getGelosteGebietPKs() {
    return Collections.emptyList();
  }

  @Override
  public void loeseKonfliktAutomatisch() {
    if (!gruppenZurAuswahl.isEmpty()) {
      konfliktLoesung.setGruppe(gruppenZurAuswahl.get(0));
    } else if (!p2ListeZurAuswahl.isEmpty()) {
      P2Liste p2Liste = p2ListeZurAuswahl.get(0);
      if (p2Liste.isListe()) {
        konfliktLoesung.setListe(p2Liste.getListe());
      } else if (p2Liste.isStel()) {
        konfliktLoesung.setStel(p2Liste.getStel());
      }
    } else if (!listenkandidaturenZurAuswahl.isEmpty()) {
      konfliktLoesung.setListenkandidatur(listenkandidaturenZurAuswahl.get(0));
    }

    notifyEntity();
  }

  @Override
  public void loeseKonfliktAutomatisch(int indexDerLoesung) {
    throw new UnsupportedOperationException();
  }

  @Override
  public void loeseKonfliktAutomatischZyklisch(int indexDerLoesung) {
    throw new UnsupportedOperationException();
  }

  @Override
  public BigInteger getAnzahlLoesungen() {
    return new BigInteger("1");
  }

  @Override
  public int getAnzahlZyklischerLoesungen() {
    throw new UnsupportedOperationException();
  }

  @Override
  public String createLoesungBeschreibung(Collection<Wahlvorschlag> wahlvorschlaege, Collection<Kandidatur> kandidaturen, Collection<Listenverbindung> listenverbindungen, Collection<PolitischeGruppe> politischeGruppen, Collection<Wahldurchfuehrungsgebiet> gebiete, Messages messages) {
    return "";
  }

  @Override
  public List<ErgebnisKonfliktLosoption> getLosoptionenVerfuegbar(AbstractWahlvorschlagDAO wahlvorschlagDao,
                                                                  AbstractKandidaturDAO kandidaturDao,
                                                                  AbstractListenverbindungDAO listenverbindungDao,
                                                                  AbstractPolitischeGruppeDAO politischeGruppeDao,
                                                                  AbstractWahldurchfuehrungsgebietDAO wahldurchfuehrungsgebietDao,
                                                                  Messages messages) {
    List<ErgebnisKonfliktLosoption> result = Collections.emptyList();

    if (!gruppenZurAuswahl.isEmpty()) {
      result = createLosoptionen(politischeGruppeDao, gruppenZurAuswahl, messages);
    } else if (!p2ListeZurAuswahl.isEmpty()) {
      result = createLosoptionen(wahlvorschlagDao, p2ListeZurAuswahl, messages);
    } else if (!listenkandidaturenZurAuswahl.isEmpty()) {
      result = createLosoptionen(kandidaturDao, listenkandidaturenZurAuswahl, messages);
    }

    return result;
  }

  @Override
  public List<ErgebnisKonfliktLosoption> getLosoptionenGelost(AbstractWahlvorschlagDAO wahlvorschlagDao, AbstractKandidaturDAO kandidaturDao, AbstractListenverbindungDAO listenverbindungDao, AbstractPolitischeGruppeDAO politischeGruppeDao, AbstractWahldurchfuehrungsgebietDAO wahldurchfuehrungsgebietDao, Messages messages) {
    List<ErgebnisKonfliktLosoption> losoption = Collections.emptyList();

    if (konfliktLoesung.isGruppeEntscheidung()) {
      losoption = createLosoptionen(politischeGruppeDao, Collections.singleton(konfliktLoesung.getGruppe()), messages);
    } else if (konfliktLoesung.isP2ListeEntscheidung()) {
      losoption = createLosoptionen(wahlvorschlagDao, Collections.singletonList(konfliktLoesung.getP2Liste()), messages);
    } else if (konfliktLoesung.isListenKandidaturEntscheidung()) {
      losoption = createLosoptionen(kandidaturDao, Collections.singleton(konfliktLoesung.getListenkandidatur()), messages);
    }

    return losoption;
  }

  @Override
  public void setLosoptionenGelost(List<ErgebnisKonfliktLosoption> values) {
    if (values.size() > 1) {
      throw new IllegalStateException("Es muss genau 1 Losoption geben: " + values);
    }

    ErgebnisKonfliktLosoption losoption = values.get(0);
    if (losoption.isInstanceOf(PolitischeGruppe.class)) {
      konfliktLoesung.setGruppe((GruppePK) losoption.getPk());
    } else if (losoption.isInstanceOf(Liste.class)) {
      setGelosteP2Liste((WahlvorschlagPK) losoption.getPk());
    } else if (losoption.isInstanceOf(Listenkandidatur.class)) {
      konfliktLoesung.setListenkandidatur((PersonendatenPK) losoption.getPk());
    }

    notifyEntity();
  }

  @Override
  public String getClassName() {
    return OsvLegacyKonfliktCustomData.class.getName();
  }

  private List<ErgebnisKonfliktLosoption> createLosoptionen(AbstractWahlvorschlagDAO wahlvorschlagDao, List<P2Liste> p2Listen, Messages messages) {
    List<ErgebnisKonfliktLosoption> result = new ArrayList<>();
    boolean listenInnerhalbEinerGruppe = p2Listen.stream().map(p2 -> p2.getGruppe(wahlvorschlagDao)).distinct().count() == 1;
    for (P2Liste p2Liste : p2Listen) {
      if (p2Liste.isListe()) {
        result.add(createLosoptionFuerListe(wahlvorschlagDao, p2Liste.getListe(), listenInnerhalbEinerGruppe, messages));
      } else if (p2Liste.isStel()) {
        result.add(createStelLosoption(wahlvorschlagDao, p2Liste.getStel(), messages));
      }
    }
    return result;
  }

  private ErgebnisKonfliktLosoption createLosoptionFuerListe(AbstractWahlvorschlagDAO wahlvorschlagDao, WahlvorschlagPK pk, boolean innerhalbEinerGruppe, Messages messages) {
    Wahlvorschlag wahlvorschlag = wahlvorschlagDao.findByPrimaryKey(pk);
    String wahlvorschlagDisplayName = createWahlvorschlagDisplayName(wahlvorschlag, messages);
    if (innerhalbEinerGruppe) {
      wahlvorschlagDisplayName += " (" + wahlvorschlag.getGebiet().getName() + ")";
    }
    return new ErgebnisKonfliktLosoption(wahlvorschlag, wahlvorschlagDisplayName, wahlvorschlag.getGebiet());
  }


  private ErgebnisKonfliktLosoption createStelLosoption(AbstractWahlvorschlagDAO wahlvorschlagDao, OsvLegacyStel stel, Messages messages) {
    Liste eineListeDesStels = getEineListeDesStels(stel, wahlvorschlagDao);

    String stelDisplayName = createStelDisplayName(wahlvorschlagDao, stel,
            NlListe.getStelNummerWas(eineListeDesStels).orElse(null), createWahlvorschlagDisplayName(eineListeDesStels, messages));

    return new ErgebnisKonfliktLosoption(eineListeDesStels, stelDisplayName, eineListeDesStels.getGebiet());
  }

  private String createWahlvorschlagDisplayName(Wahlvorschlag wahlvorschlag, Messages messages) {
    return ErgebnisKonfliktCustomDataFormatter.formatWahlvorschlagDisplayName(messages, wahlvorschlag, w -> "");
  }

  private String createStelDisplayName(AbstractWahlvorschlagDAO wahlvorschlagDao,
                                       OsvLegacyStel stel, Integer stelNummer, String wahlvorschlagDisplayName) {

    String gebietString = stel.getListenDesStels().stream()
            .map(liste -> wahlvorschlagDao.findByPrimaryKey(liste).getGebiet().getName())
            .collect(Collectors.joining(", "));

    return wahlvorschlagDisplayName + ", Stel: " + stelNummer + " (" + gebietString + ")";
  }

  private Liste getEineListeDesStels(OsvLegacyStel stel, AbstractWahlvorschlagDAO wahlvorschlagDAO) {
    return wahlvorschlagDAO.findByPrimaryKey(stel.getListenDesStels().iterator().next()).castTo(Liste.class).getEntity();
  }

  private void setGelosteP2Liste(WahlvorschlagPK pk) {
    Set<WahlvorschlagPK> listenDesStels = p2ListeZurAuswahl.stream()
            .filter(p2Liste -> p2Liste.isStel() && p2Liste.getStel().getListenDesStels().contains(pk))
            .findAny().map(p2Liste -> p2Liste.getStel().getListenDesStels()).orElse(null);

    if (listenDesStels == null) {
      konfliktLoesung.setListe(pk);
    } else {
      konfliktLoesung.setStel(new OsvLegacyStel(listenDesStels));
    }
  }
}
