
package de.ivu.elect.dokumente.ergebnis;

import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;
import static org.apache.commons.lang3.StringUtils.isAnyBlank;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;

import de.ivu.document.generator.template.BaseDocument;
import de.ivu.document.generator.template.annotations.DocumentComplexField;
import de.ivu.document.generator.template.annotations.DocumentMethod;
import de.ivu.document.generator.template.annotations.DocumentSimpleField;
import de.ivu.elect.business.gebietsbaum.boundary.WahlverwaltungGebietInformation;
import de.ivu.elect.business.gebietsbaum.boundary.WahlverwaltungGebietInformationByNummerComparator;
import de.ivu.elect.business.gebietsbaum.control.GebietByNummerComparator;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.wahl.entity.WahlartErgebnisart;

public abstract class AbstractErgebnisTabellarischDocumentZeile extends BaseDocument implements ErgebnisDocumentZeile, ErgebnisDocumentMitWahlverwaltungGebietInformation {

  private static final long serialVersionUID = 1L;

  public static final Comparator<Gebiet> GEBIET_DEFAULT_COMPARATOR = GebietByNummerComparator.ASCENDING;

  public static final Comparator<WahlverwaltungGebietInformation> GEBIET_INFORMATION_DEFAULT_COMPARATOR = new WahlverwaltungGebietInformationByNummerComparator();

  public static final Comparator<WahlverwaltungGebietInformation> GEMEINDE_INFORMATION_DEFAULT_COMPARATOR = GEBIET_INFORMATION_DEFAULT_COMPARATOR;

  @DocumentComplexField("Zeile-Daten-Gebiet") // ELECT-8770
  public ErgebnisDocumentDatenGebietDocument datenGebiet;

  @DocumentSimpleField("Zeile-Daten-Art")
  public ErgebnisDocumentDatenArt datenArt;

  public boolean datenArtZwischensumme;

  /**
   * Wert in erster Spalte "Nr."
   */
  @DocumentSimpleField("Nummer")
  public Long nummer;

  /**
   * Wert in zweiter Spalte "Stimmbezirke Briefwahlvorstände Gemeinden"
   */
  @DocumentSimpleField("Name")
  public String name;

  @DocumentComplexField("Wahlkreis")
  public ErgebnisDocumentGebietDocument wahlkreis; // Oberbayern

  @DocumentComplexField("Stimmkreis")
  public ErgebnisDocumentGebietDocument stimmkreis; // 101 München-Hadern

  @DocumentComplexField("Wahlbereich")
  public ErgebnisDocumentGebietDocument wahlbereich;

  @DocumentComplexField("Wahlbezirk")
  public ErgebnisDocumentErfassungsgebietDocument wahlbezirk; // Wahlbezirk 201

  @DocumentSimpleField("Wahlbezirk-Ist-Briefwahlbezirk")
  public Boolean wahlbezirkBriefwahlbezirk;

  @DocumentSimpleField("Wahlbezirk-Gebietsreferenz-10-Stellig")
  public String wahlbezirkGebietsreferenz10Stellig;

  @DocumentSimpleField("Wahlbezirk-Gebietsreferenz-10-Stellig-Formatiert")
  public String wahlbezirkGebietsreferenz10StelligFormatiert;

  @DocumentSimpleField("Wahlbezirk-Gebietsreferenz-13-Stellig")
  public String wahlbezirkGebietsreferenz13Stellig;

  @DocumentSimpleField("Wahlbezirk-Gebietsreferenz-13-Stellig-Formatiert")
  public String wahlbezirkGebietsreferenz13StelligFormatiert;

  @DocumentComplexField("Bundesland")
  public ErgebnisDocumentGebietDocument bundesland; // Bayern

  @DocumentComplexField("Landkreis")
  public ErgebnisDocumentGebietDocument landkreis; // Landkreis/Kreisfreie Stadt

  @DocumentComplexField("Gemeindeverband")
  public ErgebnisDocumentGebietDocument gemeindeverband; // Bspw. Samtgemeinde (KOM-NI), Amt (KOM-BB, KOM-MV, KOM-SH)

  @DocumentComplexField("Gemeinde")
  public ErgebnisDocumentGebietDocument gemeinde; // Landeshauptstadt München

  public boolean gemeindeZwischensumme;

  @DocumentSimpleField("Anzahl-Wahlberechtigte-Ohne-Wahlschein")
  public Long wahlberechtigteOhneWahlschein;

  @DocumentSimpleField("Anzahl-Wahlberechtigte-Mit-Wahlschein")
  public Long wahlberechtigteMitWahlschein;

  @DocumentSimpleField("Anzahl-Wahlberechtigte-Nicht-Im-Waehlerverzeichnis")
  public Long wahlberechtigteNichtImWaehlerverzeichnis;

  @DocumentSimpleField("Anzahl-Wahlberechtigte-Gesamt")
  public Long wahlberechtigteGesamt;

  /**
   * @deprecated ELECT-8018
   * @see AbstractErgebnisTabellarischDocumentZeile#wahlberechtigteOhneWahlschein
   */
  @Deprecated
  @DocumentSimpleField("A1")
  public Long a1;

  /**
   * @deprecated ELECT-8018
   * @see AbstractErgebnisTabellarischDocumentZeile#wahlberechtigteMitWahlschein
   */
  @Deprecated
  @DocumentSimpleField("A2")
  public Long a2;

  /**
   * @deprecated ELECT-8018
   * @see AbstractErgebnisTabellarischDocumentZeile#wahlberechtigteNichtImWaehlerverzeichnis
   */
  @Deprecated
  @DocumentSimpleField("A3")
  public Long a3;

  /**
   * @deprecated ELECT-8018
   * @see AbstractErgebnisTabellarischDocumentZeile#wahlberechtigteGesamt
   */
  @Deprecated
  @DocumentSimpleField("A")
  public Long a;

  @DocumentSimpleField("Anzahl-Waehler-Ohne-Wahlschein")
  public Long waehlerOhneWahlschein;

  @DocumentSimpleField("Anzahl-Waehler-Mit-Wahlschein")
  public Long waehlerMitWahlschein;

  @DocumentSimpleField("Anzahl-Waehler-Mit-Wahlschein-Anteil-Aus-Urnenwahl")
  public Long waehlerMitWahlscheinAnteilAusUrnenwahl;

  @DocumentSimpleField("Anzahl-Waehler-Mit-Wahlschein-Anteil-Aus-Briefwahl")
  public Long waehlerMitWahlscheinAnteilAusBriefwahl;

  @DocumentSimpleField("Anzahl-Waehler-Gesamt")
  public Long waehlerGesamt;

  /**
   * @deprecated ELECT-8018
   * @see AbstractErgebnisTabellarischDocumentZeile#waehlerMitWahlschein
   */
  @Deprecated
  @DocumentSimpleField("B1")
  public Long b1;

  /**
   * @deprecated ELECT-8018
   * @see AbstractErgebnisTabellarischDocumentZeile#waehlerOhneWahlschein
   */
  @Deprecated
  @DocumentSimpleField("B2")
  public Long b2;

  /**
   * @deprecated ELECT-8018
   * @see AbstractErgebnisTabellarischDocumentZeile#waehlerGesamt
   */
  @Deprecated
  @DocumentSimpleField("B")
  public Long b;

  @DocumentSimpleField("Stimmzettel-Gueltig")
  public Long stimmzettelGueltig;

  @DocumentSimpleField("Stimmzettel-Ungueltig")
  public Long stimmzettelUngueltig;

  @DocumentSimpleField("Stimmzettel-Gesamt")
  public Long stimmzettelGesamt;

  public final WahlartErgebnisart ergebnisart;

  public final Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet;

  public AbstractErgebnisTabellarischDocumentZeile(WahlartErgebnisart ergebnisart, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    super();

    this.ergebnisart = ergebnisart;

    this.wahldurchfuehrungsgebiet = wahldurchfuehrungsgebiet;

    this.nummer = null;
    this.name = "";

    this.datenGebiet = new ErgebnisDocumentDatenGebietDocument();

    this.datenArt = null;
    this.datenArtZwischensumme = false;

    this.wahlkreis = new ErgebnisDocumentGebietDocument();
    this.stimmkreis = new ErgebnisDocumentGebietDocument();
    this.wahlbereich = new ErgebnisDocumentGebietDocument();

    this.wahlbezirk = new ErgebnisDocumentErfassungsgebietDocument();

    this.wahlbezirkBriefwahlbezirk = this.wahlbezirk.isBriefwahlbezirk;

    this.wahlbezirkGebietsreferenz10Stellig = "";
    this.wahlbezirkGebietsreferenz10StelligFormatiert = "";
    this.wahlbezirkGebietsreferenz13Stellig = "";
    this.wahlbezirkGebietsreferenz13StelligFormatiert = "";

    this.bundesland = new ErgebnisDocumentGebietDocument();

    this.landkreis = new ErgebnisDocumentGebietDocument();

    this.gemeindeverband = new ErgebnisDocumentGebietDocument();

    this.gemeinde = new ErgebnisDocumentGebietDocument();
    this.gemeindeZwischensumme = false;

    this.wahlberechtigteOhneWahlschein = 0L;
    this.wahlberechtigteMitWahlschein = 0L;
    this.wahlberechtigteNichtImWaehlerverzeichnis = 0L;
    this.wahlberechtigteGesamt = 0L;

    // ELECT-8018, LEGACY!!!
    this.a1 = 0L;
    this.a2 = 0L;
    this.a3 = 0L;
    this.a = 0L;

    this.waehlerOhneWahlschein = 0L;
    this.waehlerMitWahlschein = 0L;
    this.waehlerMitWahlscheinAnteilAusUrnenwahl = 0L;
    this.waehlerMitWahlscheinAnteilAusBriefwahl = 0L;
    this.waehlerGesamt = 0L;

    // ELECT-8018, LEGACY!!!
    this.b1 = 0L;
    this.b2 = 0L;
    this.b = 0L;

    this.stimmzettelGueltig = 0L;
    this.stimmzettelUngueltig = 0L;
    this.stimmzettelGesamt = 0L;
  }

  @DocumentMethod
  public boolean isZwischensumme() {
    return isGemeindeZwischensumme() || isDatenArtZwischensumme();
  }

  public boolean isDatenArtZwischensumme() {
    return datenArtZwischensumme;
  }

  public boolean isGemeindeZwischensumme() {
    return gemeindeZwischensumme;
  }

  public abstract AbstractErgebnisTabellarischDocumentZeile copy();

  protected <T extends AbstractErgebnisTabellarischDocumentZeile> T copyTo(T target) {
    target.nummer = nummer;
    target.name = name;

    target.datenGebiet = datenGebiet.copy();

    target.datenArt = datenArt;
    target.datenArtZwischensumme = datenArtZwischensumme;

    target.wahlkreis = wahlkreis.copy();
    target.stimmkreis = stimmkreis.copy();
    target.wahlbereich = wahlbereich.copy();

    target.wahlbezirk = wahlbezirk.copy();

    target.wahlbezirkBriefwahlbezirk = wahlbezirkBriefwahlbezirk;

    target.wahlbezirkGebietsreferenz10Stellig = wahlbezirkGebietsreferenz10Stellig;
    target.wahlbezirkGebietsreferenz10StelligFormatiert = wahlbezirkGebietsreferenz10StelligFormatiert;
    target.wahlbezirkGebietsreferenz13Stellig = wahlbezirkGebietsreferenz13Stellig;
    target.wahlbezirkGebietsreferenz13StelligFormatiert = wahlbezirkGebietsreferenz13StelligFormatiert;

    target.bundesland = bundesland.copy();

    target.landkreis = landkreis.copy();

    target.gemeindeverband = gemeindeverband.copy();

    target.gemeinde = gemeinde.copy();
    target.gemeindeZwischensumme = gemeindeZwischensumme;

    target.wahlberechtigteOhneWahlschein = wahlberechtigteOhneWahlschein;
    target.wahlberechtigteMitWahlschein = wahlberechtigteMitWahlschein;
    target.wahlberechtigteNichtImWaehlerverzeichnis = wahlberechtigteNichtImWaehlerverzeichnis;
    target.wahlberechtigteGesamt = wahlberechtigteGesamt;

    // ELECT-8018, LEGACY!!!
    target.a1 = a1;
    target.a2 = a2;
    target.a3 = a3;
    target.a = a;

    target.waehlerOhneWahlschein = waehlerOhneWahlschein;
    target.waehlerMitWahlschein = waehlerMitWahlschein;
    target.waehlerMitWahlscheinAnteilAusUrnenwahl = waehlerMitWahlscheinAnteilAusUrnenwahl;
    target.waehlerMitWahlscheinAnteilAusBriefwahl = waehlerMitWahlscheinAnteilAusBriefwahl;
    target.waehlerGesamt = waehlerGesamt;

    // ELECT-8018, LEGACY!!!
    target.b1 = b1;
    target.b2 = b2;
    target.b = b;

    target.stimmzettelGueltig = stimmzettelGueltig;
    target.stimmzettelUngueltig = stimmzettelUngueltig;
    target.stimmzettelGesamt = stimmzettelGesamt;

    return target;
  }

  protected Long fixNullToZero(Long value) {
    return value == null ? 0 : value;
  }

  public static <T extends AbstractErgebnisTabellarischDocumentZeile> List<T> filterZeilenBerechenbar(List<T> zeilen) {
    return zeilen.stream() //
        .filter(zeile -> !zeile.isZwischensumme()) //
        .collect(toList());
  }

  public static <T extends AbstractErgebnisTabellarischDocumentZeile> List<T> filterByWahlkreis(List<T> zeilen, WahlverwaltungGebietInformation wahlkreisInformation) {
    String wahlkreisNummer = wahlkreisInformation.getNummer();

    return zeilen.stream() //
        .filter(zeile -> wahlkreisNummer.equalsIgnoreCase(zeile.wahlkreis.nummer)) //
        .collect(toList());
  }

  public static <T extends AbstractErgebnisTabellarischDocumentZeile> List<T> filterByStimmkreis(List<T> zeilen, WahlverwaltungGebietInformation stimmkreisInformation) {
    String stimmkreisNummer = stimmkreisInformation.getNummer();

    return zeilen.stream() //
        .filter(zeile -> stimmkreisNummer.equalsIgnoreCase(zeile.stimmkreis.nummer)) //
        .collect(toList());
  }

  public static <T extends AbstractErgebnisTabellarischDocumentZeile> List<T> filterByWahlbereich(List<T> zeilen, WahlverwaltungGebietInformation wahlbereichInformation) {
    String wahlbereichNummer = wahlbereichInformation.getNummer();

    return zeilen.stream() //
        .filter(zeile -> wahlbereichNummer.equalsIgnoreCase(zeile.wahlbereich.nummer)) //
        .collect(toList());
  }

  public static <T extends AbstractErgebnisTabellarischDocumentZeile> List<T> filterByGebietUebergeordnet(List<T> zeilen, Gebiet gebiet) {
    List<T> result = new ArrayList<>();

    for (T zeile : zeilen) {
      Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = zeile.wahldurchfuehrungsgebiet;

      if (wahldurchfuehrungsgebiet != null && gebiet.equals(wahldurchfuehrungsgebiet.getUebergeordnetesGebiet())) {
        result.add(zeile);
      }
    }

    if (!result.isEmpty()) {
      return result;
    }

    throw new IllegalStateException("Keine Daten zu Gebiet vorhanden! [gebiet = " + gebiet + ", anzahlZeilen=" + zeilen.size() + "]");
  }

  public static <T extends AbstractErgebnisTabellarischDocumentZeile> SortedMap<WahlverwaltungGebietInformation, List<T>> groupByDatenGebiet(List<T> zeilen) {
    SortedMap<WahlverwaltungGebietInformation, List<T>> result = new TreeMap<>(GEMEINDE_INFORMATION_DEFAULT_COMPARATOR);

    for (T zeile : zeilen) {
      ErgebnisDocumentDatenGebietDocument datenGebiet = zeile.datenGebiet;

      requireNonNull(datenGebiet, "FAILED to find required Gebiet");

      if (isAnyBlank(datenGebiet.name, datenGebiet.nummer)) {
        throw new IllegalStateException("FAILED to find required Gebiet information [art = " + datenGebiet.art + ", name = " + datenGebiet.name + ", nummer = " + datenGebiet.nummer + "]");
      }

      WahlverwaltungGebietInformation datenGebietInformation = new WahlverwaltungGebietInformation(datenGebiet.art, datenGebiet.name, datenGebiet.nummer, datenGebiet.kuerzel);
      result.putIfAbsent(datenGebietInformation, new ArrayList<>());

      result.get(datenGebietInformation).add(zeile);
    }

    return result;
  }
}
