import org.opencv.core.*;
import org.opencv.videoio.VideoCapture;
import org.opencv.highgui.HighGui;
import org.opencv.imgproc.Imgproc;
import org.opencv.imgproc.Moments;
import org.opencv.videoio.Videoio;
import processing.core.PApplet;

import java.util.*;
import java.awt.Point;

public class ImgAnalyzer {

    private final int THV;                // Threshold

    private Point currentPositionP1 = new Point(0, 0);
    private Point currentPositionP2 = new Point(0, 0);
    Queue<int[]> trails = new LinkedList<>();
    private final int TRAIL_LENGTH = 20;

    private final CamPuffer camPuffer;
    private GraphicsProgram playerGraphics;

    public ImgAnalyzer(CamPuffer cP, int threshold) {
        THV = threshold;
        camPuffer = cP;
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);

        playerGraphics = new GraphicsProgram();
        String[] args ={"GraphicsProgram"};
        PApplet.runSketch(args, playerGraphics);
    }

    public static void main(String[] args) {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
    }

    public void run() {
        while (camPuffer.getCurrentMat().elemSize() == 0) {
            System.out.println("Warten auf das erste Bild...");
            try {
                Thread.sleep(3000); // Kurze Pause, um CPU-Belastung zu vermeiden
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return;
            }
        }

        Mat currentImg = new Mat();
        Mat grayFrame = new Mat();
        Mat thresholdFrame = new Mat();

        while (true) {
            try {
                currentImg = camPuffer.getCurrentMat();

                if(currentImg.elemSize() != 0) {

                    // In Graustufen konvertieren
                    Imgproc.cvtColor(currentImg, grayFrame, Imgproc.COLOR_BGR2GRAY);

                    // Threshold anwenden
                    Imgproc.threshold(grayFrame, thresholdFrame, THV, 255, Imgproc.THRESH_BINARY);

                    //Ausgabe des Bildes zum Prüfen
                    HighGui.imshow("ThunfischHighGui", thresholdFrame);
                    HighGui.waitKey(1);
                    //Ende Bildausgabe */
                }

            } catch (Exception e) {

            }

            if (currentImg != null) {
                List<Point> positions = trackPositions(thresholdFrame);

                if (positions.size() >= 2) {
                    currentPositionP1 = positions.get(0);
                    currentPositionP2 = positions.get(1);
                    //System.out.println("currentPositionP1: " + currentPositionP1 + " currentPositionP2: " + currentPositionP2);
                }

                trails.add(new int[]{currentPositionP1.x, currentPositionP1.y, currentPositionP2.x, currentPositionP2.y});
                if(trails.size() > TRAIL_LENGTH){
                    trails.poll();
                }
                playerGraphics.updateTrail(currentPositionP1.x, currentPositionP1.y, currentPositionP2.x, currentPositionP2.y);
                //playerGraphics.draw();
            }

            /*org.opencv.core.Point pp1 = new org.opencv.core.Point(currentPositionP1.x, currentPositionP1.y);
            Imgproc.circle(currentImg, pp1, 10, new Scalar(255,0,0), -1);

            org.opencv.core.Point pp2 = new org.opencv.core.Point(currentPositionP2.x, currentPositionP2.y);
            Imgproc.circle(currentImg, pp2, 10, new Scalar(0,0,255), -1);

            if(currentImg.elemSize() != 0) {

                HighGui.imshow("el thunfisch", currentImg);

            } */
        }
    }

    private List<Point> trackPositions(Mat thresholdFrame) {
        List<MatOfPoint> contours = new ArrayList<>();
        Mat hierarchy = new Mat();

        // Finde Konturen
        Imgproc.findContours(thresholdFrame, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);

        // Liste für die Positionen und zugehörige Pixelanzahl
        List<Point> positions = new ArrayList<>();
        List<Double> whitePixelCounts = new ArrayList<>();

        for (MatOfPoint contour : contours) {
            Moments moments = Imgproc.moments(contour);
            if (moments.get_m00() != 0) {
                // Berechne den Mittelpunkt der Kontur
                int cx = (int) (moments.get_m10() / moments.get_m00());
                int cy = (int) (moments.get_m01() / moments.get_m00());

                // Berechne die Anzahl der weißen Pixel in der Kontur
                Mat mask = Mat.zeros(thresholdFrame.size(), CvType.CV_8UC1);
                Imgproc.drawContours(mask, List.of(contour), 0, new Scalar(255), -1); // Fülle die Kontur mit Weiß
                double whitePixels = Core.sumElems(mask).val[0] / 255.0; // Summe der Pixelwerte durch 255 (1 Pixel = 255)

                // Speichere Position und Pixelanzahl
                positions.add(new Point(cx, cy));
                whitePixelCounts.add(whitePixels);
            }
        }

        // Sortiere die Positionen basierend auf der Anzahl weißer Pixel (absteigend)
        List<Point> sortedPositions = new ArrayList<>();
        while (!whitePixelCounts.isEmpty()) {
            int maxIndex = whitePixelCounts.indexOf(Collections.max(whitePixelCounts));
            sortedPositions.add(positions.get(maxIndex));
            positions.remove(maxIndex);
            whitePixelCounts.remove(maxIndex);
        }

        // Gib die Positionen der zwei größten Konturen zurück
        return sortedPositions.subList(0, Math.min(sortedPositions.size(), 2));
    }


    public synchronized Point getP1() {
        return currentPositionP1;
    }

    public Point getP2() {
        return currentPositionP2;
    }

}