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.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.awt.Point;
import java.util.Queue;

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 = 200;

    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();
                }
                System.out.println(trails.size());
                playerGraphics.updateTrail(trails);
                playerGraphics.draw();

            }
        }
    }

    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);

        // Sortiere Konturen nach Fläche (absteigend)
        contours.sort((c1, c2) -> Double.compare(Imgproc.contourArea(c2), Imgproc.contourArea(c1)));

        // Speichere die Positionen der zwei größten Konturen
        List<Point> positions = new ArrayList<>();

        for (int i = 0; i < Math.min(contours.size(), 2); i++) {
            Moments moments = Imgproc.moments(contours.get(i));
            if (moments.get_m00() != 0) {
                int cx = (int) (moments.get_m10() / moments.get_m00());
                int cy = (int) (moments.get_m01() / moments.get_m00());
                positions.add(new Point(cx, cy));
            }
        }
        //Check
        return positions;
    }

    public synchronized Point getP1() {
        return currentPositionP1;
    }

    public Point getP2() {
        return currentPositionP2;
    }

}