
package de.ivu.elect.core.exchange.stimmen.csv;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.inject.Inject;

import de.ivu.elect.business.datei.entity.FileType;
import de.ivu.elect.business.ergebnis.control.StimmenImportContext;
import de.ivu.elect.business.ergebnis.entity.Stimmen;
import de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.exchange.control.FileFormat;
import de.ivu.elect.business.exchange.control.FileKonfiguration;
import de.ivu.elect.business.gebietsbaum.boundary.GebietFactory;
import de.ivu.elect.business.gebietsbaum.boundary.GebietsartCreator;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Gebietsart;
import de.ivu.elect.business.gruppe.boundary.GruppeFactory;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.SystemGruppe;
import de.ivu.elect.business.gruppe.entity.SystemGruppeEigenschaften;
import de.ivu.elect.business.gruppeimgebiet.boundary.GruppeImGebietFactory;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;
import de.ivu.elect.business.wahl.boundary.WahlDAO;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.core.exchange.ergebnis.StimmenImportMapper;
import de.ivu.elect.exchange.CsvImportMapper;
import de.ivu.elect.exchange.csv.CsvMarshaller;
import de.ivu.elect.jee.exchange.Violations;

public abstract class AbstractStimmenCsvImportMapper<T extends StimmenCsvDTO> implements StimmenImportMapper, CsvImportMapper<List<Stimmen>, StimmenImportContext> {

  protected static final Long ZERO = 0L;

  @Inject
  private transient GruppeFactory gruppeFactory;

  @Inject
  private transient GebietFactory gebietFactory;

  @Inject
  private transient SystemGruppeEigenschaften systemGruppeEigenschaften;

  @Inject
  private transient GruppeImGebietFactory gruppeImGebietFactory;

  @Inject
  private transient GebietsartCreator gebietsartCreator;

  @Inject
  private transient WahlDAO wahlDAO;

  protected AbstractStimmenCsvImportMapper() {
    super();
  }

  @Override
  public List<Stimmen> deserialize(InputStream is, StimmenImportContext context, FileKonfiguration fileKonfiguration) {
    return mappeEntityExternToIntern(getCsvMarshaller().deserialize(getStimmenCsvDTOClass(), is, fileKonfiguration), context);
  }

  protected abstract CsvMarshaller getCsvMarshaller();

  protected abstract Class<T> getStimmenCsvDTOClass();

  @Override
  public Violations validateInputFormat(InputStream is, StimmenImportContext context, FileKonfiguration fileKonfiguration) {
    Violations violations = new Violations();

    List<T> deserialize = getCsvMarshaller().deserialize(getStimmenCsvDTOClass(), is, fileKonfiguration);
    for (T importStimmen : deserialize) {
      if (!isGebietErfassungsgebiet(importStimmen, context)) {
        violations.addWarning("stimmen.import.error.gebiet.kein.erfassungsgebiet", importStimmen.getGebietsart(), importStimmen.getGebietsnummer());
      }
    }

    return violations;
  }

  private boolean isGebietErfassungsgebiet(T importStimmen, StimmenImportContext context) {
    String gebietsartImport = importStimmen.getGebietsart();
    Wahl wahl = wahlDAO.attach(context.getWahl());
    for (Gebietsart gebietsartErfassungsgebiet : wahl.getWahlart().getGebietsartenDerErfassungsgebiete()) {
      if (gebietsartImport.equals(gebietsartErfassungsgebiet.getName())) {
        return true;
      }
    }
    return false;
  }

  @Override
  public FileFormat getFileFormat() {
    return new FileFormat(getFormatName(), FileType.CSV);
  }

  protected abstract String getFormatName();

  @Override
  public boolean isAcceptContext(StimmenImportContext context) {
    return true;
  }

  @Override
  public List<String> getCsvColumnHeaders(StimmenImportContext context) {
    return getCsvMarshaller().extractHeader(getStimmenCsvDTOClass());
  }

  protected List<Stimmen> mappeEntityExternToIntern(List<T> deserialize, StimmenImportContext context) {

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

    for (T importStimmen : deserialize) {
      if (importStimmen != null) {
        if (isGebietErfassungsgebiet(importStimmen, context)) {
          result.addAll(createStimmenForSystemGruppen(context, importStimmen));
          result.addAll(createStimmenForPolitischeGruppen(context, importStimmen));
        }
      }
    }

    return result;
  }

  protected Gruppe createSystemGruppeGueltige() {
    return createSystemGruppe(SystemGruppe.GUELTIGE);
  }

  protected Gruppe createSystemGruppeUngueltige() {
    return createSystemGruppe(SystemGruppe.UNGUELTIGE);
  }

  private Gruppe createSystemGruppe(SystemGruppe systemGruppe) {
    return systemGruppeEigenschaften.createGruppeAusSystemGruppe(systemGruppe, null);
  }

  protected List<Stimmen> createStimmenForSystemGruppen(StimmenImportContext context, T importStimmen) {

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

    Gebiet gebiet = createGebiet(context, importStimmen.getGebietsart(), importStimmen.getGebietsnummer());

    addAnzahlenWahlberechtigte(result, gebiet, importStimmen);
    addAnzahlenWaehler(result, gebiet, importStimmen);
    addGueltigeStimmen(result, gebiet, importStimmen);
    addUngueltigeStimmen(result, gebiet, importStimmen);

    return result;
  }

  protected void addAnzahlenWahlberechtigte(List<Stimmen> result, Gebiet gebiet, T importStimmen) {
    Gruppe gruppe = systemGruppeEigenschaften.createGruppeAusSystemGruppe(SystemGruppe.WAHLBERECHTIGTE, null);

    GruppeImGebiet gruppeImGebietWahlberechtigte = createGruppeImGebietFor(gebiet, gruppe);

    if (importStimmen.getAnzahlWahlberechtigteOhneWahlschein() != null) {
      Stimmen anzahlWahlberechtigteOhneWahlschein = new Stimmen();
      anzahlWahlberechtigteOhneWahlschein.setStimmenart(Stimmenart.OHNE_WAHLSCHEIN);
      anzahlWahlberechtigteOhneWahlschein.setGruppe(gruppeImGebietWahlberechtigte.getGruppe());
      anzahlWahlberechtigteOhneWahlschein.setGebiet(gruppeImGebietWahlberechtigte.getGebiet());
      anzahlWahlberechtigteOhneWahlschein.setAnzahl(importStimmen.getAnzahlWahlberechtigteOhneWahlschein());

      result.add(anzahlWahlberechtigteOhneWahlschein);
    }

    if (importStimmen.getAnzahlWahlberechtigteMitWahlschein() != null) {
      Stimmen anzahlWahlberechtigteMitWahlschein = new Stimmen();
      anzahlWahlberechtigteMitWahlschein.setStimmenart(Stimmenart.MIT_WAHLSCHEIN);
      anzahlWahlberechtigteMitWahlschein.setGruppe(gruppeImGebietWahlberechtigte.getGruppe());
      anzahlWahlberechtigteMitWahlschein.setGebiet(gruppeImGebietWahlberechtigte.getGebiet());
      anzahlWahlberechtigteMitWahlschein.setAnzahl(importStimmen.getAnzahlWahlberechtigteMitWahlschein());

      result.add(anzahlWahlberechtigteMitWahlschein);
    }

    if (importStimmen.getAnzahlWahlberechtigteNichtImWaehlerverzeichnis() != null) {
      Stimmen anzahlWahlberechtigteNichtImWaehlerverzeichnis = new Stimmen();
      anzahlWahlberechtigteNichtImWaehlerverzeichnis.setStimmenart(Stimmenart.NICHT_IM_WAEHLERVERZEICHNIS);
      anzahlWahlberechtigteNichtImWaehlerverzeichnis.setGruppe(gruppeImGebietWahlberechtigte.getGruppe());
      anzahlWahlberechtigteNichtImWaehlerverzeichnis.setGebiet(gruppeImGebietWahlberechtigte.getGebiet());
      anzahlWahlberechtigteNichtImWaehlerverzeichnis.setAnzahl(importStimmen.getAnzahlWahlberechtigteNichtImWaehlerverzeichnis());

      result.add(anzahlWahlberechtigteNichtImWaehlerverzeichnis);
    }

    if (importStimmen.getAnzahlWahlberechtigteOhneWahlschein() != null || importStimmen.getAnzahlWahlberechtigteMitWahlschein() != null
        || importStimmen.getAnzahlWahlberechtigteNichtImWaehlerverzeichnis() != null) {
      Stimmen anzahlWahlberechtigteGesamt = new Stimmen();
      anzahlWahlberechtigteGesamt.setStimmenart(Stimmenart.GESAMT);
      anzahlWahlberechtigteGesamt.setGruppe(gruppeImGebietWahlberechtigte.getGruppe());
      anzahlWahlberechtigteGesamt.setGebiet(gruppeImGebietWahlberechtigte.getGebiet());
      anzahlWahlberechtigteGesamt.setAnzahl(notNull(importStimmen.getAnzahlWahlberechtigteOhneWahlschein()) + notNull(importStimmen.getAnzahlWahlberechtigteMitWahlschein())
          + notNull(importStimmen.getAnzahlWahlberechtigteNichtImWaehlerverzeichnis()));

      result.add(anzahlWahlberechtigteGesamt);
    }
  }

  private Long notNull(Long anzahl) {
    if (anzahl == null) {
      return ZERO;
    }
    return anzahl;
  }

  protected void addAnzahlenWaehler(List<Stimmen> result, Gebiet gebiet, T importStimmen) {
    Gruppe gruppe = systemGruppeEigenschaften.createGruppeAusSystemGruppe(SystemGruppe.WAEHLER, null);

    GruppeImGebiet gruppeImGebietWaehler = createGruppeImGebietFor(gebiet, gruppe);

    if (importStimmen.getAnzahlWaehlerOhneWahlschein() != null) {
      Stimmen anzahlWaehlerOhneWahlschein = new Stimmen();
      anzahlWaehlerOhneWahlschein.setStimmenart(Stimmenart.OHNE_WAHLSCHEIN);
      anzahlWaehlerOhneWahlschein.setGruppe(gruppeImGebietWaehler.getGruppe());
      anzahlWaehlerOhneWahlschein.setGebiet(gruppeImGebietWaehler.getGebiet());
      anzahlWaehlerOhneWahlschein.setAnzahl(importStimmen.getAnzahlWaehlerOhneWahlschein());

      result.add(anzahlWaehlerOhneWahlschein);
    }

    if (importStimmen.getAnzahlWaehlerMitWahlschein() != null) {
      Stimmen anzahlWaehlerMitWahlschein = new Stimmen();
      anzahlWaehlerMitWahlschein.setStimmenart(Stimmenart.MIT_WAHLSCHEIN);
      anzahlWaehlerMitWahlschein.setGruppe(gruppeImGebietWaehler.getGruppe());
      anzahlWaehlerMitWahlschein.setGebiet(gruppeImGebietWaehler.getGebiet());
      anzahlWaehlerMitWahlschein.setAnzahl(importStimmen.getAnzahlWaehlerMitWahlschein());

      result.add(anzahlWaehlerMitWahlschein);
    }

    if (importStimmen.getAnzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl() != null) {
      Stimmen anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl = new Stimmen();
      anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl.setStimmenart(Stimmenart.ALS_URNENWAHL_IN_URNE);
      anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl.setGruppe(gruppeImGebietWaehler.getGruppe());
      anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl.setGebiet(gruppeImGebietWaehler.getGebiet());
      anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl.setAnzahl(importStimmen.getAnzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl());

      result.add(anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusUrnenwahl);
    }

    if (importStimmen.getAnzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl() != null) {
      Stimmen anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl = new Stimmen();
      anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl.setStimmenart(Stimmenart.ALS_BRIEFWAHL_IN_URNE);
      anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl.setGruppe(gruppeImGebietWaehler.getGruppe());
      anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl.setGebiet(gruppeImGebietWaehler.getGebiet());
      anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl.setAnzahl(importStimmen.getAnzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl());

      result.add(anzahlWaehlerMitWahlscheinInUrnenwahlAnzahlAusBriefwahl);
    }

    if (importStimmen.getAnzahlWaehlerOhneWahlschein() != null || importStimmen.getAnzahlWaehlerMitWahlschein() != null) {
      Stimmen anzahlWaehlerGesamt = new Stimmen();
      anzahlWaehlerGesamt.setStimmenart(Stimmenart.GESAMT);
      anzahlWaehlerGesamt.setGruppe(gruppeImGebietWaehler.getGruppe());
      anzahlWaehlerGesamt.setGebiet(gruppeImGebietWaehler.getGebiet());
      anzahlWaehlerGesamt.setAnzahl(notNull(importStimmen.getAnzahlWaehlerOhneWahlschein()) + notNull(importStimmen.getAnzahlWaehlerMitWahlschein()));

      result.add(anzahlWaehlerGesamt);
    }
  }

  protected abstract void addGueltigeStimmen(List<Stimmen> result, Gebiet gebiet, T importStimmen);

  protected abstract void addUngueltigeStimmen(List<Stimmen> result, Gebiet gebiet, T importStimmen);

  protected abstract Collection<Stimmen> createStimmenForPolitischeGruppen(StimmenImportContext context, T importStimmen);

  protected Stimmen createStimmen(Long anzahl, Stimmenart stimmenart, GruppeImGebiet gruppeImGebiet) {
    Stimmen stimmen = new Stimmen();
    stimmen.setGruppe(gruppeImGebiet.getGruppe());
    stimmen.setGebiet(gruppeImGebiet.getGebiet());
    stimmen.setAnzahl(anzahl);
    stimmen.setStimmenart(stimmenart);
    return stimmen;
  }

  protected GruppeImGebiet mappeGruppeImGebietForGebiet(StimmenImportContext context, String gebietsart, String gebietsnummer, String gruppeSchluessel) {
    Gebiet gebiet = createGebiet(context, gebietsart, gebietsnummer);
    Gruppe gruppe = createGruppe(gruppeSchluessel);

    return createGruppeImGebietFor(gebiet, gruppe);
  }

  protected Gruppe createGruppe(String gruppeSchluessel) {
    Gruppe gruppe = gruppeFactory.create();
    gruppe.setSchluessel(gruppeSchluessel);
    return gruppe;
  }

  protected Gebiet createGebiet(StimmenImportContext context, String gebietsart, String gebietsnummer) {
    Gebiet gebiet = gebietFactory.create();

    gebiet.setGebietsart(Gebietsart.createGebietsartFromTemplate(gebietsartCreator.findByName(context.getMandant(), gebietsart)));
    gebiet.setNummer(gebietsnummer);

    return gebiet;
  }

  protected GruppeImGebiet createGruppeImGebietFor(Gebiet gebiet, Gruppe gruppe) {
    GruppeImGebiet gruppeImGebietUngueltige = gruppeImGebietFactory.create();
    gruppeImGebietUngueltige.setGebiet(gebiet);
    gruppeImGebietUngueltige.setGruppe(gruppe);
    return gruppeImGebietUngueltige;
  }
}
