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

import java.io.Serializable;
import java.util.List;
import java.util.Map;

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

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

import de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.entity.SystemGruppe;
import de.ivu.elect.business.gruppe.entity.SystemGruppeEigenschaften;
import de.ivu.elect.business.gruppeimgebiet.boundary.AbstractGruppeImGebietDAO;
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.kandidatur.entity.ListenkandidaturStimmzettelPosition;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingang;
import de.ivu.elect.business.stimmeingabe.entity.Stimmeingabe;
import de.ivu.elect.business.stimmeingabe.entity.ZwischensummeEingabe;
import de.ivu.elect.business.stimmzettel.boundary.EntityAufStimmzettelDatenDAOFactory;
import de.ivu.elect.business.stimmzettel.entity.EntityAufStimmzettelDaten;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;
import de.ivu.elect.business.wahlvorschlag.entity.WahlvorschlagStimmzettelPosition;
import de.ivu.elect.jee.performance.PerformanceLog;
import de.ivu.elect.jee.performance.PerformanceLogTransactionHandling;
import de.ivu.elect.jee.performance.ViewType;


import static de.ivu.elect.jee.entity.EntityUtil.castTo;
import static java.util.stream.Collectors.groupingBy;

@PerformanceLog(viewType = ViewType.STIMMENEINGABE, transactionHandling = PerformanceLogTransactionHandling.IMMEDIATELY)
public class ErgebniseingangEingabeHashCodeGenerator implements ErgebniseingangEingabeHashCodeCalculator, Serializable {

  private static final long serialVersionUID = 1L;

  private static final Logger LOG = LoggerFactory.getLogger(ErgebniseingangEingabeHashCodeGenerator.class);

  @Inject
  private transient EntityAufStimmzettelDatenDAOFactory entityAufStimmzettelDatenDAOFactory;

  @Inject
  private transient GruppeImGebietDAO gruppeImGebietDAO;

  @Inject
  private transient SystemGruppeEigenschaften systemGruppeEigenschaften;

  private final ErgebniseingangEingabeHashCodeCalculator ergebniseingangEingabeHashCodeCalculator;

  public ErgebniseingangEingabeHashCodeGenerator() {
    super();

    this.ergebniseingangEingabeHashCodeCalculator = new ErgebniseingangEingabeHashCodeCalculatorByStimmenartAndAnzahlPerWahlvorschlagStimmzettelPosition();
  }

  public String calculateHashCode(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Ergebniseingang ergebniseingang) {
    try {
      return calculateHashCode(collectStimmeingabenPerStimmenart(wahldurchfuehrungsgebiet, ergebniseingang));
    } catch (Exception ex) {
      LOG.error("FAILED to calculate hash [ergebniseingang = " + ergebniseingang.getId() + ", wahl = " + wahldurchfuehrungsgebiet.getWahldurchfuehrung().getName() + ", gebiet = "
          + wahldurchfuehrungsgebiet.getNummer() + ", ergebniseingangsart = " + ergebniseingang.getErgebniseingangsart() + "]", ex);
    }

    return "";
  }

  @Override
  public String calculateHashCode(Map<Stimmenart, List<StimmeingabeIndiziert>> stimmeingabenPerStimmenart) {
    return ergebniseingangEingabeHashCodeCalculator.calculateHashCode(stimmeingabenPerStimmenart);
  }

  @Override
  public String calculateHashCodeSearchPatternByNumberOfGruppen(Map<Stimmenart, List<StimmeingabeIndiziert>> stimmeingabenPerStimmenart, String wildcardCharacter, long numberOfGruppen) {
    return ergebniseingangEingabeHashCodeCalculator.calculateHashCodeSearchPatternByNumberOfGruppen(stimmeingabenPerStimmenart, wildcardCharacter, numberOfGruppen);
  }

  @Override
  public List<StimmeingabeIndiziert> sortByHashCodeOrder(List<StimmeingabeIndiziert> stimmeingaben) {
    return ergebniseingangEingabeHashCodeCalculator.sortByHashCodeOrder(stimmeingaben);
  }

  public Map<Stimmenart, List<StimmeingabeIndiziert>> collectStimmeingabenPerStimmenart(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Ergebniseingang ergebniseingang) {
    return collectStimmeingabenPerStimmenart(wahldurchfuehrungsgebiet, ergebniseingang, gruppeImGebietDAO, systemGruppeEigenschaften,
        entityAufStimmzettelDatenDAOFactory.getEntityAufStimmzettelDaten());
  }

  private Map<Stimmenart, List<StimmeingabeIndiziert>> collectStimmeingabenPerStimmenart(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Ergebniseingang ergebniseingang,
      AbstractGruppeImGebietDAO gruppeImGebietDAO, SystemGruppeEigenschaften systemGruppeEigenschaften, EntityAufStimmzettelDaten entityAufStimmzettelDaten) {
    return StimmeingabeIndiziert.createPerStimmenart(wahldurchfuehrungsgebiet, ergebniseingang, gruppeImGebietDAO, systemGruppeEigenschaften, entityAufStimmzettelDaten);
  }

  public static class StimmeingabeIndiziert {

    private final Stimmenart stimmenart;

    private final long anzahl;

    private final boolean systemGruppeStimmeingabe;

    private final SystemGruppe systemGruppe;

    private final boolean wahlvorschlagStimmeingabe;

    private final Long wahlvorschlagStimmzettelPosition;

    private final boolean listenkandidaturStimmeingabe;

    private final Long listenkandidaturStimmzettelPosition;

    private final boolean zwischensummeEingabe;

    private final Long zwischensummeIndex;

    private StimmeingabeIndiziert(Stimmeingabe template, SystemGruppe systemGruppe, boolean zwischensummeEingabe, Long zwischensummeIndex) {
      this(template, systemGruppe, false, null, false, null, zwischensummeEingabe, zwischensummeIndex);
    }

    private StimmeingabeIndiziert(Stimmeingabe template, boolean wahlvorschlagStimmeingabe, @Nullable Long wahlvorschlagStimmzettelPosition, boolean listenkandidaturStimmeingabe,
        @Nullable Long listenkandidaturStimmzettelPosition, boolean zwischensummeEingabe, Long zwischensummeIndex) {
      this(template, null, wahlvorschlagStimmeingabe, wahlvorschlagStimmzettelPosition, listenkandidaturStimmeingabe, listenkandidaturStimmzettelPosition, zwischensummeEingabe, zwischensummeIndex);
    }

    private StimmeingabeIndiziert(Stimmeingabe template, @Nullable SystemGruppe systemGruppe, boolean wahlvorschlagStimmeingabe, @Nullable Long wahlvorschlagStimmzettelPosition,
        boolean listenkandidaturStimmeingabe, @Nullable Long listenkandidaturStimmzettelPosition, boolean zwischensummeEingabe, Long zwischensummeIndex) {
      super();

      this.stimmenart = template.getStimmenart();
      this.anzahl = template.getAnzahl();

      this.systemGruppeStimmeingabe = systemGruppe != null;
      this.systemGruppe = systemGruppe;

      this.wahlvorschlagStimmeingabe = wahlvorschlagStimmeingabe;
      this.wahlvorschlagStimmzettelPosition = wahlvorschlagStimmzettelPosition;

      this.listenkandidaturStimmeingabe = listenkandidaturStimmeingabe;
      this.listenkandidaturStimmzettelPosition = listenkandidaturStimmzettelPosition;

      this.zwischensummeEingabe = zwischensummeEingabe;
      this.zwischensummeIndex = zwischensummeIndex;
    }

    public Stimmenart getStimmenart() {
      return stimmenart;
    }

    public long getAnzahl() {
      return anzahl;
    }

    public boolean isSystemGruppeStimmeingabe() {
      return systemGruppeStimmeingabe;
    }

    public boolean equalsSystemGruppe(SystemGruppe another) {
      return systemGruppe != null && systemGruppe.equals(another);
    }

    public boolean isWahlvorschlagStimmeingabe() {
      return wahlvorschlagStimmeingabe;
    }

    public boolean hasWahlvorschlagStimmzettelPosition() {
      return getWahlvorschlagStimmzettelPosition() != null;
    }

    public Long getWahlvorschlagStimmzettelPosition() {
      return wahlvorschlagStimmzettelPosition;
    }

    public boolean isListenkandidaturStimmeingabe() {
      return listenkandidaturStimmeingabe;
    }

    public boolean hasListenkandidaturStimmzettelPosition() {
      return getListenkandidaturStimmzettelPosition() != null;
    }

    public Long getListenkandidaturStimmzettelPosition() {
      return listenkandidaturStimmzettelPosition;
    }

    public boolean isZwischensummeEingabe() {
      return zwischensummeEingabe;
    }

    public boolean equalsZwischensummeIndex(Long another) {
      return zwischensummeIndex != null && zwischensummeIndex.equals(another);
    }

    public static Map<Stimmenart, List<StimmeingabeIndiziert>> createPerStimmenart(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Ergebniseingang ergebniseingang,
        AbstractGruppeImGebietDAO gruppeImGebietDAO, SystemGruppeEigenschaften systemGruppeEigenschaften, EntityAufStimmzettelDaten entityAufStimmzettelDaten) {
      return ergebniseingang.getStimmeingaben().stream() //
          .map(stimmeingabe -> StimmeingabeIndiziert.create(wahldurchfuehrungsgebiet, gruppeImGebietDAO.attach(stimmeingabe.getGruppeImGebiet()), stimmeingabe, systemGruppeEigenschaften,
              entityAufStimmzettelDaten)) //
          .collect(groupingBy(StimmeingabeIndiziert::getStimmenart));
    }

    private static StimmeingabeIndiziert create(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, GruppeImGebiet gruppeImGebiet, Stimmeingabe template,
        SystemGruppeEigenschaften systemGruppeEigenschaften, EntityAufStimmzettelDaten entityAufStimmzettelDaten) {
      boolean zwischensummeEingabe = template.isZwischensummeEingabe();
      Long zwischensummeIndex = null;

      if (zwischensummeEingabe) {
        zwischensummeIndex = castTo(template, ZwischensummeEingabe.class).getSummenIndex();
      }

      if (gruppeImGebiet.isSystemGruppe()) {
        return new StimmeingabeIndiziert(template, gruppeImGebiet.getGruppe().getSystemGruppe(systemGruppeEigenschaften), zwischensummeEingabe, zwischensummeIndex);
      }

      boolean listenkandidaturStimmeingabe = template.hasListenkandidatur();
      ListenkandidaturStimmzettelPosition listenkandidaturStimmzettelPosition = null;

      boolean wahlvorschlagStimmeingabe = !listenkandidaturStimmeingabe && gruppeImGebiet.isPolitischeGruppe();
      WahlvorschlagStimmzettelPosition wahlvorschlagStimmzettelPosition = null;

      if (gruppeImGebiet.isPolitischeGruppe()) {
        if (listenkandidaturStimmeingabe) {
          Listenkandidatur listenkandidatur = template.getListenkandidatur();

          if (listenkandidatur != null) {
            listenkandidaturStimmzettelPosition = listenkandidatur.getStimmzettelPosition(wahldurchfuehrungsgebiet, entityAufStimmzettelDaten);
          }
        }

        Wahlvorschlag wahlvorschlag = gruppeImGebiet.getErstenWahlvorschlagAbgeleitet(entityAufStimmzettelDaten);

        if (wahlvorschlag != null) {
          wahlvorschlagStimmzettelPosition = wahlvorschlag.getStimmzettelPosition(wahldurchfuehrungsgebiet, entityAufStimmzettelDaten);
        }
      }

      return new StimmeingabeIndiziert(template, wahlvorschlagStimmeingabe, wahlvorschlagStimmzettelPosition == null ? null : wahlvorschlagStimmzettelPosition.getPosition(),
          listenkandidaturStimmeingabe, listenkandidaturStimmzettelPosition == null ? null : listenkandidaturStimmzettelPosition.getPosition(), zwischensummeEingabe, zwischensummeIndex);
    }
  }
}
