package de.wwwu.awolf.presenter.util;

import com.google.common.collect.Lists;
import de.wwwu.awolf.model.Line;
import de.wwwu.awolf.model.Point;

import java.util.*;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.stream.Collectors;

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

    private static IntersectionComputer instance;

    /**
     * Konstruktor
     */
    private IntersectionComputer() {
    }

    public static IntersectionComputer getInstance() {
        if (instance == null) {
            instance = new IntersectionComputer();
            Logging.logInfo("Created instance of IntersectionComputer");
        }

        return instance;
    }

    /**
     * Berechnet zu einer gegebenen Menge von dualen Geraden die Schnittpunkte. Dafür wird ein modifizierter Merge-Sort
     * Algorithmus verwendett. Um die Performance zu steigern wird die Berechnung ab einer passenden Größe auf vier
     * Threads ausgelagert.
     *
     * @param lower  untere Schranke
     * @param higher obere Schranke
     * @return Liste der Schnittpunkte
     */
    public Collection<Point> compute(final List<Line> lines, final double lower, final double higher) {
        if (lower == higher) {
            return Collections.emptyList();
        } else {
            Logging.logDebug("Open ForkJoinPool: lines: " + lines.size() + " I(" + lower + ", " + higher + "]");
            ForkJoinPool pool = ForkJoinPool.commonPool();
            RecursiveComputationTask recursiveComputationTask = new RecursiveComputationTask(lines, lines, lower, higher);
            pool.execute(recursiveComputationTask);
            return recursiveComputationTask.join();
        }
    }

    /**
     * Berechnet die Schnittpunkte zwischen einer gegebenen Gerade und einer Menge an Geraden.
     *
     * @param set         Menge an Geraden
     * @param sampledLine eine spezielle Gerade
     * @return Liste mit x Koordinaten der Schnittpunkte
     */
    public List<Double> calculateIntersectionAbscissas(List<Line> set, Line sampledLine, double lower, double upper) {
        List<Line> lines = new LinkedList<>(set);
        Set<Double> intersections = new HashSet<>();

        for (Line line : lines) {
            if (line != sampledLine) {
                if (sampledLine.doIntersect(line, lower, upper)) {
                    intersections.add(sampledLine.intersect(line).getX());
                }
            }
        }

        return new ArrayList<>(intersections);
    }

    private class RecursiveComputationTask extends RecursiveTask<Collection<Point>> {

        private static final int THRESHOLD = 20;

        private final List<Line> lines;
        private final List<Line> fullList;
        private final double lower;
        private final double upper;

        public RecursiveComputationTask(final List<Line> fullList, final List<Line> lines, final double lower, final double upper) {
            this.lines = lines;
            this.fullList = fullList;
            this.lower = lower;
            this.upper = upper;
        }

        @Override
        protected Collection<Point> compute() {
            if (this.lines.isEmpty()) {
                return Collections.emptyList();
            } else if (this.lines.size() > THRESHOLD) {
                Logging.logDebug("Bigger than threshold, split into subtask.");
                return ForkJoinTask.invokeAll(createSubTask()).stream().map(ForkJoinTask::join).flatMap(Collection::stream).collect(Collectors.toList());
            } else {
                return work(this.fullList, this.lines, this.lower, this.upper);
            }
        }

        private Collection<RecursiveComputationTask> createSubTask() {
            List<RecursiveComputationTask> dividedTasks = new ArrayList<>();
            dividedTasks.add(new RecursiveComputationTask(this.fullList, this.lines.subList(0, this.lines.size() / 2), this.lower, this.upper));
            dividedTasks.add(new RecursiveComputationTask(this.fullList, this.lines.subList(this.lines.size() / 2, this.lines.size()), this.lower, this.upper));
            return dividedTasks;
        }

        private Collection<Point> work(List<Line> fullList, List<Line> lines, double lower, double higher) {
            Set<Point> points = new HashSet<>();
            List<List<Line>> lists = Lists.cartesianProduct(new ArrayList<>(fullList), new ArrayList<>(lines));
            lists.forEach(entry -> {
                if (entry.get(0).doIntersect(entry.get(1), lower, upper)) {
                    Point intersect = entry.get(0).intersect(entry.get(1));
                    if (intersect.getX() > lower && intersect.getX() <= higher) {
                        points.add(intersect);
                    }
                }
            });
            return new HashSet<>(points);
        }


    }

}
