package de.wwwu.awolf.presenter;

import de.wwwu.awolf.model.Line;
import de.wwwu.awolf.model.LineModel;
import de.wwwu.awolf.model.communication.Data;
import de.wwwu.awolf.presenter.algorithms.Algorithm;
import de.wwwu.awolf.presenter.algorithms.AlgorithmHandler;
import de.wwwu.awolf.presenter.data.DataHandler;
import de.wwwu.awolf.presenter.evaluation.EvaluatationHandler;
import de.wwwu.awolf.presenter.util.Logging;
import de.wwwu.awolf.view.ViewController;
import javafx.application.Platform;

import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Flow;

/**
 * Implementierung verschiedener Algorithmen zur Berechnung von Ausgleichsgeraden.
 *
 * @Author: Armin Wolf
 * @Email: a_wolf28@uni-muenster.de
 * @Date: 10.09.2017.
 */
public abstract class AbstractPresenter implements Flow.Subscriber<Data> {

    private final ExecutorService executor;
    private LineModel model;
    private ViewController view;
    private EvaluatationHandler evaluatationHandler;
    private DataHandler dataHandler;
    private AlgorithmHandler algorithmHandler;


    /**
     * Konstruktor
     */
    public AbstractPresenter() {
        Logging.logDebug("Create instance of Presenter.");
        this.executor = Executors.newCachedThreadPool();
        this.dataHandler = new DataHandler(this);
        this.algorithmHandler = AlgorithmHandler.getInstance();
        //empty model
        this.model = new LineModel();
        //init values null
        this.view = null;
    }

    @Override
    public void onSubscribe(Flow.Subscription subscription) {
        Logging.logInfo("New Subscription: " + subscription.toString());
        subscription.request(15);
    }


    @Override
    public void onNext(Data data) {
        switch (data.getType()) {
            case EVALUATION_TABLE_DATA:
                evaluatedDatas(data);
                break;
            case LMS:
                visualizeLmsAlgorithm(data);
                break;
            case RM:
                visualizeRmAlgorithm(data);
                break;
            case TS:
                visualizeTsAlgorithm(data);
                break;
            case GENERATOR:
                Logging.logInfo("Generierung war Erfolgreich");
                break;
            default:
                break;
        }
    }

    protected abstract void visualizeTsAlgorithm(Data data);

    protected abstract void visualizeRmAlgorithm(Data data);

    protected abstract void visualizeLmsAlgorithm(Data data);

    protected abstract void evaluatedDatas(Data data);


    @Override
    public void onError(Throwable throwable) {

    }

    @Override
    public void onComplete() {

    }

    /**
     * Execute an algorithm specified by a type
     *
     * @param type  algorithm type
     * @param lines set of lines
     */
    public void executeAlgorithmByType(Algorithm.Type type, Set<Line> lines) {
        this.algorithmHandler.runAlgorithmByType(type, lines);
    }

    /**
     * Execute an algorithm specified by a type
     * (use the Lines from the LineModel)
     *
     * @param type  algorithm type
     */
    public void executeAlgorithmByType(Algorithm.Type type) {
        if (getModel().getSize() == 0) {
            Logging.logDebug("No lines in the Model. Nothing to calculate.");
        } else {
            Logging.logDebug("AlgorithmHandler will start " + type.getName() + ", with " + getModel().getSize());
            this.algorithmHandler.runAlgorithmByType(type, getModel().getLines());
        }
    }

    /**
     * @return das zu grunde legende Modell
     */
    public LineModel getModel() {
        return model;
    }

    /**
     * @return die zu grunde legende View
     */
    public ViewController getView() {
        return view;
    }

    /**
     * @param view die zu grunde legende View
     */
    public void registerView(ViewController view) {
        this.view = view;
        Logging.enableGuiLogging();
        Logging.logDebug("View has been set.");


        //customize gui
        Platform.runLater(() -> {
            this.view.initGui();
        });
    }

    /**
     * @return Evaluation
     */
    EvaluatationHandler getEvaluatationHandler() {
        return evaluatationHandler;
    }

    DataHandler getDataHandler() {
        return dataHandler;
    }

    /**
     * Returns the instance of the ExecutorService
     *
     * @return ExecutorService instance
     */
    public ExecutorService getExecutor() {
        return Objects.requireNonNullElseGet(executor, Executors::newCachedThreadPool);
    }
}
