
package de.ivu.elect.business.ergebnis.control;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.persistence.NoResultException;

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

import de.ivu.elect.business.ergebnis.boundary.StimmenDAO;
import de.ivu.elect.business.ergebnis.entity.Ergebnis;
import de.ivu.elect.business.ergebnis.entity.Stimmen;
import de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.gebietsbaum.boundary.WahldurchfuehrungsgebietDAO;
import de.ivu.elect.business.gebietsbaum.entity.EntityMitGebiet;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.boundary.GruppeDAO;
import de.ivu.elect.business.gruppe.entity.EntityMitGruppe;
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.GruppeImGebietDAO;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.stimmeingabe.boundary.ErgebnisGueltigerEingangDAO;
import de.ivu.elect.business.stimmeingabe.boundary.ErgebnisGueltigerEingangFactory;
import de.ivu.elect.business.stimmeingabe.boundary.ErgebniseingangDAO;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingang;
import de.ivu.elect.business.stimmeingabe.entity.Stimmeingabe;
import de.ivu.elect.business.wahl.entity.WahlParameterLoader;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;

public abstract class AbstractStimmeingabeErgebnisuebernahme {

  protected static final String HEADER = "Zeitpunkt;Wahl;Gebiet;Liste Kennwort;Liste Kurzname;Listenposition;Kandidat-Familienname;Kandidat-Vorname;Anzahl Stimmen vorher";

  @Inject
  protected transient WahlParameterLoader wahlParameterLoader;

  @Inject
  protected transient GruppeDAO gruppeDAO;

  @Inject
  protected transient GruppeImGebietDAO gruppeImGebietDAO;

  @Inject
  protected transient ErgebniseingangDAO ergebniseingangDAO;

  @Inject
  protected transient StimmenDAO stimmenDAO;

  @Inject
  protected transient WahldurchfuehrungsgebietDAO wahldurchfuehrungsgebietDAO;

  @Inject
  protected transient ErgebnisGueltigerEingangDAO ergebnisGueltigerEingangDAO;

  @Inject
  protected transient ErgebnisGueltigerEingangFactory ergebnisGueltigerEingangFactory;

  @Inject
  protected transient SystemGruppeEigenschaften systemGruppeEigenschaften;

  private final Logger log;

  protected AbstractStimmeingabeErgebnisuebernahme() {
    super();

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

  protected abstract void processStimmenKonvertiert(Ergebnis ergebnis, Ergebniseingang ergebniseingang, Collection<Stimmen> stimmenKonvertiert);

  protected Map<Gruppe, Collection<Stimmen>> collectStimmenPerPolitischeGruppe(Collection<Stimmen> alleStimmen) {
    Map<Gruppe, Collection<Stimmen>> result = new HashMap<>();

    for (Stimmen stimmen : alleStimmen) {
      Gruppe gruppe = stimmen.getGruppe();

      if (gruppe.isPolitischeGruppe()) {
        result.computeIfAbsent(gruppe, x -> new ArrayList<>()).add(stimmen);
      }
    }

    return result;
  }

  protected Liste extractListe(Ergebnis ergebnis, Ergebniseingang ergebniseingang, Gruppe gruppe) {
    return attachGruppeImGebiet(ergebnis, ergebniseingang, gruppe).getListeAufStimmzettelInUebergeordneterGebietshierarchie();
  }

  protected GruppeImGebiet attachGruppeImGebiet(Ergebnis ergebnis, Ergebniseingang ergebniseingang, Gruppe gruppe) {
    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = ergebniseingang.getWahldurchfuehrungsgebiet();

    try {
      return gruppeImGebietDAO.findByGruppeAndGebiet(gruppe, wahldurchfuehrungsgebiet);
    } catch (NoResultException ignored) {
    }

    throw new IllegalStateException(
        "Keine GruppeImGebiet fuer Gruppe gefunden [ergebnis = " + ergebnis + ", wahl = " + ergebnis.getWahl() + ", gebiet = " + wahldurchfuehrungsgebiet + ", gruppe = " + gruppe + "]");
  }

  /**
   * @return aus den stimmenGruppe das eindeutige Stimmen-Objekt, das eine LISTENSTIMME ist aber nicht zu einer Listenkandidatur gehört. <code>null</code>, falls es keinen oder mehrere davon gibt.
   */
  @Nullable
  protected Stimmen extractStimmenGruppeListenstimme(Collection<Stimmen> stimmenGruppe) {
    Stimmen result = null;

    for (Stimmen stimmen : stimmenGruppe) {
      if (!stimmen.isListenkandidaturStimme() && stimmen.isListenstimme()) {
        if (result != null) {
          // listenStimmenGruppe ist nicht eindeutig, deshalb nichts ändern
          return null;
        } else {
          result = stimmen;
        }
      }
    }

    return result;
  }

  protected Stimmen createStimmen(Ergebnis ergebnis, Stimmeingabe stimmeingabe) {
    GruppeImGebiet gruppeImGebiet = stimmeingabe.getGruppeImGebiet();
    long anzahl = stimmeingabe.hasAnzahlGroesserGleichNull() ? stimmeingabe.getAnzahl() : 0L;

    Stimmen stimmen = createStimmen(ergebnis, gruppeImGebiet.getGebiet(), gruppeImGebiet.getGruppe(), stimmeingabe.getListenkandidatur(), stimmeingabe.getStimmenart(), anzahl);
    stimmen.setReferendumOption(stimmeingabe.getReferendumOption());

    return stimmen;
  }

  protected Stimmen createStimmen(Ergebnis ergebnis, Gebiet gebiet, Gruppe gruppe, Stimmenart stimmenart, Long anzahl) {
    return createStimmen(ergebnis, gebiet, gruppe, null, stimmenart, anzahl);
  }

  protected Stimmen createStimmen(Ergebnis ergebnis, Gebiet gebiet, Gruppe gruppe, Listenkandidatur listenkandidatur, Stimmenart stimmenart, Long anzahl) {
    Stimmen stimmen = new Stimmen();
    stimmen.setErgebnis(ergebnis);
    stimmen.setGebiet(gebiet);
    stimmen.setGruppe(gruppe);
    stimmen.setListenkandidatur(listenkandidatur);
    stimmen.setAnzahl(anzahl == null ? 0L : anzahl);
    stimmen.setStimmenart(stimmenart);

    return stimmen;
  }

  protected boolean isUrnenwahl(EntityMitGebiet entityMitGebiet) {
    return entityMitGebiet != null && entityMitGebiet.getGebiet().isUrnenwahlgebiet();
  }

  protected boolean isBriefwahl(EntityMitGebiet entityMitGebiet) {
    return entityMitGebiet != null && entityMitGebiet.getGebiet().isBriefwahlgebiet();
  }

  protected boolean isWaehlerOhneWahlschein(Stimmen stimmen) {
    return isWaehler(stimmen) && Stimmenart.OHNE_WAHLSCHEIN.equals(stimmen.getStimmenart());
  }

  protected boolean isWaehlerOhneWahlschein(Stimmeingabe stimmeingabe) {
    return isWaehler(stimmeingabe) && Stimmenart.OHNE_WAHLSCHEIN.equals(stimmeingabe.getStimmenart());
  }

  protected boolean isWaehlerMitWahlschein(Stimmen stimmen) {
    return isWaehler(stimmen) && Stimmenart.MIT_WAHLSCHEIN.equals(stimmen.getStimmenart());
  }

  protected boolean isWaehlerMitWahlschein(Stimmeingabe stimmeingabe) {
    return isWaehler(stimmeingabe) && Stimmenart.MIT_WAHLSCHEIN.equals(stimmeingabe.getStimmenart());
  }

  protected boolean isWaehlerGesamt(Stimmen stimmen) {
    return isWaehler(stimmen) && Stimmenart.GESAMT.equals(stimmen.getStimmenart());
  }

  protected boolean isWaehlerGesamt(Stimmeingabe stimmeingabe) {
    return isWaehler(stimmeingabe) && Stimmenart.GESAMT.equals(stimmeingabe.getStimmenart());
  }

  protected boolean isWahlberechtigteOhneWahlschein(Stimmen stimmen) {
    return isWahlberechtigte(stimmen) && Stimmenart.OHNE_WAHLSCHEIN.equals(stimmen.getStimmenart());
  }

  protected boolean isWahlberechtigteOhneWahlschein(Stimmeingabe stimmeingabe) {
    return isWahlberechtigte(stimmeingabe) && Stimmenart.OHNE_WAHLSCHEIN.equals(stimmeingabe.getStimmenart());
  }

  protected boolean isWahlberechtigteMitWahlschein(Stimmen stimmen) {
    return isWahlberechtigte(stimmen) && Stimmenart.MIT_WAHLSCHEIN.equals(stimmen.getStimmenart());
  }

  protected boolean isWahlberechtigteMitWahlschein(Stimmeingabe stimmeingabe) {
    return isWahlberechtigte(stimmeingabe) && Stimmenart.MIT_WAHLSCHEIN.equals(stimmeingabe.getStimmenart());
  }

  protected boolean isWahlberechtigteGesamt(Stimmen stimmen) {
    return isWahlberechtigte(stimmen) && Stimmenart.GESAMT.equals(stimmen.getStimmenart());
  }

  protected boolean isWahlberechtigteGesamt(Stimmeingabe stimmeingabe) {
    return isWahlberechtigte(stimmeingabe) && Stimmenart.GESAMT.equals(stimmeingabe.getStimmenart());
  }

  protected boolean isWaehler(EntityMitGruppe<Gruppe> entityMitGruppe) {
    return entityMitGruppe != null && isWaehler(entityMitGruppe.getGruppe());
  }

  protected boolean isWaehler(Gruppe gruppe) {
    return isSystemGruppe(gruppe, SystemGruppe.WAEHLER);
  }

  protected boolean isWahlberechtigte(EntityMitGruppe<Gruppe> entityMitGruppe) {
    return entityMitGruppe != null && isWahlberechtigte(entityMitGruppe.getGruppe());
  }

  protected boolean isWahlberechtigte(Gruppe gruppe) {
    return isSystemGruppe(gruppe, SystemGruppe.WAHLBERECHTIGTE);
  }

  protected boolean isGueltige(Stimmen stimmen, Stimmenart stimmenart) {
    return stimmen != null && isGueltige(stimmen.getGruppe()) && stimmenart.equals(stimmen.getStimmenart());
  }

  protected boolean isGueltige(Stimmeingabe stimmeingabe, Stimmenart stimmenart) {
    return stimmeingabe != null && isGueltige(stimmeingabe.getGruppe()) && stimmenart.equals(stimmeingabe.getStimmenart());
  }

  protected boolean isGueltige(EntityMitGruppe<Gruppe> entityMitGruppe) {
    return entityMitGruppe != null && isGueltige(entityMitGruppe.getGruppe());
  }

  protected boolean isGueltige(Gruppe gruppe) {
    return isSystemGruppe(gruppe, SystemGruppe.GUELTIGE);
  }

  protected boolean isUngueltige(Stimmen stimmen, Stimmenart stimmenart) {
    return stimmen != null && isUngueltige(stimmen.getGruppe()) && stimmenart.equals(stimmen.getStimmenart());
  }

  protected boolean isUngueltige(Stimmeingabe stimmeingabe, Stimmenart stimmenart) {
    return stimmeingabe != null && isUngueltige(stimmeingabe.getGruppe()) && stimmenart.equals(stimmeingabe.getStimmenart());
  }

  protected boolean isUngueltige(EntityMitGruppe<Gruppe> entityMitGruppe) {
    return entityMitGruppe != null && isUngueltige(entityMitGruppe.getGruppe());
  }

  protected boolean isUngueltige(Gruppe gruppe) {
    return isSystemGruppe(gruppe, SystemGruppe.UNGUELTIGE);
  }

  private boolean isSystemGruppe(Gruppe gruppe, SystemGruppe systemGruppe) {
    return systemGruppeEigenschaften.isGruppeEqualsSystemGruppe(gruppe, systemGruppe);
  }

  protected void info(String msg) {
    log.info(getClass().getSimpleName() + " - " + msg);
  }

  protected void warn(String msg) {
    log.warn(getClass().getSimpleName() + " - " + msg);
  }

  protected void error(String msg, Throwable cause) {
    log.error(getClass().getSimpleName() + " - " + msg, cause);
  }
}
