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

import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import de.ivu.elect.business.hochrechnung.entity.GewichtetesObject;

public class DistanceWeightedKNearestNeighborsNormierer <O extends Object> {
  public List<GewichtetesObject<O>> getDistanceWeightedKNearestNeighbors (long k, List<GewichtetesObject<O>> objekteUndDerenAbstaende) {
    /*
    based on:
    Gou, J., Du, L., Zhang, Y. and Xiong, T., 2012. A new distance-weighted k-nearest neighbor classifier. J. Inf. Comput. Sci, 9(6), pp.1429-1436.
    * */

    List<GewichtetesObject<O>> kNearestNeighbor = objekteUndDerenAbstaende.stream()
        .filter(gewichtetesObject -> gewichtetesObject.getGewicht() != null)
        .sorted()
        .limit(k + 1) // +1, weil der weitester Nachbarn kriegt Gewicht 0
        .collect(Collectors.toList());

    if (kNearestNeighbor.isEmpty()) {
      return Collections.emptyList();
    }

    Double smallestDistance = kNearestNeighbor.get(0).getGewicht();
    Double largestDistance = kNearestNeighbor.get(kNearestNeighbor.size() - 1).getGewicht();

    return kNearestNeighbor.stream().map(objektMitAbstand ->
        new GewichtetesObject<O>(objektMitAbstand.getObject(), getDWKNNWeight(objektMitAbstand.getGewicht(), smallestDistance, largestDistance))
    )
        .limit(k)
        .collect(Collectors.toList());
  }

  private Double getDWKNNWeight(Double distance, Double smallesDistance, Double largestDistance) {
    if (distance == null || smallesDistance == null || largestDistance == null) {
      return null;
    }
    if (smallesDistance.equals(largestDistance)) {
      return 1D;
    }

    //  We add 1 here if we are only interested in distances with the largest value and we don't want them to become 0
    return ((largestDistance - distance + (1/largestDistance)) * (largestDistance + smallesDistance)) / ((largestDistance - smallesDistance) * (largestDistance + distance));
    //return ((largestDistance - distance) * (largestDistance + smallesDistance)) / ((largestDistance - smallesDistance) * (largestDistance + distance));
  }
}
