
package de.ivu.elect.dokumente.ergebnistabelle.gruppen;

import static de.ivu.elect.util.ListUtil.bildeBloeckeAlsListen;
import static java.util.Arrays.asList;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;

import javax.annotation.Nonnull;
import javax.inject.Inject;

import de.ivu.elect.business.ergebnis.control.ErgebnisDaten;
import de.ivu.elect.business.ergebnis.entity.Stimmabgabeart;
import de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.gebietsbaum.boundary.WahlverwaltungGebietInformationResolverContext;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.internet.control.GebietUndStimmabgabeart;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeGruppeStimmenErfassenLabelGenerator;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeGruppeStimmenErfassenLabelGeneratorFinder;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.Wahldurchfuehrung;
import de.ivu.elect.dokumente.ergebnistabelle.AbstractErgebnistabelleDocumentColumn.ErgebnistabelleErfassungsgebietDocumentColumn;
import de.ivu.elect.dokumente.ergebnistabelle.AbstractErgebnistabelleDocumentColumn.ErgebnistabelleStatistischeGemeindekennzifferDocumentColumn;
import de.ivu.elect.dokumente.ergebnistabelle.AbstractErgebnistabelleDocumentColumn.ErgebnistabelleWaehlerGesamtDocumentColumn;
import de.ivu.elect.dokumente.ergebnistabelle.AbstractErgebnistabelleDocumentColumn.ErgebnistabelleWaehlerMitWahlscheinBriefwahlInUrneErfassbarDocumentColumn;
import de.ivu.elect.dokumente.ergebnistabelle.AbstractErgebnistabelleDocumentColumn.ErgebnistabelleWaehlerMitWahlscheinDocumentColumn;
import de.ivu.elect.dokumente.ergebnistabelle.AbstractErgebnistabelleDocumentColumn.ErgebnistabelleWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahlDocumentColumn;
import de.ivu.elect.dokumente.ergebnistabelle.AbstractErgebnistabelleDocumentColumn.ErgebnistabelleWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahlDocumentColumn;
import de.ivu.elect.dokumente.ergebnistabelle.AbstractErgebnistabelleDocumentColumn.ErgebnistabelleWahlberechtigteGesamtDocumentColumn;
import de.ivu.elect.dokumente.ergebnistabelle.AbstractErgebnistabelleDocumentColumn.ErgebnistabelleWahlberechtigteMitWahlscheinDocumentColumn;
import de.ivu.elect.dokumente.ergebnistabelle.AbstractErgebnistabelleDocumentColumn.ErgebnistabelleWahlberechtigteNichtImWaehlerverzeichnisDocumentColumn;
import de.ivu.elect.dokumente.ergebnistabelle.AbstractErgebnistabelleDocumentColumn.ErgebnistabelleWahlberechtigteOhneWahlscheinDocumentColumn;
import de.ivu.elect.dokumente.ergebnistabelle.AbstractErgebnistabelleExportMapper;
import de.ivu.elect.dokumente.ergebnistabelle.ErgebnistabelleDocumentColumn;
import de.ivu.elect.was.exchange.ergebnis.ErgebnisExportMapper;

/**
 * ELECTDD-101, entspr. V7 in MUC
 *
 * @see ErgebnisGesamtergebnisTabellarischDocumentExportMapper!!!
 */
public abstract class AbstractGruppenErgebnistabelleExportMapper extends AbstractErgebnistabelleExportMapper<GruppenErgebnistabelleCsvDTO> implements ErgebnisExportMapper {

  public static final String FILE_FORMAT_NAME = "file.format.ergebnistabelle.gruppen";

  @Inject
  protected transient StimmeingabeGruppeStimmenErfassenLabelGeneratorFinder stimmeingabeGruppeStimmenErfassenLabelGeneratorFinder;

  @Inject
  protected transient GruppenErgebnistabelleCsvDTOFactory gruppenErgebnistabelleCsvDTOFactory;

  public AbstractGruppenErgebnistabelleExportMapper() {
    super();
  }

  @Override
  protected boolean isAcceptContext(@Nonnull Wahl wahl) {
    return wahl.isWahlMitListengebieten() || wahl.isWahlMitDirektkandidatengebieten();
  }

  /**
   * Reihenfolge und Art der zu exportierenden Daten sind besser handhabbar, wenn wir sie getrennt bestimmen
   */
  protected List<GruppenErgebnistabelleZeilenSchluessel> createZeilenSchluesselSortiert(ErgebnisDaten ergebnisDaten, WahlverwaltungGebietInformationResolverContext context,
      Wahldurchfuehrungsgebiet auswertungsgebiet) {
    List<GruppenErgebnistabelleZeilenSchluessel> result = new ArrayList<>();

    for (Stimmabgabeart stimmabgabeart : asList(Stimmabgabeart.URNE, Stimmabgabeart.BRIEF, Stimmabgabeart.GESAMT)) {
      GebietUndStimmabgabeart gebietUndStimmabgabeart = new GebietUndStimmabgabeart(auswertungsgebiet, stimmabgabeart);

      for (Wahldurchfuehrungsgebiet erfassungsgebiet : ergebnisDaten.getErfassungsgebieteUnterhalbVon(gebietUndStimmabgabeart)) {
        GruppenErgebnistabelleZeilenSchluessel zeileFuerErfassungseinheit = GruppenErgebnistabelleZeilenSchluessel.createFuerErfassungseinheit(stimmabgabeart, erfassungsgebiet);

        if (isZeileExportieren(zeileFuerErfassungseinheit)) {
          result.add(zeileFuerErfassungseinheit);
        }
      }

      result.addAll(createZusaetzlicheZeilenSchluesselVorZeileFuerSummeSortiert(ergebnisDaten, auswertungsgebiet, stimmabgabeart));

      GruppenErgebnistabelleZeilenSchluessel zeileFuerSumme = GruppenErgebnistabelleZeilenSchluessel.createFuerSumme(stimmabgabeart, auswertungsgebiet);

      if (isZeileExportieren(zeileFuerSumme)) {
        result.add(zeileFuerSumme);
      }
    }

    return result;
  }

  protected Collection<GruppenErgebnistabelleZeilenSchluessel> createZusaetzlicheZeilenSchluesselVorZeileFuerSummeSortiert(ErgebnisDaten ergebnisDaten, Wahldurchfuehrungsgebiet auswertungsgebiet,
      Stimmabgabeart stimmabgabeart) {
    return Collections.emptyList(); // In der Defaultimplementierung ist nichts zu machen.
  }

  /**
   * Die Tabellen werden einzeln ausgegeben: einmal für die Kopfzahlen; einmal für Listen-, einmal für Direktstimmen.
   */
  protected List<List<ErgebnistabelleDocumentColumn>> createSpaltenSortedAndGroupedByAnzahlProSeite(ErgebnisDaten ergebnisDaten, WahlverwaltungGebietInformationResolverContext context,
      Wahldurchfuehrungsgebiet auswertungsgebiet, int anzahlSpaltenProSeite) {
    List<List<ErgebnistabelleDocumentColumn>> result = new ArrayList<>();

    result.add(collectColumnsKopfzahlen(ergebnisDaten, context, auswertungsgebiet));

    Wahldurchfuehrung wahldurchfuehrung = ergebnisDaten.getWahl();

    if (wahldurchfuehrung.isWahlMitDirektkandidatengebieten()) {
      result.addAll(collectColumnGroupsGruppeDirektstimmen(ergebnisDaten, context, auswertungsgebiet, anzahlSpaltenProSeite));
    }

    if (wahldurchfuehrung.isWahlMitListengebieten()) {
      result.addAll(collectColumnGroupsGruppeListenstimmen(ergebnisDaten, context, auswertungsgebiet, anzahlSpaltenProSeite));
    }

    return result;
  }

  protected List<ErgebnistabelleDocumentColumn> collectColumnsKopfzahlen(ErgebnisDaten ergebnisDaten, WahlverwaltungGebietInformationResolverContext context,
      Wahldurchfuehrungsgebiet auswertungsgebiet) {
    List<ErgebnistabelleDocumentColumn> result = new ArrayList<>();

    Locale locale = context.getLocale();

    if (isColumnGemeindekennzifferRendered(ergebnisDaten)) {
      result.add(createColumnGemeindekennziffer(context));
    }

    result.add(new ErgebnistabelleErfassungsgebietDocumentColumn(locale, messages, isColumnErfassungsgebietGebietstextAnzeigen(ergebnisDaten)));

    result.add(new ErgebnistabelleWahlberechtigteOhneWahlscheinDocumentColumn(locale, messages, kopfzahlLabelGenerator, auswertungsgebiet, ergebnisDaten));
    result.add(new ErgebnistabelleWahlberechtigteMitWahlscheinDocumentColumn(locale, messages, kopfzahlLabelGenerator, auswertungsgebiet, ergebnisDaten));
    result.add(new ErgebnistabelleWahlberechtigteNichtImWaehlerverzeichnisDocumentColumn(locale, messages, kopfzahlLabelGenerator, auswertungsgebiet, ergebnisDaten));
    result.add(new ErgebnistabelleWahlberechtigteGesamtDocumentColumn(locale, messages, kopfzahlLabelGenerator, auswertungsgebiet, ergebnisDaten));

    result.add(new ErgebnistabelleWaehlerGesamtDocumentColumn(locale, messages, kopfzahlLabelGenerator, auswertungsgebiet, ergebnisDaten));

    if (auswertungsgebiet.getWurzelgebiet().isBriefwahlInUrneErfassenInGebietOderDarunter()) {
      result.add(new ErgebnistabelleWaehlerMitWahlscheinBriefwahlInUrneErfassbarDocumentColumn(locale, messages, kopfzahlLabelGenerator, auswertungsgebiet, ergebnisDaten));

      if (isColumnWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahlAnzeigen(ergebnisDaten)) {
        result.add(new ErgebnistabelleWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahlDocumentColumn(locale, messages, kopfzahlLabelGenerator, auswertungsgebiet, ergebnisDaten));
      }

      if (isColumnWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahlAnzeigen(ergebnisDaten)) {
        result.add(new ErgebnistabelleWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahlDocumentColumn(locale, messages, kopfzahlLabelGenerator, auswertungsgebiet, ergebnisDaten));
      }
    } else {
      result.add(new ErgebnistabelleWaehlerMitWahlscheinDocumentColumn(locale, messages, kopfzahlLabelGenerator, auswertungsgebiet, ergebnisDaten));
    }

    return result;
  }

  private List<List<ErgebnistabelleDocumentColumn>> collectColumnGroupsGruppeListenstimmen(ErgebnisDaten ergebnisDaten, WahlverwaltungGebietInformationResolverContext context,
      Wahldurchfuehrungsgebiet auswertungsgebiet, int anzahlSpaltenProSeite) {
    return collectColumnGroupsGruppeStimmen(ergebnisDaten, context, auswertungsgebiet, collectColumnsGruppeListenstimmen(ergebnisDaten, context, auswertungsgebiet), anzahlSpaltenProSeite);
  }

  private List<ErgebnistabelleDocumentColumn> collectColumnsGruppeListenstimmen(ErgebnisDaten ergebnisDaten, WahlverwaltungGebietInformationResolverContext context,
      Wahldurchfuehrungsgebiet auswertungsgebiet) {
    List<ErgebnistabelleDocumentColumn> result = new ArrayList<>();

    StimmeingabeGruppeStimmenErfassenLabelGenerator stimmeingabeGruppeStimmenErfassenLabelGenerator = getStimmeingabeGruppeStimmenErfassenLabelGenerator(ergebnisDaten);

    for (GruppenErgebnistabelleSpaltenSchluessel spaltenSchluessel : gruppenErgebnistabelleCsvDTOFactory.getRelevanteSpaltenSchluesselSortiert(auswertungsgebiet, ergebnisDaten,
        Stimmenart.LISTENSTIMME)) {
      result.add(new GruppenErgebnistabelleDocumentColumn(context.getLocale(), messages, stimmeingabeGruppeStimmenErfassenLabelGenerator, spaltenSchluessel, auswertungsgebiet, ergebnisDaten,
          getSpaltenbezeichnungOhneLabelGenerator(spaltenSchluessel)));
    }

    return result;
  }

  private List<List<ErgebnistabelleDocumentColumn>> collectColumnGroupsGruppeDirektstimmen(ErgebnisDaten ergebnisDaten, WahlverwaltungGebietInformationResolverContext context,
      Wahldurchfuehrungsgebiet auswertungsgebiet, int anzahlSpaltenProSeite) {
    return collectColumnGroupsGruppeStimmen(ergebnisDaten, context, auswertungsgebiet, collectColumnsGruppeDirektstimmen(ergebnisDaten, context, auswertungsgebiet), anzahlSpaltenProSeite);
  }

  private List<ErgebnistabelleDocumentColumn> collectColumnsGruppeDirektstimmen(ErgebnisDaten ergebnisDaten, WahlverwaltungGebietInformationResolverContext context,
      Wahldurchfuehrungsgebiet auswertungsgebiet) {
    List<ErgebnistabelleDocumentColumn> result = new ArrayList<>();

    StimmeingabeGruppeStimmenErfassenLabelGenerator stimmeingabeGruppeStimmenErfassenLabelGenerator = getStimmeingabeGruppeStimmenErfassenLabelGenerator(ergebnisDaten);

    for (GruppenErgebnistabelleSpaltenSchluessel spaltenSchluessel : gruppenErgebnistabelleCsvDTOFactory.getRelevanteSpaltenSchluesselSortiert(auswertungsgebiet, ergebnisDaten,
        Stimmenart.DIREKTSTIMME)) {
      result.add(new GruppenErgebnistabelleDocumentColumn(context.getLocale(), messages, stimmeingabeGruppeStimmenErfassenLabelGenerator, spaltenSchluessel, auswertungsgebiet, ergebnisDaten,
          getSpaltenbezeichnungOhneLabelGenerator(spaltenSchluessel)));
    }

    return result;
  }

  private List<List<ErgebnistabelleDocumentColumn>> collectColumnGroupsGruppeStimmen(ErgebnisDaten ergebnisDaten, WahlverwaltungGebietInformationResolverContext context,
      Wahldurchfuehrungsgebiet auswertungsgebiet, List<ErgebnistabelleDocumentColumn> columnsGruppeStimmen, int anzahlSpaltenProSeite) {
    List<List<ErgebnistabelleDocumentColumn>> result = new ArrayList<>();

    List<ErgebnistabelleDocumentColumn> columnsGruppeStimmenGebietsgliederung = collectColumnsGruppeStimmenGebietsgliederung(ergebnisDaten, context, auswertungsgebiet);
    int anzahlSpaltenGruppeStimmenProSeite = anzahlSpaltenProSeite - columnsGruppeStimmenGebietsgliederung.size();

    for (List<ErgebnistabelleDocumentColumn> columnGroupGruppeStimmen : bildeBloeckeAlsListen(columnsGruppeStimmen, anzahlSpaltenGruppeStimmenProSeite)) {
      List<ErgebnistabelleDocumentColumn> columnGroup = new ArrayList<>();
      columnGroup.addAll(columnsGruppeStimmenGebietsgliederung);
      columnGroup.addAll(columnGroupGruppeStimmen);

      result.add(columnGroup);
    }

    return result;
  }

  protected List<ErgebnistabelleDocumentColumn> collectColumnsGruppeStimmenGebietsgliederung(ErgebnisDaten ergebnisDaten, WahlverwaltungGebietInformationResolverContext context,
      Wahldurchfuehrungsgebiet auswertungsgebiet) {
    List<ErgebnistabelleDocumentColumn> result = new ArrayList<>();

    if (isColumnGemeindekennzifferRendered(ergebnisDaten)) {
      result.add(createColumnGemeindekennziffer(context));
    }

    if (isColumnGruppeStimmenErfassungsgebietRendered(ergebnisDaten)) {
      result.add(new ErgebnistabelleErfassungsgebietDocumentColumn(context.getLocale(), messages, isColumnErfassungsgebietGebietstextAnzeigen(ergebnisDaten)));
    }

    return result;
  }

  protected boolean isColumnGemeindekennzifferRendered(ErgebnisDaten ergebnisDaten) {
    return true;
  }

  protected boolean isColumnErfassungsgebietGebietstextAnzeigen(ErgebnisDaten ergebnisDaten) {
    return true;
  }

  protected boolean isColumnGruppeStimmenErfassungsgebietRendered(ErgebnisDaten ergebnisDaten) {
    return true;
  }

  protected ErgebnistabelleStatistischeGemeindekennzifferDocumentColumn createColumnGemeindekennziffer(WahlverwaltungGebietInformationResolverContext context) {
    return new ErgebnistabelleStatistischeGemeindekennzifferDocumentColumn(context.getLocale(), messages, customGebietInformationen);
  }

  protected String getSpaltenbezeichnungOhneLabelGenerator(GruppenErgebnistabelleSpaltenSchluessel spaltenSchluessel) {
    return null; // Spaltenbezeichnung wird später anhand des Labelgenerators bestimmt.
  }

  private StimmeingabeGruppeStimmenErfassenLabelGenerator getStimmeingabeGruppeStimmenErfassenLabelGenerator(ErgebnisDaten ergebnisDaten) {
    return stimmeingabeGruppeStimmenErfassenLabelGeneratorFinder.getStimmeingabeGruppeStimmenErfassenLabelGeneratorFuerWahl(ergebnisDaten.getWahl());
  }
}
