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

import de.ivu.elect.business.ergebnis.entity.Stimmabgabeart;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.internet.control.GebietUndStimmabgabeart;

import java.util.*;
import java.util.function.BiPredicate;

public class StimmergebnisDatenVergleichswahl implements StimmergebnisDatenInterface {

  private final StimmergebnisDaten stimmergebnisDatenVergleich;

  private final List<Gruppe> alleGruppenAktuell;

  private final BiPredicate<Gebiet, Gebiet> gebieteGleichPredicate;

  private final BiPredicate<Gruppe, Gruppe> gruppenGleichPredicate;

  private final Map<Wahldurchfuehrungsgebiet, Optional<Wahldurchfuehrungsgebiet>> gebieteCache = new HashMap<>();

  private final Map<Gruppe, Optional<Gruppe>> gruppeCache = new HashMap<>();

  public StimmergebnisDatenVergleichswahl(StimmergebnisDaten stimmergebnisDatenVergleich, List<Gruppe> alleGruppenAktuell,
      BiPredicate<Gebiet, Gebiet> gebieteGleichPredicate, BiPredicate<Gruppe, Gruppe> gruppenGleichPredicate) {
    this.stimmergebnisDatenVergleich = stimmergebnisDatenVergleich;
    this.alleGruppenAktuell = alleGruppenAktuell;
    this.gebieteGleichPredicate = gebieteGleichPredicate;
    this.gruppenGleichPredicate = gruppenGleichPredicate;
  }

  private StimmergebnisDaten getStimmergebnisDatenVergleich() {
    return stimmergebnisDatenVergleich;
  }

  private List<Gruppe> getAlleGruppenAktuell() {
    return alleGruppenAktuell;
  }

  private BiPredicate<Gebiet, Gebiet> getGebieteGleichPredicate() {
    return gebieteGleichPredicate;
  }

  private BiPredicate<Gruppe, Gruppe> getGruppenGleichPredicate() {
    return gruppenGleichPredicate;
  }

  @Override
  public Map<PolitischeGruppe, Long> getErststimmenAktuell(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebietAktuell, Stimmabgabeart stimmabgabeart) {
    Optional<Wahldurchfuehrungsgebiet> wahldurchfuehrungsgebietVergleich = mappeWahldurchfuehrungsgebiet(wahldurchfuehrungsgebietAktuell);
    if (wahldurchfuehrungsgebietVergleich.isPresent()) {
      return ersetzePolitischeGruppenDurchAktuelle(getStimmergebnisDatenVergleich().getErststimmenAktuell(wahldurchfuehrungsgebietVergleich.get(), stimmabgabeart));
    }
    return Collections.emptyMap();
  }

  @Override
  public Map<Gruppe, Long> getErststimmenAktuellSystemgruppen(GebietUndStimmabgabeart gebietUndStimmabgabeartAktuell) {
    Optional<GebietUndStimmabgabeart> gebietUndStimmabgabeartVergleich = mappeGebietUndStimmabgabeart(gebietUndStimmabgabeartAktuell);
    if (gebietUndStimmabgabeartVergleich.isPresent()) {
      return ersetzeGruppenDurchAktuelle(getStimmergebnisDatenVergleich().getErststimmenAktuellSystemgruppen(gebietUndStimmabgabeartVergleich.get()));
    }
    return Collections.emptyMap();
  }

  @Override
  public Map<PolitischeGruppe, Long> getErststimmenVorperiode(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebietAktuell, Stimmabgabeart stimmabgabeart) {
    Optional<Wahldurchfuehrungsgebiet> wahldurchfuehrungsgebietVergleich = mappeWahldurchfuehrungsgebiet(wahldurchfuehrungsgebietAktuell);
    if (wahldurchfuehrungsgebietVergleich.isPresent()) {
      return ersetzePolitischeGruppenDurchAktuelle(getStimmergebnisDatenVergleich().getErststimmenVorperiode(wahldurchfuehrungsgebietVergleich.get(), stimmabgabeart));
    }
    return Collections.emptyMap();
  }

  @Override
  public Long getErststimmenVorperiodeGueltige(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebietAktuell, Stimmabgabeart stimmabgabeart) {
    Optional<Wahldurchfuehrungsgebiet> wahldurchfuehrungsgebietVergleich = mappeWahldurchfuehrungsgebiet(wahldurchfuehrungsgebietAktuell);
    if (wahldurchfuehrungsgebietVergleich.isPresent()) {
      return getStimmergebnisDatenVergleich().getErststimmenVorperiodeGueltige(wahldurchfuehrungsgebietVergleich.get(), stimmabgabeart);
    }
    return 0L;
  }

  @Override
  public Map<Gruppe, Long> getErststimmenVorperiodeSystemgruppen(GebietUndStimmabgabeart gebietUndStimmabgabeartAktuell) {
    Optional<GebietUndStimmabgabeart> gebietUndStimmabgabeartVergleich = mappeGebietUndStimmabgabeart(gebietUndStimmabgabeartAktuell);
    if (gebietUndStimmabgabeartVergleich.isPresent()) {
      return ersetzeGruppenDurchAktuelle(getStimmergebnisDatenVergleich().getErststimmenVorperiodeSystemgruppen(gebietUndStimmabgabeartVergleich.get()));
    }
    return Collections.emptyMap();
  }

  @Override
  public Map<PolitischeGruppe, Long> getZweitstimmenAktuell(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebietAktuell, Stimmabgabeart stimmabgabeart) {
    Optional<Wahldurchfuehrungsgebiet> wahldurchfuehrungsgebietVergleich = mappeWahldurchfuehrungsgebiet(wahldurchfuehrungsgebietAktuell);
    if (wahldurchfuehrungsgebietVergleich.isPresent()) {
      return ersetzePolitischeGruppenDurchAktuelle(getStimmergebnisDatenVergleich().getZweitstimmenAktuell(wahldurchfuehrungsgebietVergleich.get(), stimmabgabeart));
    }
    return Collections.emptyMap();
  }

  @Override
  public Map<Gruppe, Long> getZweitstimmenAktuellSystemgruppen(GebietUndStimmabgabeart gebietUndStimmabgabeartAktuell) {
    Optional<GebietUndStimmabgabeart> gebietUndStimmabgabeartVergleich = mappeGebietUndStimmabgabeart(gebietUndStimmabgabeartAktuell);
    if (gebietUndStimmabgabeartVergleich.isPresent()) {
      return ersetzeGruppenDurchAktuelle(getStimmergebnisDatenVergleich().getZweitstimmenAktuellSystemgruppen(gebietUndStimmabgabeartVergleich.get()));
    }
    return Collections.emptyMap();
  }

  @Override
  public Map<PolitischeGruppe, Long> getZweitstimmenVorperiode(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebietAktuell, Stimmabgabeart stimmabgabeart) {
    Optional<Wahldurchfuehrungsgebiet> wahldurchfuehrungsgebietVergleich = mappeWahldurchfuehrungsgebiet(wahldurchfuehrungsgebietAktuell);
    if (wahldurchfuehrungsgebietVergleich.isPresent()) {
      return ersetzePolitischeGruppenDurchAktuelle(getStimmergebnisDatenVergleich().getZweitstimmenVorperiode(wahldurchfuehrungsgebietVergleich.get(), stimmabgabeart));
    }
    return Collections.emptyMap();
  }

  @Override
  public Long getZweitstimmenVorperiodeGueltige(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebietAktuell, Stimmabgabeart stimmabgabeart) {
    Optional<Wahldurchfuehrungsgebiet> wahldurchfuehrungsgebietVergleich = mappeWahldurchfuehrungsgebiet(wahldurchfuehrungsgebietAktuell);
    if (wahldurchfuehrungsgebietVergleich.isPresent()) {
      return getStimmergebnisDatenVergleich().getZweitstimmenVorperiodeGueltige(wahldurchfuehrungsgebietVergleich.get(), stimmabgabeart);
    }
    return 0L;
  }

  @Override
  public Map<Gruppe, Long> getZweitstimmenVorperiodeSystemgruppen(GebietUndStimmabgabeart gebietUndStimmabgabeartAktuell) {
    Optional<GebietUndStimmabgabeart> gebietUndStimmabgabeartVergleich = mappeGebietUndStimmabgabeart(gebietUndStimmabgabeartAktuell);
    if (gebietUndStimmabgabeartVergleich.isPresent()) {
      return ersetzeGruppenDurchAktuelle(getStimmergebnisDatenVergleich().getZweitstimmenVorperiodeSystemgruppen(gebietUndStimmabgabeartVergleich.get()));
    }
    return Collections.emptyMap();
  }

  @Override
  public Map<PolitischeGruppe, Long> getGesamtStimmenAktuell(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebietAktuell, Stimmabgabeart stimmabgabeart) {
    Optional<Wahldurchfuehrungsgebiet> wahldurchfuehrungsgebietVergleich = mappeWahldurchfuehrungsgebiet(wahldurchfuehrungsgebietAktuell);
    if (wahldurchfuehrungsgebietVergleich.isPresent()) {
      return ersetzePolitischeGruppenDurchAktuelle(getStimmergebnisDatenVergleich().getGesamtStimmenAktuell(wahldurchfuehrungsgebietVergleich.get(), stimmabgabeart));
    }
    return Collections.emptyMap();
  }

  @Override
  public Map<Gruppe, Long> getGesamtstimmenAktuellSystemgruppen(GebietUndStimmabgabeart gebietUndStimmabgabeartAktuell) {
    Optional<GebietUndStimmabgabeart> gebietUndStimmabgabeartVergleich = mappeGebietUndStimmabgabeart(gebietUndStimmabgabeartAktuell);
    if (gebietUndStimmabgabeartVergleich.isPresent()) {
      return ersetzeGruppenDurchAktuelle(getStimmergebnisDatenVergleich().getGesamtstimmenAktuellSystemgruppen(gebietUndStimmabgabeartVergleich.get()));
    }
    return Collections.emptyMap();
  }

  @Override
  public Map<PolitischeGruppe, Long> getGesamtStimmenVorperiode(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebietAktuell, Stimmabgabeart stimmabgabeart) {
    Optional<Wahldurchfuehrungsgebiet> wahldurchfuehrungsgebietVergleich = mappeWahldurchfuehrungsgebiet(wahldurchfuehrungsgebietAktuell);
    if (wahldurchfuehrungsgebietVergleich.isPresent()) {
      return ersetzePolitischeGruppenDurchAktuelle(getStimmergebnisDatenVergleich().getGesamtStimmenVorperiode(wahldurchfuehrungsgebietVergleich.get(), stimmabgabeart));
    }
    return Collections.emptyMap();
  }

  @Override
  public Long getGesamtStimmenGueltigeVorperiode(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebietAktuell, Stimmabgabeart stimmabgabeart) {
    Optional<Wahldurchfuehrungsgebiet> wahldurchfuehrungsgebietVergleich = mappeWahldurchfuehrungsgebiet(wahldurchfuehrungsgebietAktuell);
    if (wahldurchfuehrungsgebietVergleich.isPresent()) {
      return getStimmergebnisDatenVergleich().getGesamtStimmenGueltigeVorperiode(wahldurchfuehrungsgebietVergleich.get(), stimmabgabeart);
    }
    return 0L;
  }

  @Override
  public Map<Gruppe, Long> getGesamtstimmenVorperiodeSystemgruppen(GebietUndStimmabgabeart gebietUndStimmabgabeartAktuell) {
    Optional<GebietUndStimmabgabeart> gebietUndStimmabgabeartVergleich = mappeGebietUndStimmabgabeart(gebietUndStimmabgabeartAktuell);
    if (gebietUndStimmabgabeartVergleich.isPresent()) {
      return ersetzeGruppenDurchAktuelle(getStimmergebnisDatenVergleich().getGesamtstimmenVorperiodeSystemgruppen(gebietUndStimmabgabeartVergleich.get()));
    }
    return Collections.emptyMap();
  }

  public Optional<Wahldurchfuehrungsgebiet> mappeWahldurchfuehrungsgebiet(Wahldurchfuehrungsgebiet gebietAktuell) {
    return gebieteCache.computeIfAbsent(gebietAktuell, x -> getStimmergebnisDatenVergleich().getGebiete().stream() //
        .filter(gebietVergleich -> getGebieteGleichPredicate().test(gebietAktuell, gebietVergleich)) //
        .findFirst());
  }

  private Optional<GebietUndStimmabgabeart> mappeGebietUndStimmabgabeart(GebietUndStimmabgabeart gebietUndStimmabgabeartAktuell) {
    Optional<Wahldurchfuehrungsgebiet> wahldurchfuehrungsgebietVergleich = mappeWahldurchfuehrungsgebiet(gebietUndStimmabgabeartAktuell.getWahldurchfuehrungsgebiet());
    if (wahldurchfuehrungsgebietVergleich.isPresent()) {
      return Optional.of(gebietUndStimmabgabeartAktuell.copy(wahldurchfuehrungsgebietVergleich.get()));
    }
    return Optional.empty();
  }

  private Map<PolitischeGruppe, Long> ersetzePolitischeGruppenDurchAktuelle(Map<PolitischeGruppe, Long> stimmenNachGruppen) {
    Map<PolitischeGruppe, Long> result = new HashMap<>();
    for (Map.Entry<Gruppe, Long> gruppeLongEntry : ersetzeGruppenDurchAktuelle(stimmenNachGruppen).entrySet()) {
      Gruppe gruppe = gruppeLongEntry.getKey();
      if (gruppe.isInstanceOf(PolitischeGruppe.class)) {
        result.put(gruppe.castTo(PolitischeGruppe.class).getEntity(), gruppeLongEntry.getValue());
      }
    }
    return result;
  }

  private Map<Gruppe, Long> ersetzeGruppenDurchAktuelle(Map<? extends Gruppe, Long> stimmenNachGruppen) {
    Map<Gruppe, Long> result = new HashMap<>();
    for (Map.Entry<? extends Gruppe, Long> gruppeLongEntry : stimmenNachGruppen.entrySet()) {
      Gruppe gruppeVergleich = gruppeLongEntry.getKey();
      Optional<Gruppe> gruppeAktuell = mappeGruppe(gruppeVergleich);
      if (gruppeAktuell.isPresent()) {
        result.put(gruppeAktuell.get(), gruppeLongEntry.getValue());
      } else {
        result.put(gruppeVergleich, gruppeLongEntry.getValue()); // NOTE: Stimmen von Gruppen, die nicht in Präsentation sind, werden zu Stimmen von Übrige aufsummiert
      }
    }
    return result;
  }

  private Optional<Gruppe> mappeGruppe(Gruppe gruppeVergleich) {
    return gruppeCache.computeIfAbsent(gruppeVergleich, x -> getAlleGruppenAktuell().stream()
        .filter(gruppeAktuell -> getGruppenGleichPredicate().test(gruppeAktuell, gruppeVergleich)) //
        .findFirst());
  }
}
