
package de.ivu.elect.dokumente.ergebnis;

import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.elect.business.ergebnis.control.ErgebnisDaten;
import de.ivu.elect.business.gebietsbaum.boundary.WahlverwaltungGebietInformation;
import de.ivu.elect.business.wahl.entity.WahlartErgebnisart;
import de.ivu.elect.dokumente.BaseElectContext;
import de.ivu.elect.dokumente.ergebnis.zusammenstellung.ErgebnisTabellarischAufteilungBlatt;
import de.ivu.elect.dokumente.ergebnis.zusammenstellung.ErgebnisTabellarischAufteilungBlatt.Nummerierung;
import de.ivu.elect.dokumente.ergebnis.zusammenstellung.ErgebnisTabellarischTabelle;
import de.ivu.elect.dokumente.ergebnis.zusammenstellung.ErgebnisTabellarischZeile;

public abstract class AbstractErgebnisTabellarischDocumentCreator<D extends AbstractErgebnisTabellarischDocument<Z>, Z extends AbstractErgebnisTabellarischDocumentZeile> {

  protected final Logger log;

  protected AbstractErgebnisTabellarischDocumentCreator() {
    super();

    this.log = LoggerFactory.getLogger(getClass());
  }

  // ELECT-15337
  public abstract D mapDocument(BaseElectContext baseElectContext, ErgebnisDaten ergebnisDaten, WahlverwaltungGebietInformation stimmzettelgebietInformation,
      WahlverwaltungGebietInformation tabelleDatenGebietInformation, String erfassungsgebietGebietsreferenzFormat, ErgebnisTabellarischAufteilungBlatt blatt);

  protected D mapDocument(BaseElectContext baseElectContext, ErgebnisDaten ergebnisDaten, WahlverwaltungGebietInformation stimmzettelgebietInformation,
      WahlverwaltungGebietInformation tabelleDatenGebietInformation, String erfassungsgebietGebietsreferenzFormat, ErgebnisTabellarischAufteilungBlatt blatt,
      AbstractErgebnisTabellarischDocumentZeileCreator<Z> documentZeileCreator) {
    WahlartErgebnisart ergebnisart = ergebnisDaten.getErgebnis().getErgebnisart();

    ErgebnisTabellarischTabelle tabelle = blatt.getTabelle();
    ErgebnisDocumentDatenArt tabelleDatenArt = tabelle.getTabelleDatenArt();

    D result = createDocument(baseElectContext, ergebnisart, tabelleDatenGebietInformation, tabelleDatenArt);

    List<ErgebnisTabellarischZeile> zeilen = tabelle.getZeilen();
    List<Z> resultZeilen = new ArrayList<>();

    for (int i = 0; i < zeilen.size(); i++) {
      ErgebnisTabellarischZeile zeile = zeilen.get(i);

      Z resultZeile = mapDocumentZeile(baseElectContext, ergebnisDaten, stimmzettelgebietInformation, tabelleDatenGebietInformation, erfassungsgebietGebietsreferenzFormat, result,
          documentZeileCreator, zeile);
      resultZeile.nummer = (long) (i + 1);

      resultZeilen.add(resultZeile);
    }

    result.stimmenart = tabelleDatenArt.formatStimmabgabeart(baseElectContext.getMessages());

    List<Z> resultZeilenBerechenbar = AbstractErgebnisTabellarischDocumentZeile.filterZeilenBerechenbar(resultZeilen); // ELECT-13234
    boolean existsAnyErgebniseingabestatusInDatenGebiet = !resultZeilenBerechenbar.isEmpty();

    // ELECT-9400
    result.datenGebiet.isErgebniseingabestatusKomplettEingabeBeendet = existsAnyErgebniseingabestatusInDatenGebiet;
    result.datenGebiet.isErgebniseingabestatusKomplettGesperrt = existsAnyErgebniseingabestatusInDatenGebiet;
    result.datenGebiet.isErgebniseingabestatusKomplettAbgeschlossen = existsAnyErgebniseingabestatusInDatenGebiet;
    result.datenGebiet.isErgebniseingabestatusKomplettFreigegeben = existsAnyErgebniseingabestatusInDatenGebiet;
    result.datenGebiet.isErgebniseingabestatusKomplettAusbleibend = existsAnyErgebniseingabestatusInDatenGebiet;

    for (Z resultZeile : resultZeilenBerechenbar) {
      result.datenGebiet.isErgebniseingabestatusKomplettEingabeBeendet &= resultZeile.wahlbezirk.isErgebniseingabestatusEingabeBeendet;
      result.datenGebiet.isErgebniseingabestatusKomplettGesperrt &= resultZeile.wahlbezirk.isErgebniseingabestatusGesperrt;
      result.datenGebiet.isErgebniseingabestatusKomplettAbgeschlossen &= resultZeile.wahlbezirk.isErgebniseingabestatusAbgeschlossen;
      result.datenGebiet.isErgebniseingabestatusKomplettFreigegeben &= resultZeile.wahlbezirk.isErgebniseingabestatusFreigegeben;
      result.datenGebiet.isErgebniseingabestatusKomplettAusbleibend &= resultZeile.wahlbezirk.isErgebniseingabestatusAusbleibend;
    }

    /*
     * Nummerierung VOR den Zeilen, Header, Summen setzen, um ggf. schon zu wissen, ob es die erste/letzte Seite ist...
     */

    result.seiteMitUnterschrift = false;

    Nummerierung nummerierung = blatt.getNummerierung();

    result.seitenNummer = "" + nummerierung.getNummerHorizontal();
    result.seitenNummerMaximal = "" + nummerierung.getNummerHorizontalMax();
    result.seitenNummerAusgeben = nummerierung.getNummerHorizontalMax() > nummerierung.getNummerHorizontalInitial();

    boolean existsFortsetzungVertikal = nummerierung.isWeitereNummerVertikalVorhanden();
    result.seiteMitFortsetzung = existsFortsetzungVertikal; // == "Seite mit weiteren Zeilen auf weiterem Teil...

    result.teilNummer = "" + nummerierung.getNummerVertikal();
    result.teilNummerFortsetzung = existsFortsetzungVertikal ? "" + (nummerierung.getNummerVertikal() + 1) : "";
    result.teilNummerMaximal = "" + nummerierung.getNummerVertikalMax();
    result.teilNummerAusgeben = nummerierung.getNummerVertikalMax() > nummerierung.getNummerVertikalInitial();

    result.addZeilen(resultZeilen);

    if (resultZeilen.isEmpty()) {
      log.warn("Keine Daten [documentDatenGebiet = {}, tabelleDatenArt = {}]", tabelleDatenGebietInformation, tabelleDatenArt);
    } else {
      Z resultZeileTemplate = resultZeilen.get(0);

      fillInDocumentHeader(baseElectContext, result, tabelleDatenArt, resultZeileTemplate);

      List<Z> resultZeilenUebertragsummen = new ArrayList<>();

      blatt.getZeileUebertragsummen() //
          .map(zeile -> mapDocumentZeile(baseElectContext, ergebnisDaten, stimmzettelgebietInformation, tabelleDatenGebietInformation, erfassungsgebietGebietsreferenzFormat, result,
              documentZeileCreator, zeile)) //
          .ifPresent(resultZeilenUebertragsummen::add);

      if (resultZeilenUebertragsummen.isEmpty()) {
        resultZeilenUebertragsummen.addAll(resultZeilen);
      }

      fillInDocumentSummen(baseElectContext, result, tabelleDatenArt, resultZeilenUebertragsummen);
    }

    if (resultZeilen.stream().anyMatch(resultZeile -> resultZeile.datenGebiet.isPseudoGemeindeGesamt)) {
      result.datenGebiet.name = tabelleDatenGebietInformation.getName();
      result.datenGebiet.nummer = tabelleDatenGebietInformation.getNummer();
      result.datenGebiet.nummerFormatiert = result.datenGebiet.nummer;

      result.gemeindeverband.name = result.datenGebiet.name;
      result.gemeindeverband.nummer = result.datenGebiet.nummer;
      result.gemeindeverband.nummerFormatiert = result.datenGebiet.nummerFormatiert;

      result.gemeinde.name = result.datenGebiet.name;
      result.gemeinde.nummer = result.datenGebiet.nummer;
      result.gemeinde.nummerFormatiert = result.datenGebiet.nummerFormatiert;

      result.ortsbezirk.name = result.datenGebiet.name;
      result.ortsbezirk.nummer = result.datenGebiet.nummer;
      result.ortsbezirk.nummerFormatiert = result.datenGebiet.nummerFormatiert;

      result.stadtbezirk.name = result.datenGebiet.name;
      result.stadtbezirk.nummer = result.datenGebiet.nummer;
      result.stadtbezirk.nummerFormatiert = result.datenGebiet.nummerFormatiert;
    }

    return result;
  }

  protected Z mapDocumentZeile(BaseElectContext baseElectContext, ErgebnisDaten ergebnisDaten, WahlverwaltungGebietInformation stimmzettelgebietInformation,
      WahlverwaltungGebietInformation tabelleDatenGebietInformation, String erfassungsgebietGebietsreferenzFormat, D document, AbstractErgebnisTabellarischDocumentZeileCreator<Z> documentZeileCreator,
      ErgebnisTabellarischZeile zeile) {
    return documentZeileCreator.mapDocumentZeile(baseElectContext, ergebnisDaten, stimmzettelgebietInformation, tabelleDatenGebietInformation, erfassungsgebietGebietsreferenzFormat, zeile);
  }

  protected abstract void fillInDocumentHeader(BaseElectContext baseElectContext, D result, ErgebnisDocumentDatenArt tabelleDatenArt, Z template);

  protected abstract void fillInDocumentSummen(BaseElectContext baseElectContext, D result, ErgebnisDocumentDatenArt tabelleDatenArt, List<Z> zeilen);

  /*
   *
   *
   *
   *
   *
   * ELECT-15337, LEGACY-Methoden -> NICHT mehr erweitern!!!
   *
   *
   *
   *
   *
   */

  public D createSummenDocumentFuerDatenArtUrnenwahl(BaseElectContext baseElectContext, WahlartErgebnisart ergebnisart, WahlverwaltungGebietInformation summenGebiet,
      WahlverwaltungGebietInformation datenGebiet, List<Z> zeilenInDatenGebiet) {
    return createSummenDocumentFuerDatenArt(baseElectContext, ergebnisart, summenGebiet, datenGebiet, ErgebnisDocumentDatenArt.URNENWAHL, zeilenInDatenGebiet);
  }

  public D createSummenDocumentFuerDatenArtBriefwahl(BaseElectContext baseElectContext, WahlartErgebnisart ergebnisart, WahlverwaltungGebietInformation summenGebiet,
      WahlverwaltungGebietInformation datenGebiet, List<Z> zeilenInDatenGebiet) {
    return createSummenDocumentFuerDatenArt(baseElectContext, ergebnisart, summenGebiet, datenGebiet, ErgebnisDocumentDatenArt.BRIEFWAHL, zeilenInDatenGebiet);
  }

  public D createSummenDocumentFuerDatenArtGesamt(BaseElectContext baseElectContext, WahlartErgebnisart ergebnisart, WahlverwaltungGebietInformation summenGebiet,
      WahlverwaltungGebietInformation datenGebiet, List<Z> zeilenInDatenGebiet) {
    return createSummenDocumentFuerDatenArt(baseElectContext, ergebnisart, summenGebiet, datenGebiet, ErgebnisDocumentDatenArt.GESAMT, zeilenInDatenGebiet);
  }

  private D createSummenDocumentFuerDatenArt(BaseElectContext baseElectContext, WahlartErgebnisart ergebnisart, WahlverwaltungGebietInformation summenGebiet,
      WahlverwaltungGebietInformation datenGebiet, ErgebnisDocumentDatenArt datenArt, List<Z> zeilenInDatenGebiet) {
    D result = createDocumentFuerDatenArt(baseElectContext, ergebnisart, datenGebiet, datenArt, zeilenInDatenGebiet);
    fillInSummenDocument(summenGebiet, result);

    return result;
  }

  protected void fillInSummenDocument(WahlverwaltungGebietInformation summenGebiet, D document) {
    String name = summenGebiet.getName();
    String nummer = summenGebiet.getNummer();

    document.datenGebiet.name = name;
    document.datenGebiet.nummer = nummer;
    document.datenGebiet.nummerFormatiert = nummer;
    document.datenGebiet.isPseudoGemeindeGesamt = true;

    document.gemeindeverband.name = document.datenGebiet.name;
    document.gemeindeverband.nummer = document.datenGebiet.nummer;
    document.gemeindeverband.nummerFormatiert = document.datenGebiet.nummerFormatiert;

    document.gemeinde.name = document.datenGebiet.name;
    document.gemeinde.nummer = document.datenGebiet.nummer;
    document.gemeinde.nummerFormatiert = document.datenGebiet.nummerFormatiert;

    document.ortsbezirk.name = document.datenGebiet.name;
    document.ortsbezirk.nummer = document.datenGebiet.nummer;
    document.ortsbezirk.nummerFormatiert = document.datenGebiet.nummerFormatiert;

    document.stadtbezirk.name = document.datenGebiet.name;
    document.stadtbezirk.nummer = document.datenGebiet.nummer;
    document.stadtbezirk.nummerFormatiert = document.datenGebiet.nummerFormatiert;
  }

  public D createDocumentFuerDatenArtUrnenwahl(BaseElectContext baseElectContext, WahlartErgebnisart ergebnisart, WahlverwaltungGebietInformation datenGebiet, List<Z> zeilenInDatenGebiet) {
    return createDocumentFuerDatenArt(baseElectContext, ergebnisart, datenGebiet, ErgebnisDocumentDatenArt.URNENWAHL, zeilenInDatenGebiet);
  }

  public D createDocumentFuerDatenArtBriefwahl(BaseElectContext baseElectContext, WahlartErgebnisart ergebnisart, WahlverwaltungGebietInformation datenGebiet, List<Z> zeilenInDatenGebiet) {
    return createDocumentFuerDatenArt(baseElectContext, ergebnisart, datenGebiet, ErgebnisDocumentDatenArt.BRIEFWAHL, zeilenInDatenGebiet);
  }

  public D createDocumentFuerDatenArtGesamt(BaseElectContext baseElectContext, WahlartErgebnisart ergebnisart, WahlverwaltungGebietInformation datenGebiet, List<Z> zeilenInDatenGebiet) {
    return createDocumentFuerDatenArt(baseElectContext, ergebnisart, datenGebiet, ErgebnisDocumentDatenArt.GESAMT, zeilenInDatenGebiet);
  }

  private D createDocumentFuerDatenArt(BaseElectContext baseElectContext, WahlartErgebnisart ergebnisart, WahlverwaltungGebietInformation datenGebiet, ErgebnisDocumentDatenArt datenArt,
      List<Z> zeilenInDatenGebiet) {
    return createDocument(baseElectContext, ergebnisart, datenGebiet, datenArt, collectZeilenFuerDatenArt(datenArt, zeilenInDatenGebiet));
  }

  protected D createDocument(BaseElectContext baseElectContext, WahlartErgebnisart ergebnisart, WahlverwaltungGebietInformation datenGebiet, ErgebnisDocumentDatenArt datenArt,
      List<Z> zeilenInDatenGebietFuerDatenArt) {
    for (Z zeile : zeilenInDatenGebietFuerDatenArt) {
      zeile.nummer = (long) (1 + zeilenInDatenGebietFuerDatenArt.indexOf(zeile));
    }

    D result = createDocument(baseElectContext, ergebnisart, datenGebiet, datenArt);
    result.stimmenart = datenArt.formatStimmabgabeart(baseElectContext.getMessages());

    List<Z> zeilenInDatenGebietFuerDatenArtBerechenbar = AbstractErgebnisTabellarischDocumentZeile.filterZeilenBerechenbar(zeilenInDatenGebietFuerDatenArt); // ELECT-13234
    boolean existsAnyErgebniseingabestatusInDatenGebiet = !zeilenInDatenGebietFuerDatenArtBerechenbar.isEmpty();

    // ELECT-9400
    result.datenGebiet.isErgebniseingabestatusKomplettEingabeBeendet = existsAnyErgebniseingabestatusInDatenGebiet;
    result.datenGebiet.isErgebniseingabestatusKomplettGesperrt = existsAnyErgebniseingabestatusInDatenGebiet;
    result.datenGebiet.isErgebniseingabestatusKomplettAbgeschlossen = existsAnyErgebniseingabestatusInDatenGebiet;
    result.datenGebiet.isErgebniseingabestatusKomplettFreigegeben = existsAnyErgebniseingabestatusInDatenGebiet;
    result.datenGebiet.isErgebniseingabestatusKomplettAusbleibend = existsAnyErgebniseingabestatusInDatenGebiet;

    for (Z zeile : zeilenInDatenGebietFuerDatenArtBerechenbar) {
      result.datenGebiet.isErgebniseingabestatusKomplettEingabeBeendet &= zeile.wahlbezirk.isErgebniseingabestatusEingabeBeendet;
      result.datenGebiet.isErgebniseingabestatusKomplettGesperrt &= zeile.wahlbezirk.isErgebniseingabestatusGesperrt;
      result.datenGebiet.isErgebniseingabestatusKomplettAbgeschlossen &= zeile.wahlbezirk.isErgebniseingabestatusAbgeschlossen;
      result.datenGebiet.isErgebniseingabestatusKomplettFreigegeben &= zeile.wahlbezirk.isErgebniseingabestatusFreigegeben;
      result.datenGebiet.isErgebniseingabestatusKomplettAusbleibend &= zeile.wahlbezirk.isErgebniseingabestatusAusbleibend;
    }

    result.addZeilen(zeilenInDatenGebietFuerDatenArt);

    if (zeilenInDatenGebietFuerDatenArt.isEmpty()) {
      log.warn("Keine Daten [datenGebiet = {}, datenArt = {}]", datenGebiet == null ? "null" : datenGebiet.getNummer() + " (" + datenGebiet.getName() + ")", datenArt);
    } else {
      fillInDocument(baseElectContext, result, zeilenInDatenGebietFuerDatenArt.get(0), datenArt);
    }

    return result;
  }

  protected abstract D createDocument(BaseElectContext baseElectContext, WahlartErgebnisart ergebnisart, WahlverwaltungGebietInformation datenGebiet, ErgebnisDocumentDatenArt datenArt);

  protected void fillInDocument(BaseElectContext baseElectContext, D document, Z template, ErgebnisDocumentDatenArt datenArt) {
    fillInDocumentBasisinformationen(baseElectContext, document, template, datenArt);
  }

  protected void fillInDocumentBasisinformationen(BaseElectContext baseElectContext, AbstractErgebnisTabellarischDocument< ? > document, AbstractErgebnisTabellarischDocumentZeile template,
      ErgebnisDocumentDatenArt datenArt) {
    document.wahlkreis = template.wahlkreis.copy();
    document.stimmkreis = template.stimmkreis.copy();
    document.wahlbereich = template.wahlbereich.copy();

    document.bundesland = template.bundesland.copy();
    document.landkreis = template.landkreis.copy();
    document.gemeindeverband = template.gemeindeverband.copy();
    document.gemeinde = template.gemeinde.copy();
    document.ortsbezirk = template.ortsbezirk.copy();
    document.stadtbezirk = template.stadtbezirk.copy();

    document.stimmenart = datenArt.formatStimmabgabeart(baseElectContext.getMessages());
  }

  protected List<Z> collectZeilenFuerDatenArt(ErgebnisDocumentDatenArt datenArt, List<Z> zeilen) {
    if (ErgebnisDocumentDatenArt.GESAMT.equals(datenArt)) {
      return zeilen;
    }

    List<Z> result = new ArrayList<>();

    for (Z zeile : zeilen) {
      if (datenArt.equals(zeile.datenArt)) {
        result.add(zeile);
      }
    }

    return result;
  }
}
