package kaka.cakelight;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.stage.Stage;
import kaka.cakelight.mode.AmbientMode;
import org.opencv.core.Core;
import org.opencv.core.Mat;

import static kaka.cakelight.Main.log;

public class GuiTest extends Application {
    private static final int BLOCK = 45;
    private static final int GUTTER = 3 * BLOCK;
    private Canvas canvas;
    private Configuration config;
    private CakeLight cakelight;
    private boolean paused;

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

    @Override
    public void start(Stage stage) throws Exception {
        config = Configuration.from("config.properties");
        canvas = new Canvas(16 * BLOCK + 2 * GUTTER, 9 * BLOCK + 2 * GUTTER);

        Pane root = new Pane();
        root.getChildren().add(canvas);

        Scene scene = new Scene(root);
        scene.setOnKeyPressed(keyEvent -> {
            if (keyEvent.getCode() == KeyCode.ESCAPE || keyEvent.getCode() == KeyCode.Q) {
                stage.close();
                cakelight.cleanup();
            }
            if (keyEvent.getCode() == KeyCode.SPACE) {
                paused = !paused;
            }
        });

        stage.setTitle("Cakelight");
        stage.setScene(scene);
        stage.setOnCloseRequest(windowEvent -> cakelight.cleanup());
        stage.show();

        setupCakeLight();
    }

    private void setupCakeLight() {
        log("Running with config:\n" + config);
        cakelight = new CakeLight(config);
        VideoMode mode = new VideoMode();
        cakelight.setMode(mode);
        cakelight.startLoop();
        mode.onVideoFrame(frame -> drawFrame(canvas.getGraphicsContext2D(), frame));
    }

    private javafx.scene.paint.Color getLedColor(LedFrame frame, int led) {
        kaka.cakelight.Color c = frame.getLedColor(led);
        return javafx.scene.paint.Color.rgb(c.r(), c.g(), c.b());
    }

    private void drawFrame(GraphicsContext gc, VideoFrame frame) {
        if (paused) return;
        System.out.println("Drawing a frame");
        drawCols(gc, frame);
        drawRows(gc, frame);
//        drawVideo(gc, frame);
        drawBorderAndGrid(gc);
    }

    private void drawLEDs(GraphicsContext gc, LedFrame frame) {
        int ledLength = GUTTER;
        float colSize = 16f * BLOCK / config.leds.cols;
        float rowSize = 9f * BLOCK / config.leds.rows;
//        DropShadow shadow = new DropShadow(BlurType.ONE_PASS_BOX, Color.RED, colSize * 2, colSize, 0, 0);
        for (int x = 0; x < config.leds.cols; x++) {
            // Top
            gc.setFill(getLedColor(frame, config.leds.cols * 2 + config.leds.rows - x - 1));
            gc.fillRect(GUTTER + x * colSize, GUTTER - ledLength, colSize, ledLength);
            // Bottom
            gc.setFill(getLedColor(frame, x));
            gc.fillRect(GUTTER + x * colSize, GUTTER + 9 * BLOCK, colSize, ledLength);
        }
        for (int y = 0; y < config.leds.rows; y++) {
            // Left
            gc.setFill(getLedColor(frame, config.leds.cols * 2 + config.leds.rows + y));
            gc.fillRect(GUTTER - ledLength, GUTTER + y * rowSize, ledLength, rowSize);
            // Right
            gc.setFill(getLedColor(frame, config.leds.rows + config.leds.cols - y - 1));
            gc.fillRect(GUTTER + 16 * BLOCK, GUTTER + y * rowSize, ledLength, rowSize);
        }
    }

    private void drawVideo(GraphicsContext gc, VideoFrame frame) {
        byte[] rgb = new byte[3];
        Mat img = frame.getConvertedImage();
        float colSize = 16 * BLOCK / (float)img.cols();
        float rowSize = 9 * BLOCK / (float)img.rows();
        for (int x = 0, cols = img.cols(); x < cols; x++) {
            for (int y = 0, rows = img.rows(); y < rows; y++) {
                img.get(y, x, rgb);
                gc.setFill(Color.rgb(rgb[0] & 0xff, rgb[1] & 0xff, rgb[2] & 0xff, 0.5));
                gc.fillRect(GUTTER + x * colSize, GUTTER + y * rowSize, colSize, rowSize);
            }
        }
    }

    private void drawCols(GraphicsContext gc, VideoFrame frame) {
        byte[] rgb = new byte[3];
        for (int x = 0; x < config.leds.cols; x++) {
            for (int y = 0; y < 9; y++) {
                frame.getColImage().get(y, x, rgb);
                drawColPixel(gc, x, y, rgb);
            }
        }
    }

    private void drawRows(GraphicsContext gc, VideoFrame frame) {
        byte[] rgb = new byte[3];
        for (int y = 0; y < config.leds.rows; y++) {
            for (int x = 0; x < 16; x++) {
                frame.getRowImage().get(y, x, rgb);
                drawRowPixel(gc, x, y, rgb);
            }
        }
    }

    private void drawColPixel(GraphicsContext gc, int x, int y, byte[] rgb) {
        float ledSize = 16f * BLOCK / config.leds.cols;
        gc.setFill(Color.rgb(rgb[0] & 0xff, rgb[1] & 0xff, rgb[2] & 0xff, 0.5));
        gc.fillRect(GUTTER + x * ledSize, GUTTER + y * BLOCK, ledSize, BLOCK);
    }

    private void drawRowPixel(GraphicsContext gc, int x, int y, byte[] rgb) {
        float ledSize = 9f * BLOCK / config.leds.rows;
        gc.setFill(Color.rgb(rgb[0] & 0xff, rgb[1] & 0xff, rgb[2] & 0xff, 0.5));
        gc.fillRect(GUTTER + x * BLOCK, GUTTER + y * ledSize, BLOCK, ledSize);
    }

    private void drawBorderAndGrid(GraphicsContext gc) {
        gc.setStroke(Color.BLACK);
        gc.setLineWidth(BLOCK * 0.1);
        gc.strokeRect(GUTTER, GUTTER, 16 * BLOCK, 9 * BLOCK);
        gc.setLineWidth(1);
        for (int x = 1; x < 16; x++) {
            gc.strokeLine(GUTTER + x * BLOCK, GUTTER, GUTTER + x * BLOCK, GUTTER + 9 * BLOCK);
        }
        for (int y = 1; y < 9; y++) {
            gc.strokeLine(GUTTER, GUTTER + y * BLOCK, GUTTER + 16 * BLOCK, GUTTER + y * BLOCK);
        }
    }

    private void drawPixel(GraphicsContext gc, int x, int y, Paint paint) {
        gc.setFill(paint);
        gc.fillRect(GUTTER + x * BLOCK, GUTTER + y * BLOCK, BLOCK, BLOCK);
    }
}
