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

import static de.ivu.elect.business.ergebnis.entity.Stimmenart.DIREKTSTIMME;
import static de.ivu.elect.business.ergebnis.entity.Stimmenart.LISTENSTIMME;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;

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

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.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.Gruppe;
import de.ivu.elect.business.gruppe.entity.SystemGruppe;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;

/**
 *  on 23.03.2016.
 */
@Deprecated // Implementierung zu speziell, de.ivu.elect.business.hochrechnung.boundary.HochrechnungsalgorithmusFinder nutzen
public abstract class AbstractStimmenHochrechner {

  @Inject
  StimmergebnisDatenCreator stimmergebnisDatenCreator;

  @Inject
  GruppeDAO gruppeDAO;

  @Inject
  StimmenDAO stimmenDAO;

  protected void hochrechneStimmenVonErfassungsgebiet(Wahldurchfuehrungsgebiet gebiet,
      StimmenFuerHochrechnung stimmenFuerHochrechnung, StimmergebnisDaten stimmergebnisDaten,
      Ergebnis ergebnisFuerHochrechnung) {

    Gruppe gueltige = stimmergebnisDaten.getGruppeFuerSystemGruppe(SystemGruppe.GUELTIGE);

    List<Wahldurchfuehrungsgebiet> uebergeordneteGebiete = getUebergeordneteGebiete(gebiet);
    for (Stimmenart stimmenart : Arrays.asList(LISTENSTIMME, DIREKTSTIMME)) {
      final Set<Gruppe> gruppenZuZugelassenenWahlvorschlaegen;
      if (stimmenart.isListenstimme()) {
        gruppenZuZugelassenenWahlvorschlaegen = getGruppenZuZugelassenenListenAuchInUebergeordnetenGebieten(gebiet,
            stimmergebnisDaten);
      } else {
        gruppenZuZugelassenenWahlvorschlaegen = getGruppenZuZugelassenenDirektkandidatenAuchInUebergeordnetenGebieten(
            gebiet, stimmergebnisDaten);
      }

      for (Gruppe gruppe : gruppenZuZugelassenenWahlvorschlaegen) {
        Long prognose = getHochgerechneteStimmen(gruppe, gueltige, gebiet, uebergeordneteGebiete, stimmenart,
            stimmenFuerHochrechnung);
        if (prognose != null) {
          createStimmen(ergebnisFuerHochrechnung, prognose, gebiet, gruppe, stimmenart);
        }
      }
    }
  }

  protected abstract @Nullable Long getHochgerechneteStimmen(Gruppe gruppe, Gruppe gueltige,
      Wahldurchfuehrungsgebiet gebiet, List<Wahldurchfuehrungsgebiet> uebergeordneteGebiete, Stimmenart stimmenart,
      StimmenFuerHochrechnung stimmenFuerHochrechnung);

  protected Stimmen createStimmen(Ergebnis ergebnisFuerHochrechnung, Long prognose, Wahldurchfuehrungsgebiet gebiet,
      Gruppe gruppe, Stimmenart stimmenart) {
    Stimmen result = new Stimmen();
    result.setErgebnis(ergebnisFuerHochrechnung);
    result.setGruppe(gruppe);
    result.setGebiet(gebiet);
    result.setAnzahl(prognose);
    result.setStimmenart(stimmenart);

    stimmenDAO.persist(result);

    return result;
  }

  protected Long berechnePrognose(Supplier<Stimmenzaehler> lx, @Nullable Long y, Supplier<Stimmenzaehler> lz) {
    if (y == null) {
      return null;
    }

    Long x = lx.get().get();
    if (x == null) {
      return null;
    }

    Long z = lz.get().get();
    if (z == null || z == 0L) {
      return null;
    }

    return x * y / z;
  }

  /**
   * @return alle Gruppen, die in gebiet oder einem der übergeordneten Gebiete eine zugelassene Liste haben
   */
  protected Set<Gruppe> getGruppenZuZugelassenenListenAuchInUebergeordnetenGebieten(Wahldurchfuehrungsgebiet gebiet,
      StimmergebnisDaten stimmergebnisDaten) {
    Set<Gruppe> result = new HashSet<>();
    for (Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet : getMitUebergeordnetenGebieten(gebiet)) {
      for (Liste liste : stimmergebnisDaten.getZugelasseneListenZuGebiet(wahldurchfuehrungsgebiet)) {
        result.add(liste.getGruppe());
      }
    }

    return result;
  }

  protected Set<Gruppe> getGruppenZuZugelassenenDirektkandidatenAuchInUebergeordnetenGebieten(
      Wahldurchfuehrungsgebiet gebiet, StimmergebnisDaten stimmergebnisDaten) {
    Set<Gruppe> result = new HashSet<>();
    for (Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet : getMitUebergeordnetenGebieten(gebiet)) {
      for (Wahlvorschlag wahlvorschlag : stimmergebnisDaten
          .getZugelasseneWahlvorschlaegeAufGebiet(wahldurchfuehrungsgebiet)) {
        if (wahlvorschlag.isDirektkandidat()) {
          result.add(wahlvorschlag.getGruppe());
        }
      }
    }

    return result;
  }

  protected List<Wahldurchfuehrungsgebiet> getUebergeordneteGebiete(Wahldurchfuehrungsgebiet gebiet) {
    List<Wahldurchfuehrungsgebiet> result = new ArrayList<>();
    Wahldurchfuehrungsgebiet g = gebiet;
    while (true) {
      Gebiet uebergeordnetesGebiet = g.getUebergeordnetesGebiet();
      g = uebergeordnetesGebiet == null
          ? null
          : uebergeordnetesGebiet.castTo(Wahldurchfuehrungsgebiet.class).getEntity();
      if (g == null) {
        return result;
      } else {
        result.add(g);
      }
    }
  }

  List<Wahldurchfuehrungsgebiet> getMitUebergeordnetenGebieten(Wahldurchfuehrungsgebiet gebiet) {
    List<Wahldurchfuehrungsgebiet> result = new ArrayList<>();
    Wahldurchfuehrungsgebiet g = gebiet;
    while (g != null) {
      result.add(g);
      Gebiet uebergeordnetesGebiet = g.getUebergeordnetesGebiet();
      g = uebergeordnetesGebiet == null
          ? null
          : uebergeordnetesGebiet.castTo(Wahldurchfuehrungsgebiet.class).getEntity();
    }
    return result;
  }

}
