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

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;

import de.ivu.elect.business.auffaelligkeit.entity.AbstandartZwischenGebieten;
import de.ivu.elect.business.hochrechnung.boundary.HochrechnungsalgorithmusKonfiguration;
import de.ivu.elect.jee.presentation.dynamicform.FormBuilder;
import de.ivu.elect.jee.presentation.dynamicform.LambdaEnumProperty;
import de.ivu.elect.jee.presentation.dynamicform.LambdaLongProperty;

public class HochrechnenMitDistanceWeightedKNearestNeighborsKonfiguration implements HochrechnungsalgorithmusKonfiguration {
  private long defaul_k_value = 25; // Anzahl von zu ein Gebiet benachbarten (nach ausgewaehlten Abstand) Gebieten zu betrachten.
  private Long k = null;

  private AbstandartZwischenGebieten abstandartZwischenGebieten;
  private final Map<AbstandartZwischenGebieten, AbstandGetter<Double>> abstandGetterNachAbstandArt = new HashMap<>();

  public HochrechnenMitDistanceWeightedKNearestNeighborsKonfiguration() {
    k = defaul_k_value;
    for (AbstandartZwischenGebieten abstandart: AbstandartZwischenGebieten.getAbstandartenInBevorzugteReihenfolge()) {
      abstandGetterNachAbstandArt.computeIfAbsent(abstandart, AbstandartZwischenGebieten::getAbstandGetter);
    }
    this.abstandartZwischenGebieten = getDefaultAbstandart();
  }

  public long getK() {
    return this.k != null ? this.k : defaul_k_value;
  }

  public void setK(long k) {
    this.k = k;
  }

  public void setDefaul_k_valueToSquareRootNumberOfGebiete(KoordinatenInformationen koordinatenInformationen) {
    setDefaul_k_valueToSquareRootNumberOf(koordinatenInformationen.getAlleGebiete().size());
  }

  public void setDefaul_k_valueToSquareRootNumberOf(long GebieteAnzahl) {
    this.defaul_k_value = (long) Math.ceil(Math.sqrt(GebieteAnzahl + 1));
  }

  public AbstandartZwischenGebieten getAbstandartZwischenGebieten() {
    return abstandartZwischenGebieten;
  }

  public void setAbstandartZwischenGebieten(AbstandartZwischenGebieten abstandartZwischenGebieten) {
    this.abstandartZwischenGebieten = abstandartZwischenGebieten;
  }

  private void setAbstandartZwischenGebietenToDefaultWohldefinierterAbstandart(KoordinatenInformationen koordinatenInformationen) {
    setAbstandartZwischenGebieten(getDefaultWohldefinierterAbstandart(koordinatenInformationen));
  }

  private AbstandartZwischenGebieten getDefaultAbstandart() {
    List<AbstandartZwischenGebieten> abstandartZwischenGebietenInBevorzugteReihenfolge = AbstandartZwischenGebieten
        .getAbstandartenInBevorzugteReihenfolge();
    return abstandartZwischenGebietenInBevorzugteReihenfolge.isEmpty()? null : abstandartZwischenGebietenInBevorzugteReihenfolge.get(0);
  }

  private AbstandartZwischenGebieten getDefaultWohldefinierterAbstandart(KoordinatenInformationen koordinatenInformationen) {
    List<AbstandartZwischenGebieten> abstandartenInBevorzugteReihenfolge = AbstandartZwischenGebieten
        .getAbstandartenInBevorzugteReihenfolge();
    for (AbstandartZwischenGebieten abstandart: abstandartenInBevorzugteReihenfolge) {
      AbstandGetter<Double> abstandGetter = abstandGetterNachAbstandArt.get(abstandart);
      if (abstandGetter != null && abstandGetter.setIsDefiniert(koordinatenInformationen)) {
        return abstandart;
      }
    }
    return null;
  }

  public AbstandGetter<Double> getAbstandGetter() {
    AbstandartZwischenGebieten gewaehlteAbstandartZwischenGebieten = getAbstandartZwischenGebieten();

    return gewaehlteAbstandartZwischenGebieten != null? abstandGetterNachAbstandArt.get(gewaehlteAbstandartZwischenGebieten) : null;
  }

  /*
  uncomment if you want to make VorlaefigesErgebnisAlsFiktivesErgebnisUebernehmen it chosable. Now it's set as default.
  @Override
  public VorlaefigesErgebnisAlsFiktivesErgebnisUebernehmen getVorlaefigesErgebnisAlsFiktivesErgebnisUebernehmen() {
    return vorlaefigesErgebnisAlsFiktivesErgebnisUebernehmen;
  }

  @Override
  public void setVorlaefigesErgebnisAlsFiktivesErgebnisUebernehmen(VorlaefigesErgebnisAlsFiktivesErgebnisUebernehmen vorlaefigesErgebnisAlsFiktivesErgebnisUebernehmen) {
    this.vorlaefigesErgebnisAlsFiktivesErgebnisUebernehmen = vorlaefigesErgebnisAlsFiktivesErgebnisUebernehmen;
  }*/

  @Override
  public void addObservingProperties(FormBuilder formBuilder, LambdaEnumProperty observedProperty, BiFunction<String, Object[], String> messageGetter) {

    LambdaLongProperty lambdaLongPropertyK = new LambdaLongProperty(
        messageGetter.apply("stimmeingabe.auffaelligkeit.visualisiert.anzahl.von.gebiet.vergleichen", new Object[0]), true,
        this::setK,
        this::getK, 0L, 1000L);
    lambdaLongPropertyK.setTooltip(messageGetter.apply("stimmeingabe.auffaelligkeit.visualisiert.anzahl.von.gebiet.vergleichen" + ".tooltip", new Object[0]));

    LambdaEnumProperty<AbstandartZwischenGebieten> lambdaAbstandartZwischenGebietenEnumProperty = new LambdaEnumProperty<>(
        messageGetter.apply("stimmeingabe.auffaelligkeit.abstandart.zwischen.gebieten", new Object[0]), true,
        this::setAbstandartZwischenGebieten, this::getAbstandartZwischenGebieten,
        AbstandartZwischenGebieten.class, AbstandartZwischenGebieten.getAbstandartenInBevorzugteReihenfolge(), true);

    Set<HochrechnungsalgorithmusArt> showByHochrechnungsalgorithmusArt = new HashSet<>();
    showByHochrechnungsalgorithmusArt.add(HochrechnungsalgorithmusArt.HOCHRECHNEN_MIT_DISTANCE_WEIGHTED_KNEAREST_NEIGHBORS);
    addAbstractObservingLambdaProperty(formBuilder, observedProperty, lambdaLongPropertyK,
        showByHochrechnungsalgorithmusArt, true);
    addAbstractObservingLambdaProperty(formBuilder, observedProperty, lambdaAbstandartZwischenGebietenEnumProperty,
        showByHochrechnungsalgorithmusArt, true);

    abstandGetterNachAbstandArt.values().forEach(abstandGetter ->
        abstandGetter.addObservingProperties(formBuilder, lambdaAbstandartZwischenGebietenEnumProperty, messageGetter)
    );
  }

  @Override
  public HochrechnungsalgorithmusArt getHochrechnungsalgorithmusArt() {
    return HochrechnungsalgorithmusArt.HOCHRECHNEN_MIT_DISTANCE_WEIGHTED_KNEAREST_NEIGHBORS;
  }

  @Override
  public void adjustDefaultSelectionNachKoordinatenInformationen(KoordinatenInformationen koordinatenInformationen) {
    //setDefaul_k_valueToSquareRootNumberOfGebiete(koordinatenInformationen);
    setAbstandartZwischenGebietenToDefaultWohldefinierterAbstandart(koordinatenInformationen);
  }
}
