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

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;

import javax.inject.Inject;

import de.ivu.elect.business.ergebnis.control.ErgebnisDaten;
import de.ivu.elect.business.ergebnis.entity.Ergebnisart;
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.AbstractErgebnisTabellarischDocumentCreator;
import de.ivu.elect.dokumente.ergebnis.ErgebnisDocumentDatenArt;
import de.ivu.elect.dokumente.ergebnis.ErgebnisDocumentDatenGebietDocument;
import de.ivu.elect.dokumente.ergebnis.ErgebnisDocumentGebietDocument;
import de.ivu.elect.dokumente.ergebnis.zusammenstellung.ErgebnisTabellarischAufteilung;
import de.ivu.elect.dokumente.ergebnis.zusammenstellung.ErgebnisTabellarischAufteilungBlatt;

public class ErgebnisGesamtergebnisTabellarischDocumentCreator
    extends
      AbstractErgebnisTabellarischDocumentCreator<ErgebnisGesamtergebnisTabellarischDocument, ErgebnisGesamtergebnisTabellarischDocumentZeile> {

  @Inject
  private transient ErgebnisGesamtergebnisTabellarischDocumentZeileCreator documentZeileCreator;

  public ErgebnisGesamtergebnisTabellarischDocumentCreator() {
    super();
  }

  @Override
  protected ErgebnisGesamtergebnisTabellarischDocument createDocument(BaseElectContext baseElectContext, WahlartErgebnisart ergebnisart, WahlverwaltungGebietInformation stimmzettelgebietInformation,
      WahlverwaltungGebietInformation datenGebiet, ErgebnisDocumentDatenArt datenArt) {
    return new ErgebnisGesamtergebnisTabellarischDocument(baseElectContext, ergebnisart, ErgebnisGesamtergebnisTabellarischDocumentDokumentVorlageArt.GESAMTERGEBNIS,
        new ErgebnisDocumentDatenGebietDocument(stimmzettelgebietInformation, datenGebiet), datenArt);
  }

  @Override
  public ErgebnisGesamtergebnisTabellarischDocument mapDocument(BaseElectContext baseElectContext, ErgebnisDaten ergebnisDaten, WahlverwaltungGebietInformation stimmzettelgebietInformation,
      WahlverwaltungGebietInformation tabelleDatenGebietInformation, String erfassungsgebietGebietsreferenzFormat, ErgebnisTabellarischAufteilungBlatt blatt) {
    ErgebnisGesamtergebnisTabellarischDocument result = mapDocument(baseElectContext, ergebnisDaten, stimmzettelgebietInformation, tabelleDatenGebietInformation, erfassungsgebietGebietsreferenzFormat,
        blatt, documentZeileCreator);

    /*
     *  ErgebnisGesamtergebnisTabellarischDocument#dokumentVorlageArt wird ausgewertet, um das richtige ODT-Template zu finden!!!
     */

    if (ErgebnisTabellarischAufteilung.STIMMENART_STIMMZETTEL.equals(blatt.getStimmenart())) {
      result.dokumentVorlageArt = ErgebnisGesamtergebnisTabellarischDocumentDokumentVorlageArt.STIMMZETTEL;

      if (blatt.isErstesBlattMitStimmenart()) {
        result.dokumentVorlageArt = ErgebnisGesamtergebnisTabellarischDocumentDokumentVorlageArt.STIMMZETTEL_ERSTE_SEITE_ANDERS;
      }
    }

    if (ErgebnisTabellarischAufteilung.STIMMENART_DIREKTSTIMMEN.equals(blatt.getStimmenart())) {
      result.dokumentVorlageArt = ErgebnisGesamtergebnisTabellarischDocumentDokumentVorlageArt.DIREKTSTIMMEN;

      if (blatt.isErstesBlattMitStimmenart()) {
        result.dokumentVorlageArt = ErgebnisGesamtergebnisTabellarischDocumentDokumentVorlageArt.DIREKTSTIMMEN_ERSTE_SEITE_ANDERS;
      }
    }

    if (ErgebnisTabellarischAufteilung.STIMMENART_LISTENSTIMMEN.equals(blatt.getStimmenart())) {
      result.dokumentVorlageArt = ErgebnisGesamtergebnisTabellarischDocumentDokumentVorlageArt.LISTENSTIMMEN;

      if (blatt.isErstesBlattMitStimmenart()) {
        result.dokumentVorlageArt = ErgebnisGesamtergebnisTabellarischDocumentDokumentVorlageArt.LISTENSTIMMEN_ERSTE_SEITE_ANDERS;
      }
    }

    return result;
  }

  @Override
  protected void fillInDocumentHeader(BaseElectContext baseElectContext, ErgebnisGesamtergebnisTabellarischDocument result, ErgebnisDocumentDatenArt tabelleDatenArt,
      ErgebnisGesamtergebnisTabellarischDocumentZeile template) {
    super.fillInDocument(baseElectContext, result, template, tabelleDatenArt);

    result.berechneUeberschriftenQueue();
  }

  @Override
  protected void fillInDocumentSummen(BaseElectContext baseElectContext, ErgebnisGesamtergebnisTabellarischDocument result, ErgebnisDocumentDatenArt tabelleDatenArt,
      List<ErgebnisGesamtergebnisTabellarischDocumentZeile> zeilen) {
    result.berechneSummen(zeilen);
  }

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

  @Override
  public ErgebnisGesamtergebnisTabellarischDocument createDocumentFuerDatenArtUrnenwahl(BaseElectContext baseElectContext, WahlartErgebnisart ergebnisart,
      WahlverwaltungGebietInformation stimmzettelgebietInformation, WahlverwaltungGebietInformation datenGebiet, List<ErgebnisGesamtergebnisTabellarischDocumentZeile> zeilenInDatenGebiet) {
    ErgebnisGesamtergebnisTabellarischDocument result = super.createDocumentFuerDatenArtUrnenwahl(baseElectContext, ergebnisart, stimmzettelgebietInformation, datenGebiet, zeilenInDatenGebiet);

    fillInZeilenZwischensummenFuerGemeinden(stimmzettelgebietInformation, datenGebiet, result); // ELECT-8697

    return result;
  }

  @Override
  public ErgebnisGesamtergebnisTabellarischDocument createDocumentFuerDatenArtBriefwahl(BaseElectContext baseElectContext, WahlartErgebnisart ergebnisart,
      WahlverwaltungGebietInformation stimmzettelgebietInformation, WahlverwaltungGebietInformation datenGebiet, List<ErgebnisGesamtergebnisTabellarischDocumentZeile> zeilenInDatenGebiet) {
    ErgebnisGesamtergebnisTabellarischDocument result = super.createDocumentFuerDatenArtBriefwahl(baseElectContext, ergebnisart, stimmzettelgebietInformation, datenGebiet, zeilenInDatenGebiet);

    fillInZeilenZwischensummenFuerGemeinden(stimmzettelgebietInformation, datenGebiet, result); // ELECT-8697

    return result;
  }

  private void fillInZeilenZwischensummenFuerGemeinden(WahlverwaltungGebietInformation stimmzettelgebietInformation, WahlverwaltungGebietInformation datenGebiet,
      ErgebnisGesamtergebnisTabellarischDocument document) {
    ErgebnisDocumentDatenArt datenArt = document.datenArt;

    // Zeilen kopieren, um über diese (ohne Änderungen duch Hinzufügen der Zwischensummenzeilen) zu iterieren...
    List<ErgebnisGesamtergebnisTabellarischDocumentZeile> zeilenInGemeindeverband = new ArrayList<>(document.zeilen);

    int gemeindeBetrachtetCounter = 0;
    ErgebnisDocumentGebietDocument gemeindeBetrachtet = null;
    List<ErgebnisGesamtergebnisTabellarischDocumentZeile> zeilenInGemeindeBetrachtet = new ArrayList<>();

    for (Iterator<ErgebnisGesamtergebnisTabellarischDocumentZeile> iter = zeilenInGemeindeverband.iterator(); iter.hasNext();) {
      ErgebnisGesamtergebnisTabellarischDocumentZeile zeile = iter.next();
      ErgebnisDocumentGebietDocument gemeindeInZeile = zeile.gemeinde;

      if (!zeile.isZwischensumme()) {
        if (gemeindeBetrachtet == null) {
          gemeindeBetrachtet = gemeindeInZeile;
          gemeindeBetrachtetCounter++;
        }

        if (Objects.equals(gemeindeBetrachtet.name, gemeindeInZeile.name) && Objects.equals(gemeindeBetrachtet.nummer, gemeindeInZeile.nummer)) {
          zeilenInGemeindeBetrachtet.add(zeile);
        } else {
          // Zwischensummenzeile für "bisherige" Gemeinde VOR aktuell betrachteter Zeile (der "nächsten" Gemeinde) hinzufügen...
          fillInZeileZwischensummenFuerGemeinde(stimmzettelgebietInformation, datenGebiet, datenArt, gemeindeBetrachtet, document, document.zeilen.indexOf(zeile), zeilenInGemeindeBetrachtet);

          gemeindeBetrachtet = gemeindeInZeile;
          gemeindeBetrachtetCounter++;

          zeilenInGemeindeBetrachtet.clear();
          zeilenInGemeindeBetrachtet.add(zeile);
        }

        if (!iter.hasNext() && gemeindeBetrachtetCounter > 1) {
          // Zwischensummenzeile für letzte Gemeinde HINTER aktuell betrachteter Zeile (== letzte Zeile in der Tabelle) hinzufügen...
          fillInZeileZwischensummenFuerGemeinde(stimmzettelgebietInformation, datenGebiet, datenArt, gemeindeBetrachtet, document, document.zeilen.indexOf(zeile) + 1, zeilenInGemeindeBetrachtet);
        }
      }
    }
  }

  private void fillInZeileZwischensummenFuerGemeinde(WahlverwaltungGebietInformation stimmzettelgebietInformation, WahlverwaltungGebietInformation datenGebiet, ErgebnisDocumentDatenArt datenArt,
      ErgebnisDocumentGebietDocument gemeinde, ErgebnisGesamtergebnisTabellarischDocument targetDocument, int targetZeilenIndexFuerZwischensummen,
      List<ErgebnisGesamtergebnisTabellarischDocumentZeile> sourceZeilenFuerZwischensumme) {
    String zwischensummenBezeichnung = gemeinde.name + " gesamt";

    ErgebnisGesamtergebnisTabellarischDocument sourceDocument = targetDocument;

    fillInZeileZwischensummen(zwischensummenBezeichnung, stimmzettelgebietInformation, datenGebiet, datenArt, targetDocument, targetZeilenIndexFuerZwischensummen, sourceDocument,
        sourceZeilenFuerZwischensumme, zeileZwischensummen -> {
          zeileZwischensummen.containsZwischensumme = true;
        });
  }

  public ErgebnisGesamtergebnisTabellarischDocument createDocumentAlleDatenArten(BaseElectContext baseElectContext, Ergebnisart ergebnisart,
      WahlverwaltungGebietInformation stimmzettelgebietInformation, WahlverwaltungGebietInformation datenGebiet, List<ErgebnisGesamtergebnisTabellarischDocumentZeile> zeilenInDatenGebiet) {
    ErgebnisGesamtergebnisTabellarischDocument documentFuerDatenArtUrnenwahl = createDocumentFuerDatenArtUrnenwahl(baseElectContext, ergebnisart, stimmzettelgebietInformation, datenGebiet,
        zeilenInDatenGebiet);
    ErgebnisGesamtergebnisTabellarischDocument documentFuerDatenArtBriefwahl = createDocumentFuerDatenArtBriefwahl(baseElectContext, ergebnisart, stimmzettelgebietInformation, datenGebiet,
        zeilenInDatenGebiet);

    List<ErgebnisGesamtergebnisTabellarischDocumentZeile> zeilenInDatenGebietFuerAlleDatenArten = new ArrayList<>();
    zeilenInDatenGebietFuerAlleDatenArten.addAll(documentFuerDatenArtUrnenwahl.zeilen);
    zeilenInDatenGebietFuerAlleDatenArten.addAll(documentFuerDatenArtBriefwahl.zeilen);

    ErgebnisGesamtergebnisTabellarischDocument result = createDocument(baseElectContext, ergebnisart, stimmzettelgebietInformation, datenGebiet, ErgebnisDocumentDatenArt.GESAMT,
        zeilenInDatenGebietFuerAlleDatenArten);
    result.stimmenart = "";

    /*
     * Die (Gesamt-) Summenzeile aus den Dokumenten per Datenart als Zwischenzeilen im kombinierten Dokument hinzufügen. Das Ganze erst nach Erzeugung des kombinierten Dokumentes, damit diese Anzahlen
     * nicht in die Summenzeile des kombinierten Dokumentes eingerechnet werden!
     */

    fillInZeileZwischensummenFuerDatenArt(baseElectContext, stimmzettelgebietInformation, datenGebiet, result, documentFuerDatenArtUrnenwahl);
    fillInZeileZwischensummenFuerDatenArt(baseElectContext, stimmzettelgebietInformation, datenGebiet, result, documentFuerDatenArtBriefwahl);

    return result;
  }

  public ErgebnisGesamtergebnisTabellarischDocument createSummenDocumentAlleDatenArten(BaseElectContext baseElectContext, Ergebnisart ergebnisart,
      WahlverwaltungGebietInformation stimmzettelgebietInformation, WahlverwaltungGebietInformation summenGebiet, WahlverwaltungGebietInformation datenGebiet,
      List<ErgebnisGesamtergebnisTabellarischDocumentZeile> zeilenInDatenGebiet) {
    ErgebnisGesamtergebnisTabellarischDocument result = createDocumentAlleDatenArten(baseElectContext, ergebnisart, stimmzettelgebietInformation, datenGebiet, zeilenInDatenGebiet);
    fillInSummenDocument(summenGebiet, result);

    return result;
  }

  private void fillInZeileZwischensummenFuerDatenArt(BaseElectContext baseElectContext, WahlverwaltungGebietInformation stimmzettelgebietInformation, WahlverwaltungGebietInformation datenGebiet,
      ErgebnisGesamtergebnisTabellarischDocument documentFuerAlleDatenArten, ErgebnisGesamtergebnisTabellarischDocument documentFuerDatenArt) {
    ErgebnisDocumentDatenArt datenArt = documentFuerDatenArt.datenArt;

    ErgebnisGesamtergebnisTabellarischDocumentZeile letzteZeileFuerDatenArtInDocumentFuerAlleDatenArten = null;

    for (ErgebnisGesamtergebnisTabellarischDocumentZeile zeile : documentFuerAlleDatenArten.zeilen) {
      if (datenArt.equals(zeile.datenArt)) {
        letzteZeileFuerDatenArtInDocumentFuerAlleDatenArten = zeile;
      }
    }

    if (letzteZeileFuerDatenArtInDocumentFuerAlleDatenArten != null) {
      String zwischensummenBezeichnung = datenArt.formatZwischensummeGesamt(baseElectContext.getMessages()); // Urnenwahl/Briefwahl gesamt

      ErgebnisGesamtergebnisTabellarischDocument targetDocument = documentFuerAlleDatenArten;
      int targetZeilenIndexFuerZwischensummen = documentFuerAlleDatenArten.zeilen.indexOf(letzteZeileFuerDatenArtInDocumentFuerAlleDatenArten) + 1;

      ErgebnisGesamtergebnisTabellarischDocument sourceDocument = documentFuerDatenArt;
      List<ErgebnisGesamtergebnisTabellarischDocumentZeile> sourceZeilenFuerZwischensumme = documentFuerDatenArt.zeilen;

      fillInZeileZwischensummen(zwischensummenBezeichnung, stimmzettelgebietInformation, datenGebiet, datenArt, targetDocument, targetZeilenIndexFuerZwischensummen, sourceDocument,
          sourceZeilenFuerZwischensumme, zeileZwischensummen -> {
            zeileZwischensummen.datenArtZwischensumme = true;
          });
    }
  }

  private void fillInZeileZwischensummen(String zwischensummenBezeichnung, WahlverwaltungGebietInformation stimmzettelgebietInformation, WahlverwaltungGebietInformation datenGebiet,
      ErgebnisDocumentDatenArt datenArt, ErgebnisGesamtergebnisTabellarischDocument targetDocument, int targetZeilenIndexFuerZwischensummen, ErgebnisGesamtergebnisTabellarischDocument sourceDocument,
      List<ErgebnisGesamtergebnisTabellarischDocumentZeile> sourceZeilenFuerZwischensumme, Consumer<ErgebnisGesamtergebnisTabellarischDocumentZeile> zeileZwischensummenPostProcessor) {
    if (!sourceZeilenFuerZwischensumme.isEmpty()) {
      ErgebnisGesamtergebnisTabellarischDocument dummyDocumentFuerZwischensummen = sourceDocument.copy();
      dummyDocumentFuerZwischensummen.berechneSummen(sourceZeilenFuerZwischensumme);

      ErgebnisGesamtergebnisTabellarischDocumentZeile zeileZwischensummen = sourceZeilenFuerZwischensumme.get(0).copy();
      dummyDocumentFuerZwischensummen.ergebnisSummen.copyTo(zeileZwischensummen);

      zeileZwischensummen.name = zwischensummenBezeichnung;

      zeileZwischensummen.datenGebiet = new ErgebnisDocumentDatenGebietDocument(stimmzettelgebietInformation, datenGebiet);
      zeileZwischensummen.datenArt = datenArt;

      zeileZwischensummen.wahlbezirk.name = zwischensummenBezeichnung;
      zeileZwischensummen.wahlbezirk.nummer = zwischensummenBezeichnung;
      zeileZwischensummen.wahlbezirk.nummerFormatiert = zwischensummenBezeichnung;
      zeileZwischensummen.wahlbezirk.kuerzel = zwischensummenBezeichnung;

      zeileZwischensummen.wahlbezirkGebietsreferenz10Stellig = zwischensummenBezeichnung;
      zeileZwischensummen.wahlbezirkGebietsreferenz10StelligFormatiert = zwischensummenBezeichnung;
      zeileZwischensummen.wahlbezirkGebietsreferenz13Stellig = zwischensummenBezeichnung;
      zeileZwischensummen.wahlbezirkGebietsreferenz13StelligFormatiert = zwischensummenBezeichnung;

      if (targetZeilenIndexFuerZwischensummen < 0 || targetZeilenIndexFuerZwischensummen > targetDocument.zeilen.size()) {
        log.error("\t\tErzeuge KEINE Zeile für Zwischensumme aufgrund eines ungültigen Index [datenArt = {}, name = {}, zeilen für zwischensummen = {}, zeilen in tabelle bisher = {}, index = {}]",
            datenArt, zwischensummenBezeichnung, sourceZeilenFuerZwischensumme.size(), targetDocument.zeilen.size(), targetZeilenIndexFuerZwischensummen);
      } else {
        log.info("\t\tErzeuge Zeile für Zwischensumme [datenGebiet = {} ({}), datenArt = {}, name = {}, zeilen für zwischensummen = {}, zeilen in tabelle bisher = {}, index = {}]",
            datenGebiet.getNummer(), datenGebiet.getName(), datenArt, zwischensummenBezeichnung, sourceZeilenFuerZwischensumme.size(), targetDocument.zeilen.size(),
            targetZeilenIndexFuerZwischensummen);

        zeileZwischensummenPostProcessor.accept(zeileZwischensummen);

        targetDocument.zeilen.add(targetZeilenIndexFuerZwischensummen, zeileZwischensummen);
      }
    }
  }

  public ReferendumErgebnisGesamtergebnisTabellarischDocument createReferendumDocument(BaseElectContext baseElectContext, WahlartErgebnisart ergebnisart,
      WahlverwaltungGebietInformation stimmzettelgebietInformation, WahlverwaltungGebietInformation datenGebiet, WahlverwaltungGebietInformation gebietUebergeordnetInformation,
      ErgebnisDocumentDatenArt datenArt, List<ReferendumErgebnisGesamtergebnisTabellarischDocumentZeile> zeilenFuerDatenArt) {
    ReferendumErgebnisGesamtergebnisTabellarischDocument result = createReferendumDocument(baseElectContext, ergebnisart, stimmzettelgebietInformation, gebietUebergeordnetInformation, datenArt);

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

    return result;
  }

  protected ReferendumErgebnisGesamtergebnisTabellarischDocument createReferendumDocument(BaseElectContext baseElectContext, WahlartErgebnisart ergebnisart,
      WahlverwaltungGebietInformation stimmzettelgebietInformation, WahlverwaltungGebietInformation datenGebiet, ErgebnisDocumentDatenArt datenArt) {
    return new ReferendumErgebnisGesamtergebnisTabellarischDocument(baseElectContext, ergebnisart, new ErgebnisDocumentDatenGebietDocument(stimmzettelgebietInformation, datenGebiet), datenArt);
  }

  protected void fillInReferendumDocument(BaseElectContext baseElectContext, ReferendumErgebnisGesamtergebnisTabellarischDocument document,
      ReferendumErgebnisGesamtergebnisTabellarischDocumentZeile template, ErgebnisDocumentDatenArt datenArt) {
    fillInDocumentBasisinformationen(baseElectContext, document, template, datenArt);
  }

  @Override
  protected void fillInDocument(BaseElectContext baseElectContext, ErgebnisGesamtergebnisTabellarischDocument document, ErgebnisGesamtergebnisTabellarischDocumentZeile template,
      ErgebnisDocumentDatenArt datenArt) {
    document.berechneUeberschriftenQueue();

    super.fillInDocument(baseElectContext, document, template, datenArt);

    document.berechneSummen(document.zeilen);
    document.berechneUeberschriftenQueue();
  }
}
