
package de.ivu.elect.was.sitzberechnung.control;

import java.math.BigDecimal;
import java.util.List;
import java.util.stream.Collectors;

import javax.inject.Inject;

import org.apache.commons.lang3.math.Fraction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.elect.business.ergebnis.control.StimmergebnisDaten;
import de.ivu.elect.business.gebietsbaum.entity.AbstractGebiet;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;
import de.ivu.elect.business.kandidatur.entity.NlKandidatur;
import de.ivu.elect.business.sitzberechnung.entity.SitzberechnungsalgorithmusKonfiguration;
import de.ivu.elect.business.sitzberechnung.entity.Sitzberechnungseingabe;
import de.ivu.elect.business.system.boundary.SystemLogWriter;
import de.ivu.elect.business.system.entity.SystemLogQuelle;
import de.ivu.elect.business.wahl.entity.AbstractWahlParameterLoader;
import de.ivu.elect.business.wahl.entity.NlWahlCustomData;
import de.ivu.elect.business.wahl.entity.NlWahlart;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.WahlartErgebnisart;
import de.ivu.elect.sitzberechnung.Sitzberechnungsergebnis;
import de.ivu.elect.sitzberechnung.Waehlbarkeit;
import de.ivu.elect.sitzberechnung.osv.exchange.OSVLegacySitzberechnungseingabe;
import de.ivu.elect.sitzberechnung.osv.exchange.control.OSVLegacySitzberechnung;
import de.ivu.elect.was.sitzberechnung.entity.ElectSitzberechnungEntities;

abstract class AbstractOsvLegacySitzberechnungsalgorithmus extends AbstractElectSitzberechnungsalgorithmus<SitzberechnungsalgorithmusKonfiguration>
    implements
    OsvLegacySitzberechnungsalgorithmusMarker {

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

  @Inject
  private transient SystemLogWriter systemLogWriter;

  @Inject
  private transient OsvLegacySitzberechnungsalgorithmusMapper mapper;

  protected AbstractOsvLegacySitzberechnungsalgorithmus() {
    super();
  }

  @Override
  public de.ivu.elect.business.sitzberechnung.entity.Sitzberechnungsergebnis berechneSitzverteilung(Sitzberechnungseingabe<SitzberechnungsalgorithmusKonfiguration> sitzberechnungseingabe) {
    if (isErfassungPhase(sitzberechnungseingabe)) {
      ////ELECT-10800
      LOG.info("Ergebnisberechnung nicht durchgeführt, da in Erfassungsphase"); // ELECT-11919 logging
      return de.ivu.elect.business.sitzberechnung.entity.Sitzberechnungsergebnis.KEIN_ERGEBNIS;
    }
    return super.berechneSitzverteilung(sitzberechnungseingabe);
  }

  private boolean isErfassungPhase(Sitzberechnungseingabe<SitzberechnungsalgorithmusKonfiguration> sitzberechnungseingabe) {
    return sitzberechnungseingabe.getErgebnis().getWahl().getCustomData(NlWahlCustomData.class).getNlWahlPhase().isErfassung();
  }

  @Override
  protected de.ivu.elect.business.sitzberechnung.entity.Sitzberechnungsergebnis berechneSitzverteilung(ElectSitzberechnungContext sitzberechnungContext,
                                                                                                       Sitzberechnungseingabe<SitzberechnungsalgorithmusKonfiguration> sitzberechnungseingabe) {
    try {
      StimmergebnisDaten stimmergebnisDaten = sitzberechnungseingabe.getStimmergebnisDaten();
      List<Wahldurchfuehrungsgebiet> wahlgebiete = stimmergebnisDaten.getGebiete().stream().filter(AbstractGebiet::isWahlgebiet).collect(Collectors.toList());

      if (wahlgebiete.size() != 1) {
        throw new IllegalStateException("Es muss genau ein Wahlgebiet geben: " + wahlgebiete);
      }

      Wahldurchfuehrungsgebiet wahlgebiet = wahlgebiete.get(0);
      ElectSitzberechnungEntities sitzberechnungEntities = mapSitzberechnungEntitiesImWahlgebiet(sitzberechnungContext, wahlgebiet, sitzberechnungseingabe);

      de.ivu.elect.sitzberechnung.Sitzberechnungseingabe eingabe = mapSitzberechnungseingabeExtern(sitzberechnungContext, sitzberechnungEntities,
          sitzberechnungseingabe);

      Fraction preferencialBarrierAlsBruch = getPreferencialBarrierAlsBruch(stimmergebnisDaten.getWahl().getQuorumProzenthuerde(), stimmergebnisDaten.getWahl(),
          stimmergebnisDaten.getWurzelgebiet());

      Sitzberechnungsergebnis sitzberechnungsergebnisExtern = new OSVLegacySitzberechnung(
          createOsveingabedaten(eingabe, stimmergebnisDaten, preferencialBarrierAlsBruch))
          .berechneSitzverteilung(eingabe);

      return mapSitzberechnungsergebnisIntern(sitzberechnungContext, sitzberechnungEntities, sitzberechnungsergebnisExtern);
    } catch (Exception ex) {
      // ELECT-10072
      systemLogWriter.writeErrorLogMessageFuerSystemMandant("Fehler in OSV-Sitzberechnung - Ergebnis: " + sitzberechnungseingabe.getErgebnis(), ex,
          SystemLogQuelle.ZWISCHENERGEBNISBERECHNUNG);
    }

    return de.ivu.elect.business.sitzberechnung.entity.Sitzberechnungsergebnis.createLeeresErgebnis();
  }

  private OSVLegacySitzberechnungseingabe createOsveingabedaten(de.ivu.elect.sitzberechnung.Sitzberechnungseingabe eingabe, StimmergebnisDaten stimmergebnisDaten,
                                                                Fraction preferencialBarrierAlsBruch) {
    return mapper.mappeOsvEingabe(getOSVLegacySitzberechnungWahlart(), eingabe.getListen(), stimmergebnisDaten.getGebiete(),
        preferencialBarrierAlsBruch.getNumerator(),
        preferencialBarrierAlsBruch.getDenominator());
  }

  Fraction getPreferencialBarrierAlsBruch(BigDecimal prozentHuerde, Wahl wahl, Gebiet wurzelGebiet) {
    int numeratorAsInt;
    if (prozentHuerde == null) {

      // ELECTVAPP-968 default aus Wahlart nehmen
      numeratorAsInt = (int) NlWahlart.valueOf(wahl).getPreferencialBarrierNumerator(wurzelGebiet.getAnzahlSitze());
    } else {
      numeratorAsInt = prozentHuerde.intValue();
    }

    return Fraction.getFraction(numeratorAsInt, 100).reduce();
  }

  @Override
  protected ElectErgebnisKonfliktMapper getErgebnisKonfliktMapper(ElectSitzberechnungContext sitzberechnungContext, ElectSitzberechnungEntities sitzberechnungEntities) {
    return mapper.getKonfliktMapper();
  }

  @Override
  protected ElectStaatMapper getStaatMapper() {
    return new ElectStaatMapper() {

      @Override
      public boolean isInlaender(Kandidatur kandidatur) {
        return kandidatur != null && !new NlKandidatur(kandidatur).isAndereStaatsbuergerschaft();
      }
    };
  }

  @Override
  protected Waehlbarkeit getWaehlbarkeit(Kandidatur kandidatur) {
    //ELECT-10336
    if (kandidatur.getKandidaturStatus().isZugelassen()) {
      return Waehlbarkeit.WAEHLBAR;
    }
    return Waehlbarkeit.NICHT_WAEHLBAR;
  }

  @Override
  public SitzberechnungsalgorithmusKonfiguration getDefaultKonfiguration() {
    return new SitzberechnungsalgorithmusKonfiguration();
  }

  @Override
  public boolean isMarkiertGewaehlte(AbstractWahlParameterLoader wahlParameterLoader, Wahl wahl, WahlartErgebnisart ergebnisart) {
    return true;
  }

  @Override
  public boolean isVergibtSitze() {
    return true;
  }
}
