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

import java.util.HashSet;
import java.util.Set;
import java.util.function.BiFunction;

import de.ivu.elect.business.auffaelligkeit.entity.AbstandartZwischenGebieten;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.hochrechnung.entity.AbstandGetter;
import de.ivu.elect.business.hochrechnung.entity.DistanceMeasureArt;
import de.ivu.elect.business.hochrechnung.entity.GebietDaten;
import de.ivu.elect.business.hochrechnung.entity.KoordinatenInformationen;
import de.ivu.elect.jee.presentation.dynamicform.FormBuilder;
import de.ivu.elect.jee.presentation.dynamicform.LambdaEnumProperty;


import static java.util.Arrays.asList;

/**
 * <h1>Geometrischer Abstand zwischen GebietDaten</h1>
 * Jedes Gebiet wird durch ein Punkt im Euklidischen Raum praesentiert.
 * Abstaende zwischen Gebieten koennen dann nach Euklidischem Abstand, Manhattan-abstand, usw. berechnet
 */
public class GeometricAbstandZwischenGebietDaten implements AbstandGetter<Double> {
  private DistanceMeasureArt distanceMeasureArt = DistanceMeasureArt.EUKLIDISCHER_ABSTAND;
  private Long gewicht = 16L;
  private final PointGetter<Double> pointGetter;
  private Boolean isDefiniert = null;

  public GeometricAbstandZwischenGebietDaten(PointGetter<Double> pointGetter) {
    this.pointGetter = pointGetter;
  }

  public GeometricAbstandZwischenGebietDaten(DistanceMeasureArt distanceMeasureArt, PointGetter<Double> pointGetter) {
    this.distanceMeasureArt = distanceMeasureArt;
    this.pointGetter = pointGetter;
  }

  private DistanceMeasureArt getDistanceMeasureArt() {
    return distanceMeasureArt;
  }

  public void setDistanceMeasureArt(DistanceMeasureArt distanceMeasureArt) {
    this.distanceMeasureArt = distanceMeasureArt;
  }

  @Override
  public Double getAbstandWennDefiniert(GebietDaten<Double> g1, GebietDaten<Double> g2, KoordinatenInformationen koordinatenInformationen) {
    if (isDefiniert() && g1.isErgebnisVorhanden() && g2.isErgebnisVorhanden()) {
      return computeGeometricDistance(pointGetter.getPoint(g1, koordinatenInformationen), pointGetter.getPoint(g2, koordinatenInformationen));
    }
    return null;
  }

  @Override
  public Long getGewicht() {
    return gewicht;
  }

  @Override
  public void setGewicht(Long gewicht) {
    this.gewicht = gewicht;
  }

  @Override
  public boolean isDefiniert() {
    return this.isDefiniert != null && this.isDefiniert;
  }

  @Override
  public boolean setIsDefiniert(KoordinatenInformationen koordinatenInformationen) {
    this.isDefiniert = koordinatenInformationen.getAllePolitischeGruppenSortiert().stream().anyMatch(politischeGruppe -> hatPolitischeGruppeStimmenInVorperiode(politischeGruppe, koordinatenInformationen));
    return this.isDefiniert;
  }

  @Override
  public AbstandartZwischenGebieten getAbstandartZwischenGebieten() {
    return AbstandartZwischenGebieten.GEOMETRISCH;
  }

  @Override
  public void addObservingProperties(FormBuilder formBuilder, LambdaEnumProperty observedProperty, BiFunction<String, Object[], String> messageGetter) {
    LambdaEnumProperty lambdaEnumPropertyDisatanceMeasure = new LambdaEnumProperty<>(messageGetter.apply("stimmeingabe.auffaelligkeit.distance.measure.art", new Object[0]), true, this::setDistanceMeasureArt,
        this::getDistanceMeasureArt,
        DistanceMeasureArt.class, asList(DistanceMeasureArt.values()));

    Set<Enum> showBy = new HashSet<>();
    showBy.add(AbstandartZwischenGebieten.GEOMETRISCH);
    showBy.add(AbstandartZwischenGebieten.MISCHUNG_MIT_BENUTZER_DEFINIERTE_GEWICHTUNG);
    addAbstractObservingLambdaProperty(formBuilder, observedProperty, lambdaEnumPropertyDisatanceMeasure,
        showBy, true);
  }

  private boolean hatPolitischeGruppeStimmenInVorperiode(PolitischeGruppe politischeGruppe, KoordinatenInformationen koordinatenInformationen) {
    return koordinatenInformationen.getAlleStimmenartenSortiert().stream().anyMatch(stimmenart -> koordinatenInformationen.getGruppeInVorperiodeVorhandenChecker().isPolitischeGruppeInVorperiodeVorhanden(politischeGruppe, stimmenart));
  }

  private Double computeGeometricDistance(double[] p1, double[] p2) {
    return distanceMeasureArt.getDistanceMeasure().compute(p1, p2);
  }
}
