package de.wwwu.awolf.presenter.algorithms.naiv;

import de.wwwu.awolf.model.Line;
import de.wwwu.awolf.model.communication.Data;
import de.wwwu.awolf.presenter.AbstractPresenter;
import de.wwwu.awolf.presenter.algorithms.Algorithm;
import de.wwwu.awolf.presenter.util.FastElementSelector;
import de.wwwu.awolf.presenter.util.Logging;

import java.util.*;
import java.util.concurrent.Flow;

/**
 * Implementierung verschiedener Algorithmen zur Berechnung von Ausgleichsgeraden.
 *
 * @Author: Armin Wolf
 * @Email: a_wolf28@uni-muenster.de
 * @Date: 15.09.2017.
 */
public class NaivLeastMedianOfSquaresEstimator implements Algorithm {

    private static final Algorithm.Type type = Type.NAIV_LMS;
    private List<Line> setOfLines = new ArrayList<>();

    private int n;
    private double ds, b, m;
    private Flow.Subscriber<? super Data> subscriber;
    private AbstractPresenter presenter;


    /**
     * Crude Algorithmus zum berechnen des LSM-Schätzers.
     */
    private Line crudeAlg() {
        ds = Double.MAX_VALUE;
        b = 0d;
        m = 0d;
        List<Line> triple = new ArrayList<>();
        double beta;
        double alpha;
        double dijk;
        Logging.logInfo("=== S T A R T - naiv L M S ===");
        long start;
        long end;
        start = System.currentTimeMillis();
        for (Line i : setOfLines) {
            for (Line j : setOfLines) {
                for (Line k : setOfLines) {
                    triple.add(i);
                    triple.add(j);
                    triple.add(k);
                    Collections.sort(triple);
                    beta = (triple.get(0).getB() - triple.get(2).getB()) / (triple.get(0).getM() - triple.get(2).getM());
                    alpha = (triple.get(1).getB() + triple.get(2).getB() - (beta * (triple.get(1).getM() + triple.get(2).getM()))) * 0.5;
                    dijk = f(alpha, beta);
                    if (dijk < ds) {
                        ds = dijk;
                        b = alpha;
                        m = beta;
                    }
                    triple.clear();
                }
            }
        }
        end = System.currentTimeMillis();
        Logging.logInfo("=== E N D - naiv L M S ===");
        Logging.logInfo("Slope: "  + getSlope() + ", y-Interception: " + getYInterception());

        return new Line(getSlope(), getYInterception());
    }

    /**
     * Berechnet die Gerade mit dem medianen Fehler
     *
     * @param a y-Achsenabschnitt
     * @param b Steigung
     * @return medianer Fehler
     */
    private Double f(double a, double b) {
        List<Double> res = new ArrayList<>();
        for (Line p : setOfLines) {
            res.add(Math.abs(p.getB() - (a + b * p.getM())));
        }
        return FastElementSelector.randomizedSelect(res, res.size() * 0.5);
    }

    @Override
    public Line call() {
        return crudeAlg();
    }

    @Override
    public void setInput(Set<Line> lines) {
        this.setOfLines = new LinkedList<>(lines);
    }

    @Override
    public Type getType() {
        return type;
    }

    @Override
    public void setPresenter(AbstractPresenter presenter) {
        this.presenter = presenter;
        subscribe(presenter);
    }

    /**
     * @return y-Achsenabschnitt
     */
    public Double getYInterception() {
        return b * -1;
    }

    /**
     * @return Steigung
     */
    public Double getSlope() {
        return m * -1;
    }

    @Override
    public void subscribe(Flow.Subscriber<? super Data> subscriber) {
        this.subscriber = subscriber;
    }
}
