
package de.ivu.elect.business.sitzberechnung.control.absolutemehrheitswahl;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import de.ivu.elect.business.ergebnis.entity.Ergebnis;
import de.ivu.elect.business.ergebnis.entity.ErgebnisKonfliktArt;
import de.ivu.elect.business.ergebnis.entity.ErgebnisLogEintragArt;
import de.ivu.elect.business.ergebnis.entity.GewaehltStatus;
import de.ivu.elect.business.ergebniskonflikt.control.util.ErgebnisKonfliktLoeser;
import de.ivu.elect.business.ergebniskonflikt.entity.ErgebnisKonflikt;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.kandidatur.entity.Direktkandidatur;
import de.ivu.elect.business.kandidatur.entity.WaehlerBenannteKandidaturen;
import de.ivu.elect.business.person.entity.PersonendatenPK;
import de.ivu.elect.business.sitzberechnung.control.util.SitzberechnungProGebietHelper;
import de.ivu.elect.business.sitzberechnung.control.wahlneutral.MehrheitswahlSitzberechnungsalgorithmusInternal;
import de.ivu.elect.business.sitzberechnung.entity.SitzberechnungsalgorithmusKonfiguration;
import de.ivu.elect.business.sitzberechnung.entity.Sitzberechnungseingabe;
import de.ivu.elect.business.sitzberechnung.entity.Sitzberechnungsergebnis;
import de.ivu.elect.util.NotNull;

public abstract class AbstractAbsoluteMehrheitswahlSitzberechnungsalgorithmusInternal implements MehrheitswahlSitzberechnungsalgorithmusInternal<SitzberechnungsalgorithmusKonfiguration> {

  private static final long RANG_ERSTPLATZIERT = 1L;

  private static final long RANG_ZWEITPLATZIERT = 2L;

  protected final AbstractAbsoluteMehrheitswahlSitzberechnungsalgorithmus sitzberechnungsalgorithmus;

  protected final Sitzberechnungseingabe<SitzberechnungsalgorithmusKonfiguration> sitzberechnungseingabe;

  AbstractAbsoluteMehrheitswahlSitzberechnungsalgorithmusInternal(AbstractAbsoluteMehrheitswahlSitzberechnungsalgorithmus sitzberechnungsalgorithmus,
      Sitzberechnungseingabe<SitzberechnungsalgorithmusKonfiguration> sitzberechnungseingabe) {
    super();

    this.sitzberechnungsalgorithmus = sitzberechnungsalgorithmus;

    this.sitzberechnungseingabe = sitzberechnungseingabe;
  }

  @Override
  public Sitzberechnungsergebnis berechneSitzverteilung() {
    return berechneSitzverteilung(collectDirektwahlgebiete(sitzberechnungseingabe));
  }

  public Sitzberechnungsergebnis berechneSitzverteilung(List<Wahldurchfuehrungsgebiet> direktwahlgebiete) {
    Sitzberechnungsergebnis sitzberechnungsergebnis = Sitzberechnungsergebnis.createLeeresErgebnis();

    for (Wahldurchfuehrungsgebiet direktwahlgebiet : direktwahlgebiete) {
      SitzberechnungProGebietHelper helper = new SitzberechnungProGebietHelper(sitzberechnungseingabe, direktwahlgebiet);

      berechneSitzverteilungImGebiet(helper, direktwahlgebiet, collectDirektkandidaturen(sitzberechnungseingabe, direktwahlgebiet));

      // Sitze-Objekte nicht notwendig...

      sitzberechnungsergebnis.addAll(helper.getSitzberechnungsergebnis());
    }

    return sitzberechnungsergebnis;
  }

  private void berechneSitzverteilungImGebiet(SitzberechnungProGebietHelper helper, Wahldurchfuehrungsgebiet direktwahlgebiet, List<Direktkandidatur> direktkandidaturen) {
    AbsoluteMehrheitswahlKandidaturenNachStimmenSortierer sortierer = new AbsoluteMehrheitswahlKandidaturenNachStimmenSortierer();

    long anzahlStimmenWaehlerBenannteDirektkandidaturSonstige = 0L;

    for (Direktkandidatur direktkandidatur : direktkandidaturen) {
      if (direktkandidatur.isZugelassen()) {
        long anzahlStimmen = getAnzahlStimmen(sitzberechnungseingabe, direktkandidatur);

        if (WaehlerBenannteKandidaturen.isWaehlerBenannteDirektkandidaturSonstige(helper.getMessages(), direktkandidatur)) {
          anzahlStimmenWaehlerBenannteDirektkandidaturSonstige += anzahlStimmen;
        } else {
          sortierer.add(anzahlStimmen, direktkandidatur);
        }
      } else {
        helper.createGewaehltNichtZugelassen(direktkandidatur);
      }
    }

    Ergebnis ergebnis = sitzberechnungseingabe.getErgebnis();

    helper.log(ErgebnisLogEintragArt.TITEL, "sitzberechnung.algorithmus.protokoll.titel", direktwahlgebiet.getName(), ergebnis.getWahl().getWahlart().getNameGui(),
        helper.getMessages().lokalisiere(ergebnis.getErgebnisart()));

    if (anzahlStimmenWaehlerBenannteDirektkandidaturSonstige > 0L && !sitzberechnungsalgorithmus.isBerechnetErgebnisFallsWaehlerBenannteWahlvorschlaegeSonstigeErfasst()) { // ELECT-9453
      helper.log(ErgebnisLogEintragArt.WARNUNG, "sitzberechnung.algorithmus.bemerkung.absolute.mehrheitswahl.fehler.waehler.benannte.direktkandidatur.sonstige", direktwahlgebiet.getName(),
          anzahlStimmenWaehlerBenannteDirektkandidaturSonstige);

      return;
    }

    if (sortierer.isEmpty()) {
      helper.log(ErgebnisLogEintragArt.WARNUNG, "sitzberechnung.algorithmus.protokoll.kein.zugelassener.direktkandidat.in.wahlkreis", direktwahlgebiet.getName());

      return;
    }

    if (sortierer.isAlleStimmenNull()) {
      helper.log(ErgebnisLogEintragArt.WARNUNG, "sitzberechnung.algorithmus.protokoll.anzahl.stimmen.ist.0.in.gebiet", direktwahlgebiet.getName());

      return;
    }

    markiereGewaehlte(helper, direktwahlgebiet, sortierer);
  }

  protected void markiereGewaehlte(SitzberechnungProGebietHelper helper, Wahldurchfuehrungsgebiet direktwahlgebiet, AbsoluteMehrheitswahlKandidaturenNachStimmenSortierer sortierer) {
    helper.log(ErgebnisLogEintragArt.INFO, "sitzberechnung.algorithmus.bemerkung.absolute.mehrheitswahl.anzahl.stimmen.absolute.mehrheit", direktwahlgebiet.getName(),
        sortierer.getAnzahlStimmenAbsoluteMehrheit());

    if (sortierer.hatWahlsiegerAbsoluteMehrheit()) {
      markiereGewaehlteErstplatzierterAbsoluteMehrheit(helper, direktwahlgebiet, sortierer);
    } else if (sortierer.sindTeilnehmerAnStichwahlEindeutig()) {
      markiereGewaehlteTeilnehmerInStichwahlEindeutig(helper, direktwahlgebiet, sortierer);
    } else if (sortierer.getErstplatzierte().size() > 2) {
      markiereGewaehlteErstplatzierteUnentschieden(helper, direktwahlgebiet, sortierer);
    } else {
      markiereGewaehlteZweitplatzierteUnentschieden(helper, direktwahlgebiet, sortierer);
    }
  }

  protected void markiereGewaehlteErstplatzierterAbsoluteMehrheit(SitzberechnungProGebietHelper helper, Wahldurchfuehrungsgebiet direktwahlgebiet,
      AbsoluteMehrheitswahlKandidaturenNachStimmenSortierer sortierer) {
    Direktkandidatur erstplatzierter = sortierer.getErstplatzierte().iterator().next();
    long anzahlStimmenErstplatzierter = NotNull.notNull(sortierer.getHoechsteStimmenzahl());

    helper.log(ErgebnisLogEintragArt.INFO, "sitzberechnung.algorithmus.mehrheitswahl.kandidat.gewinnt", direktwahlgebiet.getName(), helper.displayPerson(erstplatzierter),
        anzahlStimmenErstplatzierter);

    List<Direktkandidatur> kandidaturenIstGewaehlt = new ArrayList<>();
    kandidaturenIstGewaehlt.add(erstplatzierter);

    long rangIstGewaehlt = RANG_ERSTPLATZIERT;
    long rangIstNichtGewaehlt = RANG_ZWEITPLATZIERT;

    for (Direktkandidatur kandidaturIstGewaehlt : kandidaturenIstGewaehlt) {
      helper.createGewaehlt(kandidaturIstGewaehlt, GewaehltStatus.GEWAEHLT, rangIstGewaehlt);
    }

    for (Direktkandidatur kandidatur : sortierer.getAlle()) { // ELECT-9466
      if (!kandidaturenIstGewaehlt.contains(kandidatur)) {
        helper.createGewaehlt(kandidatur, GewaehltStatus.NICHT_GEWAEHLT, rangIstNichtGewaehlt);
      }
    }
  }

  protected void markiereGewaehlteTeilnehmerInStichwahlEindeutig(SitzberechnungProGebietHelper helper, Wahldurchfuehrungsgebiet direktwahlgebiet,
      AbsoluteMehrheitswahlKandidaturenNachStimmenSortierer sortierer) {
    List<Direktkandidatur> erstplatzierte = sortierer.getErstplatzierte();
    List<Direktkandidatur> zweitplatzierte = sortierer.getZweitplatzierte();

    helper.log(ErgebnisLogEintragArt.INFO, "sitzberechnung.algorithmus.bemerkung.absolute.mehrheitswahl.stichwahl.notwendig", direktwahlgebiet.getName());

    List<Direktkandidatur> kandidaturenIstInStichwahl = new ArrayList<>();
    kandidaturenIstInStichwahl.addAll(erstplatzierte);

    long rangIstInStichwahl = RANG_ERSTPLATZIERT;
    boolean rangIstInStichwahlErhoehen = false;

    if (kandidaturenIstInStichwahl.size() < 2) {
      rangIstInStichwahlErhoehen = true;

      kandidaturenIstInStichwahl.addAll(zweitplatzierte);
    }

    for (Direktkandidatur kandidaturIstInStichwahl : kandidaturenIstInStichwahl) {
      helper.createGewaehlt(kandidaturIstInStichwahl, GewaehltStatus.STICHWAHL, rangIstInStichwahl);

      if (rangIstInStichwahlErhoehen) {
        rangIstInStichwahl++;
      }
    }

    long rangIstNichtGewaehlt = kandidaturenIstInStichwahl.size() + 1L;

    for (Direktkandidatur kandidatur : sortierer.getAlle()) { // ELECT-9466
      if (!kandidaturenIstInStichwahl.contains(kandidatur)) {
        helper.createGewaehlt(kandidatur, GewaehltStatus.NICHT_GEWAEHLT, rangIstNichtGewaehlt);
      }
    }
  }

  protected void markiereGewaehlteErstplatzierteUnentschieden(SitzberechnungProGebietHelper helper, Wahldurchfuehrungsgebiet direktwahlgebiet,
      AbsoluteMehrheitswahlKandidaturenNachStimmenSortierer sortierer) {
    if (sitzberechnungsalgorithmus.isErstplatzierteUnentschiedenErfordertWiederholungswahl(sitzberechnungseingabe)) {
      // KEINE Gewählten - ALLE nehmen an der Wiederholungswahl teil...
      helper.log(ErgebnisLogEintragArt.WARNUNG, "sitzberechnung.algorithmus.bemerkung.absolute.mehrheitswahl.wiederholungswahl.notwendig", direktwahlgebiet.getName());
    } else { // ELECT-14200
      markiereGewaehlteErstplatzierteInStichwahlLosentscheid(helper, direktwahlgebiet, sortierer);
    }
  }

  private void markiereGewaehlteErstplatzierteInStichwahlLosentscheid(SitzberechnungProGebietHelper helper, Wahldurchfuehrungsgebiet direktwahlgebiet,
      AbsoluteMehrheitswahlKandidaturenNachStimmenSortierer sortierer) {
    List<Direktkandidatur> erstplatzierte = sortierer.getErstplatzierte();

    if (erstplatzierte.size() < 3) {
      throw new IllegalStateException("Unerwarteter Zustand beim AbsoluteMehrheitswahlSitzberechnungsalgorithmus: Losentscheid Erstplatzierte, aber weniger als drei Erstplatzierte: " + sortierer);
    }

    List<Direktkandidatur> kandidaturenIstInStichwahl = new ArrayList<>(); // Leer, weil ja gelost werden muss...

    List<Direktkandidatur> kandidaturenIstInStichwahlFallsGelost = new ArrayList<>();
    kandidaturenIstInStichwahlFallsGelost.addAll(erstplatzierte);

    long rangIstInStichwahl = RANG_ERSTPLATZIERT;
    long rangIstInStichwahlFallsGelost = RANG_ERSTPLATZIERT;

    markiereGewaehlteInStichwahlLosentscheid(helper, AbsoluteMehrheitswahlLosentscheidTyp.STIMMENGLEICHHEIT_DER_ERSTPLATZIERTEN, direktwahlgebiet, sortierer, rangIstInStichwahl,
        kandidaturenIstInStichwahl, rangIstInStichwahlFallsGelost, kandidaturenIstInStichwahlFallsGelost);
  }

  protected void markiereGewaehlteZweitplatzierteUnentschieden(SitzberechnungProGebietHelper helper, Wahldurchfuehrungsgebiet direktwahlgebiet,
      AbsoluteMehrheitswahlKandidaturenNachStimmenSortierer sortierer) {
    markiereGewaehlteZweitplatzierteInStichwahlLosentscheid(helper, direktwahlgebiet, sortierer);
  }

  private void markiereGewaehlteZweitplatzierteInStichwahlLosentscheid(SitzberechnungProGebietHelper helper, Wahldurchfuehrungsgebiet direktwahlgebiet,
      AbsoluteMehrheitswahlKandidaturenNachStimmenSortierer sortierer) {
    List<Direktkandidatur> erstplatzierte = sortierer.getErstplatzierte();
    List<Direktkandidatur> zweitplatzierte = sortierer.getZweitplatzierte();

    if (erstplatzierte.size() != 1) {
      throw new IllegalStateException("Unerwarteter Zustand beim AbsoluteMehrheitswahlSitzberechnungsalgorithmus: Losentscheid Zweitplatzierte, aber kein eindeutiger Erstplatzierter: " + sortierer);
    }

    if (zweitplatzierte.size() < 2) {
      throw new IllegalStateException("Unerwarteter Zustand beim AbsoluteMehrheitswahlSitzberechnungsalgorithmus: Losentscheid Zweitplatzierte, aber weniger als zwei Zweitplatzierte: " + sortierer);
    }

    List<Direktkandidatur> kandidaturenIstInStichwahl = new ArrayList<>();
    kandidaturenIstInStichwahl.addAll(erstplatzierte);

    List<Direktkandidatur> kandidaturenIstInStichwahlFallsGelost = new ArrayList<>();
    kandidaturenIstInStichwahlFallsGelost.addAll(zweitplatzierte);

    long rangIstInStichwahl = RANG_ERSTPLATZIERT;
    long rangIstInStichwahlFallsGelost = RANG_ZWEITPLATZIERT;

    markiereGewaehlteInStichwahlLosentscheid(helper, AbsoluteMehrheitswahlLosentscheidTyp.STIMMENGLEICHHEIT_DER_ZWEITPLATZIERTEN, direktwahlgebiet, sortierer, rangIstInStichwahl,
        kandidaturenIstInStichwahl, rangIstInStichwahlFallsGelost, kandidaturenIstInStichwahlFallsGelost);
  }

  private void markiereGewaehlteInStichwahlLosentscheid(SitzberechnungProGebietHelper helper, AbsoluteMehrheitswahlLosentscheidTyp losentscheidTyp, Wahldurchfuehrungsgebiet direktwahlgebiet,
      AbsoluteMehrheitswahlKandidaturenNachStimmenSortierer sortierer, long rangIstInStichwahl, List<Direktkandidatur> kandidaturenIstInStichwahl, long rangIstInStichwahlFallsGelost,
      List<Direktkandidatur> kandidaturenIstInStichwahlFallsGelost) {
    helper.log(ErgebnisLogEintragArt.WARNUNG, "sitzberechnung.algorithmus.bemerkung.absolute.mehrheitswahl.stichwahl.nach.losentscheid.notwendig", direktwahlgebiet.getName());

    for (Direktkandidatur kandidaturIstInStichwahl : kandidaturenIstInStichwahl) {
      helper.createGewaehlt(kandidaturIstInStichwahl, GewaehltStatus.STICHWAHL, rangIstInStichwahl);
    }

    Set<PersonendatenPK> kandidaturenPKsZurAuswahl = new HashSet<>();

    for (Direktkandidatur kandidaturIstInStichwahlFallsGelost : kandidaturenIstInStichwahlFallsGelost) {
      kandidaturenPKsZurAuswahl.add(kandidaturIstInStichwahlFallsGelost.getPk());
    }

    long rangIstNichtGewaehlt = rangIstInStichwahlFallsGelost + 1L;

    ErgebnisKonflikt konflikt = entscheideOderErzeugeKonflikt(helper, losentscheidTyp, direktwahlgebiet, kandidaturenPKsZurAuswahl);
    Set<PersonendatenPK> kandidaturenPKsIstInStichwahl = konflikt.getCustomData(AbsoluteMehrheitswahlErgebnisKonfliktCustomData.class).getGelosteKandidaturenPKs();

    for (Direktkandidatur kandidaturIstInStichwahlFallsGelost : kandidaturenIstInStichwahlFallsGelost) {
      if (konflikt.isGeloest()) {
        if (kandidaturenPKsIstInStichwahl.contains(kandidaturIstInStichwahlFallsGelost.getPk())) {
          helper.createGewaehlt(kandidaturIstInStichwahlFallsGelost, GewaehltStatus.STICHWAHL, rangIstInStichwahlFallsGelost);
        } else {
          helper.createGewaehlt(kandidaturIstInStichwahlFallsGelost, GewaehltStatus.NICHT_GEWAEHLT, rangIstNichtGewaehlt);
        }
      } else {
        helper.createGewaehlt(kandidaturIstInStichwahlFallsGelost, GewaehltStatus.LOS_OFFEN, rangIstInStichwahlFallsGelost);
      }
    }

    for (Direktkandidatur kandidatur : sortierer.getAlle()) { // ELECT-9466
      if (!kandidaturenIstInStichwahl.contains(kandidatur) && !kandidaturenIstInStichwahlFallsGelost.contains(kandidatur)) {
        helper.createGewaehlt(kandidatur, GewaehltStatus.NICHT_GEWAEHLT, rangIstNichtGewaehlt);
      }
    }
  }

  private ErgebnisKonflikt entscheideOderErzeugeKonflikt(SitzberechnungProGebietHelper helper, AbsoluteMehrheitswahlLosentscheidTyp losentscheidTyp, Wahldurchfuehrungsgebiet direktwahlgebiet, Set<PersonendatenPK> kandidaturenPKsZurAuswahl) {
    ErgebnisKonflikt konflikt = createErgebnisKonflikt(helper, losentscheidTyp, direktwahlgebiet, kandidaturenPKsZurAuswahl);
    ErgebnisKonfliktLoeser konfliktLoeser = new ErgebnisKonfliktLoeser();

    return helper.loeseKonflikt(konflikt, konfliktLoeser, false);
  }

  private ErgebnisKonflikt createErgebnisKonflikt(SitzberechnungProGebietHelper helper, AbsoluteMehrheitswahlLosentscheidTyp losentscheidTyp, Wahldurchfuehrungsgebiet direktwahlgebiet, Set<PersonendatenPK> kandidaturenPKsZurAuswahl) {
    AbsoluteMehrheitswahlErgebnisKonfliktCustomData absoluteMehrheitswahlErgebnisKonfliktCustomData = new AbsoluteMehrheitswahlErgebnisKonfliktCustomData();
    absoluteMehrheitswahlErgebnisKonfliktCustomData.setWahlgebiet(direktwahlgebiet);
    absoluteMehrheitswahlErgebnisKonfliktCustomData.setWahlvorschlaggebiet(direktwahlgebiet);
    absoluteMehrheitswahlErgebnisKonfliktCustomData.setAnzahlZuLosen(losentscheidTyp.getAnzahlZuLosen());
    absoluteMehrheitswahlErgebnisKonfliktCustomData.setBeteiligteKandidaturenPKs(kandidaturenPKsZurAuswahl);

    return helper.createKonflikt(absoluteMehrheitswahlErgebnisKonfliktCustomData, ErgebnisKonfliktArt.LOSENTSCHEID_DIREKTKANDIDAT, helper.getMessages().lokalisiere(losentscheidTyp));
  }
}
