
package de.ivu.elect.presentation.ergebnis.dokumente.gesamtergebnis;

import static de.ivu.elect.was.exchange.internet.velocity.DetailTableBerechner.summiere;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import de.ivu.elect.business.ergebnis.control.ErgebnisDaten;
import de.ivu.elect.business.ergebnis.entity.Sitze;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.gruppe.entity.SystemGruppe;
import de.ivu.elect.business.internet.control.GebietUndStimmabgabeart;
import de.ivu.elect.business.stimmzettel.entity.EntityAufStimmzettelDaten;
import de.ivu.elect.business.wahlvorschlag.entity.Direktkandidat;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.dokumente.CoreElectContext;
import de.ivu.elect.dokumente.kandidatur.KandidaturCoreDaten;
import de.ivu.elect.was.exchange.internet.velocity.DetailTableBerechner;
import de.ivu.elect.was.exchange.internet.velocity.PolitischeGruppeProzentGewinnVerlust;
import de.ivu.elect.was.exchange.internet.velocity.SystemGruppeProzentGewinnVerlust;

public class GruppeErgebnisBuilder implements Serializable {

  private static final long serialVersionUID = 1L;

  private final CoreElectContext coreElectContext;

  private final EntityAufStimmzettelDaten entityAufStimmzettelDaten;

  private final KandidaturCoreDaten kandidaturCoreDaten;

  private final List<PolitischeGruppe> politischeGruppen;

  private final Gruppe gueltige;

  private final Gruppe ungueltige;

  private final Map<PolitischeGruppe, Long> anzahlSitzeProGruppe;

  private final Map<PolitischeGruppe, Long> anzahlSitzeAnspruchProGruppe;

  private final Set<PolitischeGruppe> gruppenMitDirektstimmen;

  private final Set<PolitischeGruppe> gruppenMitListenstimmen;

  private final DetailTableBerechner direktstimmenDetailTableBerechner;

  private final DetailTableBerechner listenstimmenDetailTableBerechner;

  private final DetailTableBerechner gesamtstimmenDetailTableBerechner;

  private final Map<PolitischeGruppe, Liste> listePerPolitischeGruppe;

  private final Map<PolitischeGruppe, Direktkandidat> direktkandidatPerPolitischeGruppe;

  public GruppeErgebnisBuilder(CoreElectContext coreElectContext, ErgebnisDaten ergebnisDaten, GebietUndStimmabgabeart gebietUndStimmabgabeart, KandidaturCoreDaten kandidaturCoreDaten) {
    super();

    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet();

    this.coreElectContext = coreElectContext;

    this.entityAufStimmzettelDaten = ergebnisDaten.getEntityAufStimmzettelDaten();

    this.kandidaturCoreDaten = kandidaturCoreDaten;

    this.politischeGruppen = ergebnisDaten.getPolitischeGruppenZugelassenSortiertNachPositionAufGebiet(wahldurchfuehrungsgebiet);

    this.gueltige = ergebnisDaten.getGruppeGueltige();
    this.ungueltige = ergebnisDaten.getGruppeUnGueltige();

    this.anzahlSitzeProGruppe = bestimmeAnzahlSitzeProGruppe(politischeGruppen, wahldurchfuehrungsgebiet, ergebnisDaten);
    this.anzahlSitzeAnspruchProGruppe = bestimmeAnzahlSitzeAnspruchProGruppe(politischeGruppen, wahldurchfuehrungsgebiet, ergebnisDaten);

    this.gruppenMitDirektstimmen = bestimmeGruppenMitDirektstimmen(politischeGruppen, wahldurchfuehrungsgebiet, ergebnisDaten);
    this.gruppenMitListenstimmen = bestimmeGruppenMitListenstimmen(politischeGruppen, wahldurchfuehrungsgebiet, ergebnisDaten);

    this.listePerPolitischeGruppe = bestimmeListePerPolitischeGruppe(ergebnisDaten, wahldurchfuehrungsgebiet);
    this.direktkandidatPerPolitischeGruppe = bestimmeDirektkandidatPerPolitischeGruppe(ergebnisDaten, wahldurchfuehrungsgebiet);

    this.direktstimmenDetailTableBerechner = createDirektstimmenDetailTableBerechner(ergebnisDaten, gebietUndStimmabgabeart);
    this.listenstimmenDetailTableBerechner = createListenstimmenDetailTableBerechner(ergebnisDaten, gebietUndStimmabgabeart);
    this.gesamtstimmenDetailTableBerechner = createGesamtstimmenDetailTableBerechner(ergebnisDaten, gebietUndStimmabgabeart);
  }

  public static Set<PolitischeGruppe> bestimmeGruppenMitListenstimmen(List<PolitischeGruppe> politischeGruppen, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, ErgebnisDaten ergebnisDaten) {
    Set<PolitischeGruppe> result = new HashSet<>();

    for (PolitischeGruppe politischeGruppe : politischeGruppen) {
      if (ergebnisDaten.getGruppeImGebiet(politischeGruppe, wahldurchfuehrungsgebiet).hatListeInGebietshierarchie(ergebnisDaten.getAlleWahlvorschlaegeProGruppe())) {
        result.add(politischeGruppe);
      }
    }

    return result;
  }

  /**
   * @return die politischen Gruppen, die auf dem wahldurchfuehrungsgebiet einen zugelassenen Wahlvorschlag für Direktstimmen haben (also einen zugelassenen Direktkandidaten)
   */
  public static Set<PolitischeGruppe> bestimmeGruppenMitDirektstimmen(List<PolitischeGruppe> politischeGruppen, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, ErgebnisDaten ergebnisDaten) {
    Set<PolitischeGruppe> result = new HashSet<>();

    for (PolitischeGruppe politischeGruppe : politischeGruppen) {
      if (ergebnisDaten.getGruppeImGebiet(politischeGruppe, wahldurchfuehrungsgebiet).hatZugelassenenDirektkandidatInGebietshierarchie(ergebnisDaten.getAlleWahlvorschlaegeProGruppe())) {
        result.add(politischeGruppe);
      }
    }

    return result;
  }

  private DetailTableBerechner createGesamtstimmenDetailTableBerechner(ErgebnisDaten ergebnisDaten, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    List<PolitischeGruppe> politischeGruppen = ergebnisDaten.getPolitischeGruppenZugelassenSortiertNachPositionAufGebiet(gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet());
    Long direktstimmenUngueltigeVorperiode = ergebnisDaten.getErststimmenVorperiodeUngueltige(gebietUndStimmabgabeart);
    Long listenstimmenUngueltigeVorperiode = ergebnisDaten.getZweitstimmenVorperiodeUngueltige(gebietUndStimmabgabeart);
    Map<PolitischeGruppe, Long> gesamtstimmenProGruppeAktuelleWahl = ergebnisDaten.getErgebnisDatenGesamtstimmen().getGesamtStimmenAktuell(gebietUndStimmabgabeart);
    Map<PolitischeGruppe, Long> gesamtstimmenVorperiodeProGruppe = ergebnisDaten.getErgebnisDatenGesamtstimmen().getGesamtStimmenVorperiode(gebietUndStimmabgabeart);
    Long gesamtstimmenGueltigeVorperiode = ergebnisDaten.getErgebnisDatenGesamtstimmen().getGesamtStimmenGueltigeVorperiode(gebietUndStimmabgabeart);
    Long anzahlGueltigerDirektstimmen = ergebnisDaten.getErststimmenAktuellGueltige(gebietUndStimmabgabeart);
    Long anzahlUngueltigerDirektstimmen = ergebnisDaten.getErststimmenAktuellUngueltige(gebietUndStimmabgabeart);
    Long anzahlGueltigerListenstimmen = ergebnisDaten.getZweitstimmenAktuellGueltige(gebietUndStimmabgabeart);
    Long anzahlUngueltigerListenstimmen = ergebnisDaten.getZweitstimmenAktuellUngueltige(gebietUndStimmabgabeart);

    return new DetailTableBerechner(politischeGruppen, gesamtstimmenProGruppeAktuelleWahl, gesamtstimmenVorperiodeProGruppe, gesamtstimmenGueltigeVorperiode,
        summiere(anzahlGueltigerDirektstimmen, anzahlGueltigerListenstimmen), summiere(anzahlUngueltigerDirektstimmen, anzahlUngueltigerListenstimmen), gesamtstimmenGueltigeVorperiode,
        summiere(direktstimmenUngueltigeVorperiode, listenstimmenUngueltigeVorperiode));
  }

  private DetailTableBerechner createListenstimmenDetailTableBerechner(ErgebnisDaten ergebnisDaten, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    List<PolitischeGruppe> politischeGruppen = ergebnisDaten.getPolitischeGruppenZugelassenSortiertNachPositionAufGebiet(gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet());
    Map<PolitischeGruppe, Long> listenstimmenProGruppeAktuelleWahl = ergebnisDaten.getZweitstimmenAktuell(gebietUndStimmabgabeart);
    Map<PolitischeGruppe, Long> listenstimmenVorperiodeProGruppe = ergebnisDaten.getZweitstimmenVorperiode(gebietUndStimmabgabeart);
    Long listenstimmenGueltigeVorperiode = ergebnisDaten.getZweitstimmenVorperiodeGueltige(gebietUndStimmabgabeart);
    Long listenstimmenUngueltigeVorperiode = ergebnisDaten.getZweitstimmenVorperiodeUngueltige(gebietUndStimmabgabeart);
    Long anzahlGueltigerListenstimmen = ergebnisDaten.getZweitstimmenAktuellGueltige(gebietUndStimmabgabeart);
    Long anzahlUngueltigerListenstimmen = ergebnisDaten.getZweitstimmenAktuellUngueltige(gebietUndStimmabgabeart);

    return new DetailTableBerechner(politischeGruppen, listenstimmenProGruppeAktuelleWahl, listenstimmenVorperiodeProGruppe, listenstimmenGueltigeVorperiode, anzahlGueltigerListenstimmen,
        anzahlUngueltigerListenstimmen, listenstimmenGueltigeVorperiode, listenstimmenUngueltigeVorperiode);
  }

  private DetailTableBerechner createDirektstimmenDetailTableBerechner(ErgebnisDaten ergebnisDaten, GebietUndStimmabgabeart gebietUndStimmabgabeart) {
    List<PolitischeGruppe> politischeGruppen = ergebnisDaten.getPolitischeGruppenZugelassenSortiertNachPositionAufGebiet(gebietUndStimmabgabeart.getWahldurchfuehrungsgebiet());
    Map<PolitischeGruppe, Long> direktstimmenProGruppeAktuelleWahl = ergebnisDaten.getErststimmenAktuell(gebietUndStimmabgabeart);
    Map<PolitischeGruppe, Long> direktstimmenVorperiodeProGruppe = ergebnisDaten.getErststimmenVorperiode(gebietUndStimmabgabeart);
    Long direktstimmenGueltigeVorperiode = ergebnisDaten.getErststimmenVorperiodeGueltige(gebietUndStimmabgabeart);
    Long direktstimmenUngueltigeVorperiode = ergebnisDaten.getErststimmenVorperiodeUngueltige(gebietUndStimmabgabeart);
    Long anzahlGueltigerDirektstimmen = ergebnisDaten.getErststimmenAktuellGueltige(gebietUndStimmabgabeart);
    Long anzahlUngueltigerDirektstimmen = ergebnisDaten.getErststimmenAktuellUngueltige(gebietUndStimmabgabeart);

    return new DetailTableBerechner(politischeGruppen, direktstimmenProGruppeAktuelleWahl, direktstimmenVorperiodeProGruppe, direktstimmenGueltigeVorperiode, anzahlGueltigerDirektstimmen,
        anzahlUngueltigerDirektstimmen, direktstimmenGueltigeVorperiode, direktstimmenUngueltigeVorperiode);
  }

  private Map<PolitischeGruppe, Long> bestimmeAnzahlSitzeAnspruchProGruppe(List<PolitischeGruppe> politischeGruppen, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, ErgebnisDaten ergebnisDaten) {
    Map<PolitischeGruppe, Long> result = new HashMap<>();

    for (PolitischeGruppe politischeGruppe : politischeGruppen) {
      long anzahlSitze = 0L;

      for (Sitze sitze : ergebnisDaten.getSitzeFuerPolitischeGruppe(politischeGruppe)) {
        Gebiet gebietDerSitze = sitze.getGebiet();
        if (gebietDerSitze.equals(wahldurchfuehrungsgebiet) || gebietDerSitze.isUntergebietVon(wahldurchfuehrungsgebiet)) {
          anzahlSitze += sitze.getAnzahlAnspruch();
        }
      }

      result.put(politischeGruppe, anzahlSitze);
    }

    return result;
  }

  private Map<PolitischeGruppe, Long> bestimmeAnzahlSitzeProGruppe(List<PolitischeGruppe> politischeGruppen, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, ErgebnisDaten ergebnisDaten) {
    Map<PolitischeGruppe, Long> result = new HashMap<>();

    for (PolitischeGruppe politischeGruppe : politischeGruppen) {
      long anzahlSitze = 0L;

      for (Sitze sitze : ergebnisDaten.getSitzeFuerPolitischeGruppe(politischeGruppe)) {
        Gebiet gebietDerSitze = sitze.getGebiet();
        if (gebietDerSitze.equals(wahldurchfuehrungsgebiet) || gebietDerSitze.isUntergebietVon(wahldurchfuehrungsgebiet)) {
          anzahlSitze += sitze.getAnzahl();
        }
      }

      result.put(politischeGruppe, anzahlSitze);
    }

    return result;
  }

  private Map<PolitischeGruppe, Liste> bestimmeListePerPolitischeGruppe(ErgebnisDaten ergebnisDaten, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    Map<PolitischeGruppe, Liste> result = new HashMap<>();

    for (Liste liste : ergebnisDaten.getListen()) {
      if (wahldurchfuehrungsgebiet.equals(liste.getGebiet()) || wahldurchfuehrungsgebiet.isUntergebietVon(liste.getGebiet())) {
        result.put(liste.getGruppe(), liste);
      }
    }

    return result;
  }

  private Map<PolitischeGruppe, Direktkandidat> bestimmeDirektkandidatPerPolitischeGruppe(ErgebnisDaten ergebnisDaten, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    Map<PolitischeGruppe, Direktkandidat> result = new HashMap<>();

    for (Direktkandidat direktkandidat : ergebnisDaten.getDirektkandidaten()) {
      if (wahldurchfuehrungsgebiet.equals(direktkandidat.getGebiet()) || wahldurchfuehrungsgebiet.isUntergebietVon(direktkandidat.getGebiet())) {
        result.put(direktkandidat.getGruppe(), direktkandidat);
      }
    }

    return result;
  }

  private transient List<PolitischeGruppenErgebnisAufGebiet> politischeGruppenErgebisse;

  public List<PolitischeGruppenErgebnisAufGebiet> erstellePolitischeGruppenErgebisse() {
    initErgebnisse();

    return politischeGruppenErgebisse;
  }

  private transient GruppenErgebnisAufGebiet guelteGruppenErgebnisse;

  public GruppenErgebnisAufGebiet erstelleGueltigeErgebnisse() {
    initErgebnisse();

    return guelteGruppenErgebnisse;
  }

  private transient GruppenErgebnisAufGebiet unguelteGruppenErgebnisse;

  public GruppenErgebnisAufGebiet erstelleUngueltigeErgebnisse() {
    initErgebnisse();

    return unguelteGruppenErgebnisse;
  }

  private void initErgebnisse() {
    if (politischeGruppenErgebisse != null) {
      return;
    }

    politischeGruppenErgebisse = new ArrayList<>();

    Long laufendeNummer = 0L;

    for (PolitischeGruppe politischeGruppe : politischeGruppen) {
      boolean hatDirektstimmen = gruppenMitDirektstimmen.contains(politischeGruppe);
      boolean hatListenstimmen = gruppenMitListenstimmen.contains(politischeGruppe);

      PolitischeGruppeProzentGewinnVerlust direktstimmen = null;

      if (hatDirektstimmen) {
        direktstimmen = direktstimmenDetailTableBerechner.getStimmen().get(politischeGruppe);
      }
      PolitischeGruppeProzentGewinnVerlust listenstimmen = null;
      if (hatListenstimmen) {
        listenstimmen = listenstimmenDetailTableBerechner.getStimmen().get(politischeGruppe);
      }
      PolitischeGruppeProzentGewinnVerlust gesamtstimmen = null;
      if (hatDirektstimmen || hatListenstimmen) {
        gesamtstimmen = gesamtstimmenDetailTableBerechner.getStimmen().get(politischeGruppe);
      }

      laufendeNummer++;

      politischeGruppenErgebisse.add(new PolitischeGruppenErgebnisAufGebiet(coreElectContext, entityAufStimmzettelDaten, politischeGruppe, listePerPolitischeGruppe.get(politischeGruppe),
          direktkandidatPerPolitischeGruppe.get(politischeGruppe), kandidaturCoreDaten, listenstimmen, direktstimmen, gesamtstimmen, laufendeNummer, anzahlSitzeProGruppe.get(politischeGruppe),
          anzahlSitzeAnspruchProGruppe.get(politischeGruppe)));
    }

    Map<SystemGruppe, SystemGruppeProzentGewinnVerlust> direktstimmenProGueltigUndUngueltig = direktstimmenDetailTableBerechner.getStimmenProGueltigUndUngueltig();
    Map<SystemGruppe, SystemGruppeProzentGewinnVerlust> listenstimmenProGueltigUndUngueltig = listenstimmenDetailTableBerechner.getStimmenProGueltigUndUngueltig();
    Map<SystemGruppe, SystemGruppeProzentGewinnVerlust> gesamtstimmenProGueltigUndUngueltig = gesamtstimmenDetailTableBerechner.getStimmenProGueltigUndUngueltig();

    guelteGruppenErgebnisse = new GruppenErgebnisAufGebiet(coreElectContext, gueltige, direktstimmenProGueltigUndUngueltig.get(SystemGruppe.GUELTIGE),
        listenstimmenProGueltigUndUngueltig.get(SystemGruppe.GUELTIGE), gesamtstimmenProGueltigUndUngueltig.get(SystemGruppe.GUELTIGE));
    unguelteGruppenErgebnisse = new GruppenErgebnisAufGebiet(coreElectContext, ungueltige, direktstimmenProGueltigUndUngueltig.get(SystemGruppe.UNGUELTIGE),
        listenstimmenProGueltigUndUngueltig.get(SystemGruppe.UNGUELTIGE), gesamtstimmenProGueltigUndUngueltig.get(SystemGruppe.UNGUELTIGE));
  }
}
