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.ArrayList;
import java.util.List;
import java.util.Set;
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 NaivTheilSenEstimator implements Algorithm {

    private static final Algorithm.Type type = Type.NAIV_TS;

    private List<Line> setOfLines;
    private double slope;
    private double yInterception;
    private Flow.Subscriber<? super Data> subscriber;
    private AbstractPresenter presenter;

    @Override
    public Line call() {
        this.slope = 0d;
        this.yInterception = 0d;
        Logging.logInfo("=== S T A R T - naiv T S ===");
        long start;
        long end;
        start = System.currentTimeMillis();
        List<Double> slopesList = new ArrayList<>();
        int cnt = 0;
        for (int i = 0; i < setOfLines.size(); i++) {
            double x = setOfLines.get(i).getM();
            double y = setOfLines.get(i).getB();

            for (int j = i + 1; j < setOfLines.size(); j++) {
                if (x != setOfLines.get(j).getM()) { // x must be different, otherwise slope becomes infinite
                    Double slope = (setOfLines.get(j).getB() - y) / (setOfLines.get(j).getM() - x);
                    slopesList.add(slope);
                    ++cnt;
                }
            }
        }


        List<Double> list1 = new ArrayList<>();
        List<Double> list2 = new ArrayList<>();
        for (Line line : setOfLines) {
            list1.add(line.getM());
            list2.add(line.getB());
        }
        double median1 = FastElementSelector.randomizedSelect(list1, list1.size() * 0.5);
        double median2 = FastElementSelector.randomizedSelect(list2, list2.size() * 0.5);
        slope = FastElementSelector.randomizedSelect(slopesList, slopesList.size() * 0.5);
        yInterception = median2 - slope * median1;
        end = System.currentTimeMillis();
        Logging.logInfo("=== E N D - naiv T S ===");
        Logging.logInfo("Slope: "  + getSlope() + ", y-Interception: " + getYInterception());

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

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

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

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


    /**
     * @return Steigung
     */
    public double getSlope() {
        return slope * -1;
    }

    /**
     * @return y-Achsenabschnitt
     */
    public double getYInterception() {
        return yInterception * -1;
    }

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