package presenter.evaluation;

import model.Interval;
import model.Line;
import model.LineModel;
import model.Point;
import presenter.algorithms.naiv.NaivLeastMedianOfSquaresEstimator;
import presenter.algorithms.naiv.NaivRepeatedMedianEstimator;
import presenter.algorithms.naiv.NaivTheilSenEstimator;
import presenter.algorithms.util.IntersectionCounter;
import presenter.algorithms.advanced.LeastMedianOfSquaresEstimator;
import presenter.algorithms.advanced.RepeatedMedianEstimator;
import presenter.algorithms.advanced.TheilSenEstimator;
import presenter.generator.DatasetGenerator;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Observable;

/**
 * Implementierung verschiedener Algorithmen zur Berechnung von Ausgleichsgeraden.
 *
 * @Author: Armin Wolf
 * @Email: a_wolf28@uni-muenster.de
 * @Date: 01.08.2017.
 */
public class EvaluateAlgorithms extends Observable {

    private LineModel arrangement;

    private LinkedList<Line> lmsL;
    private LinkedList<Line> rmL;
    private LinkedList<Line> tsL;

    private ArrayList<Point> lmsP;
    private ArrayList<Point> tsP;

    private Thread lmsThread;
    private Thread rmThread;
    private Thread tsThread;

    private Double[] tsRes = new Double[2];
    private Double[] rmRes = new Double[2];
    private Double[] lmsRes = new Double[2];

    private DatasetGenerator generator;

    private String[][] names = {{"MSE", "RMSE", "MAE", "MDAE","Steigung","y-Achsenabschnitt", "S-MSE", "S-RMSE", "S-MAE", "S-MDAE", "Brute-force Steigung", "Brute-force y-Achsenabschnitt"}, {"MAPE", "MDAPE", "RMSPE", "RMDSPE","Steigung","y-Achsenabschnitt"}};

    //übergebene Parameter
    private int type;
    private int iterations;
    private int alg;

    /**
     * Konstruktor zur evaluation
     *
     * @param type Typ der evaluation
     * @param n    Größe des Datensatzes
     * @param alg  0 = lms,
     *             1 = rm,
     *             2 = ts,
     *             3 = lms, rm,
     *             4 = lms, ts,
     *             5 = rm,  ts,
     *             6 = lms, rm, ts,
     */
    public EvaluateAlgorithms(int type, int n, int alg, String datasettyp) {
        this.arrangement = new LineModel();
        generator = new DatasetGenerator();
        switch (datasettyp) {
            case "Punktwolke":
                arrangement.setLines(generator.generateDataCloud(n));
                break;
            case "Gerade":
                arrangement.setLines(generator.generateDataLines(n));
                break;
            case "Kreis und Gerade":
                arrangement.setLines(generator.generateCircle(n));
                break;
        }
        this.type = type;
        this.iterations = n;
        this.alg = alg;

        IntersectionCounter counter = new IntersectionCounter();
        counter.run(arrangement.getLines(), new Interval(-99999, 99999));
        counter.calculateIntersectionAbscissas(arrangement);


        lmsL = new LinkedList<>(arrangement.getLines());
        rmL = new LinkedList<>(arrangement.getLines());
        tsL = new LinkedList<>(arrangement.getLines());

        lmsP = new ArrayList<>(arrangement.getNodes());
        tsP = new ArrayList<>(arrangement.getNodes());
    }

    public void run() throws InterruptedException {
        setChanged();
        String[] msg = {"eval-dataset-generated"};
        notifyObservers(msg);
        ArrayList<String> result;
        ArrayList<ArrayList<String>> multipleResults = new ArrayList<>();

        /*  DEBUG
        NaivLeastMedianOfSquaresEstimator l = new NaivLeastMedianOfSquaresEstimator(arrangement.getLines());
        NaivRepeatedMedianEstimator r = new NaivRepeatedMedianEstimator(arrangement.getLines());
        NaivTheilSenEstimator t = new NaivTheilSenEstimator(arrangement.getLines());

        l.run();
        r.run();
        t.run();
        System.out.println();
        DEBUG  */

        startLMS();
        startRM();
        startTS();
        switch (type) {
            case 0:
                //der alg der gewählt wurde
                if (alg == 0) {
                    final double[] m = new double[1];
                    final double[] b = new double[1];
                    Thread t = new Thread(() -> {
                        NaivLeastMedianOfSquaresEstimator l = new NaivLeastMedianOfSquaresEstimator(arrangement.getLines());
                        l.run();
                        m[0] = l.getM();
                        b[0] = l.getB();
                    });
                    t.start();
                    startLMS();
                    t.join();

                    result = getScaleDependentMeasure(arrangement.getLines(), lmsRes[0], lmsRes[1]);
                    result.addAll(getScaledErrorBasedMeasure(arrangement.getLines(), lmsRes[0], lmsRes[1],m[0], b[0]));
                    Double[] tmp = {lmsRes[0], lmsRes[1],m[0], b[0]};
                    sendPlotLineResults(tmp, 0);
                } else if (alg == 1) {
                    final double[] m = new double[1];
                    final double[] b = new double[1];
                    Thread t = new Thread(() -> {
                        NaivRepeatedMedianEstimator r = new NaivRepeatedMedianEstimator(arrangement.getLines());
                        r.run();
                        m[0] = r.getM();
                        b[0] = r.getB();
                    });
                    t.start();
                    startRM();
                    result = getScaleDependentMeasure(arrangement.getLines(), rmRes[0], rmRes[1]);
                    result.addAll(getScaledErrorBasedMeasure(arrangement.getLines(), rmRes[0], rmRes[1],m[0], b[0]));
                    Double[] tmp = {rmRes[0], rmRes[1],m[0], b[0]};
                    sendPlotLineResults(tmp, 1);
                } else {
                    final double[] m = new double[1];
                    final double[] b = new double[1];
                    Thread t = new Thread(() -> {
                        NaivTheilSenEstimator ts = new NaivTheilSenEstimator(arrangement.getLines());
                        ts.run();
                        m[0] = ts.getM();
                        b[0] = ts.getB();
                    });
                    t.start();
                    startTS();
                    result = getScaleDependentMeasure(arrangement.getLines(), tsRes[0], tsRes[1]);
                    result.addAll(getScaledErrorBasedMeasure(arrangement.getLines(), tsRes[0], tsRes[1],m[0], b[0]));
                    Double[] tmp = {tsRes[0], tsRes[1],m[0], b[0]};
                    sendPlotLineResults(tmp, 2);
                }
                sendTableApproximationTypes();
                sendTableApproximationData(result, alg);
                break;
            case 1:
                ArrayList<Double[]> lineRes;
                switch (alg) {
                    case 3:

                        result = getPercentigeErrorBasedMeasure(arrangement.getLines(), lmsRes[0], lmsRes[1]);
                        multipleResults.add(result);
                        result = getPercentigeErrorBasedMeasure(arrangement.getLines(), rmRes[0], rmRes[1]);
                        multipleResults.add(result);
                        result = fillPseudoResults();
                        multipleResults.add(result);

                        lineRes = new ArrayList<>();
                        lineRes.add(lmsRes);
                        lineRes.add(rmRes);
                        sendPloteLineResults(lineRes, new Integer[]{0,1});

                        break;
                    case 4:
                        result = getPercentigeErrorBasedMeasure(arrangement.getLines(), lmsRes[0], lmsRes[1]);
                        multipleResults.add(result);
                        result = fillPseudoResults();
                        multipleResults.add(result);
                        result = getPercentigeErrorBasedMeasure(arrangement.getLines(), tsRes[0], tsRes[1]);
                        multipleResults.add(result);

                        lineRes = new ArrayList<>();
                        lineRes.add(lmsRes);
                        lineRes.add(tsRes);
                        sendPloteLineResults(lineRes, new Integer[]{0, 2});
                        break;
                    case 5:
                        result = fillPseudoResults();
                        multipleResults.add(result);
                        result = getPercentigeErrorBasedMeasure(arrangement.getLines(), rmRes[0], rmRes[1]);
                        multipleResults.add(result);
                        result = getPercentigeErrorBasedMeasure(arrangement.getLines(), tsRes[0], tsRes[1]);
                        multipleResults.add(result);

                        lineRes = new ArrayList<>();
                        lineRes.add(rmRes);
                        lineRes.add(tsRes);
                        sendPloteLineResults(lineRes, new Integer[]{1, 2});
                        break;
                    case 6:
                        result = getPercentigeErrorBasedMeasure(arrangement.getLines(), lmsRes[0], lmsRes[1]);
                        multipleResults.add(result);
                        result = getPercentigeErrorBasedMeasure(arrangement.getLines(), rmRes[0], rmRes[1]);
                        multipleResults.add(result);
                        result = getPercentigeErrorBasedMeasure(arrangement.getLines(), tsRes[0], tsRes[1]);
                        multipleResults.add(result);

                        lineRes = new ArrayList<>();
                        lineRes.add(lmsRes);
                        lineRes.add(rmRes);
                        lineRes.add(tsRes);
                        sendPloteLineResults(lineRes, new Integer[]{0, 1, 2});
                        break;
                }

                sendTableApproximationData(multipleResults);
                break;
        }


    }

    public void sendTableApproximationData(ArrayList<String> result, int col) {
        ArrayList<String> tableInput = new ArrayList<>();
        tableInput.add("eval-d");
        tableInput.add("" + col);
        for (int i = 0; i < names[type].length; i++) {
            tableInput.add(result.get(i));
        }
        tableInput.add("");
        setChanged();
        notifyObservers(tableInput.stream().toArray(String[]::new));
        tableInput.clear();
    }

    public void sendTableApproximationData(ArrayList<ArrayList<String>> result) {
        ArrayList<String> tableInput = new ArrayList<>();

        //iteration über die ApproximationsGüten -- Zeilen
        for (int j = 0; j <= result.get(0).size(); j++) {
            tableInput.add("eval-ds");
            if (j != result.get(0).size()) {

                tableInput.add(names[type][j]);
                //iteration über die alg. -- Spalten
                for (int i = 0; i < 3; i++) {
                    tableInput.add(result.get(i).get(j));
                }
            } else {
                tableInput.add("");
                tableInput.add("");
                tableInput.add("");
                tableInput.add("");
            }
            setChanged();
            notifyObservers(tableInput.stream().toArray(String[]::new));
            tableInput.clear();
        }


    }

    public void sendTableApproximationTypes() {
        ArrayList<String> tableInput = new ArrayList<>();
        tableInput.add("eval-t");
        tableInput.add("" + 0);
        for (int i = 0; i < names[type].length; i++) {
            tableInput.add(names[type][i]);
        }
        tableInput.add("");
        setChanged();
        notifyObservers(tableInput.stream().toArray(String[]::new));
        tableInput.clear();
    }

    public void sendPlotLineResults(Double[] res, int alg) {
        //visualisiere m,b

        ArrayList<String> lines = new ArrayList<>();
        lines.add("lines-res");
        lines.add("" + alg);
        //lms res
        for (int i=0;i<res.length;i++) {
            lines.add(res[i] + "");
        }
        setChanged();
        notifyObservers(lines.stream().toArray(String[]::new));
    }

    public void sendPloteLineResults(ArrayList<Double[]> res, Integer[] algs) {
        ArrayList<String> lines = new ArrayList<>();
        lines.add("lines-res-mult");
        for (int i = 0; i < algs.length; i++) {
            lines.add("" + algs[i]);
            //lms res
            Double[] tmp = res.get(i);
            lines.add(tmp[0] + "");
            lines.add(tmp[1] + "");
        }
        setChanged();
        notifyObservers(lines.stream().toArray(String[]::new));
    }

    public void startLMS() throws InterruptedException {

        lmsThread = new Thread(() -> {
            LeastMedianOfSquaresEstimator lmsAlg = new LeastMedianOfSquaresEstimator(lmsL,lmsP);
            lmsAlg.run();
            lmsAlg.getResult();
            lmsRes[0] = lmsAlg.getSlope();
            lmsRes[1] = lmsAlg.getyInterception();
        });
        lmsThread.start();
        lmsThread.join();
    }

    public void startRM() throws InterruptedException {
        rmThread = new Thread(() -> {
            RepeatedMedianEstimator rmAlg = new RepeatedMedianEstimator(rmL);
            rmAlg.run();
            rmAlg.getResult();
            rmRes[0] = rmAlg.getSlope();
            rmRes[1] = rmAlg.getyInterception();
        });
        rmThread.start();
        rmThread.join();

    }

    public void startTS() throws InterruptedException {
        tsThread = new Thread(() -> {
            TheilSenEstimator tsAlg = new TheilSenEstimator(tsL,tsP);
            tsAlg.run();
            tsAlg.getResult();
            tsRes[0] = tsAlg.getSlope();
            tsRes[1] = tsAlg.getyInterception();
        });
        tsThread.start();
        tsThread.join();
    }



    public ArrayList<String> getScaleDependentMeasure(final LinkedList<Line> lines, final Double m, final Double b) {
        ScaleDependentMeasure scaleDependentMeasure = new ScaleDependentMeasure(lines, m, b);
        ArrayList<String> ret = new ArrayList<>();
        ret.add(scaleDependentMeasure.mse().toString());
        ret.add(scaleDependentMeasure.rmse().toString());
        ret.add(scaleDependentMeasure.mae().toString());
        ret.add(scaleDependentMeasure.mdae().toString());
        ret.add(m.toString());
        ret.add(b.toString());

        return ret;
    }

    public ArrayList<String> getPercentigeErrorBasedMeasure(final LinkedList<Line> lines, final Double m, final Double b) {
        PercentageErrorBasedMeasure percentageErrorBasedMeasure = new PercentageErrorBasedMeasure(lines, m, b);
        ArrayList<String> ret = new ArrayList<>();
        ret.add(percentageErrorBasedMeasure.mape().toString());
        ret.add(percentageErrorBasedMeasure.mdape().toString());
        ret.add(percentageErrorBasedMeasure.rmspe().toString());
        ret.add(percentageErrorBasedMeasure.rmdspe().toString());
        ret.add(m.toString());
        ret.add(b.toString());
        return ret;
    }

    public ArrayList<String> getScaledErrorBasedMeasure(final LinkedList<Line> lines, final Double m, final Double b, final Double nM, final Double nB) {
        ScaledErrorBasedMeasure scaledErrorBasedMeasure = new ScaledErrorBasedMeasure(lines, m, b, nM, nB);
        ArrayList<String> ret = new ArrayList<>();
        ret.add(scaledErrorBasedMeasure.mse().toString());
        ret.add(scaledErrorBasedMeasure.rmse().toString());
        ret.add(scaledErrorBasedMeasure.mae().toString());
        ret.add(scaledErrorBasedMeasure.mdae().toString());
        ret.add(nM.toString());
        ret.add(nB.toString());
        return ret;
    }

    private ArrayList<String> fillPseudoResults() {
        ArrayList<String> result = new ArrayList<>();
        result.add(" ");
        result.add(" ");
        result.add(" ");
        result.add(" ");
        result.add(" ");
        result.add(" ");
        return result;
    }

    public LinkedList<Line> getData() {
        return arrangement.getLines();
    }
}
