import requestAnimationFrame from "dat.gui/src/dat/utils/requestAnimationFrame.js";
import { canvas2d_draw_grid } from "../../../../engine/graphics/canvas/canvas2d_draw_grid.js";
import { MouseEvents } from "../../../../engine/input/devices/events/MouseEvents.js";
import { PointerDevice } from "../../../../engine/input/devices/PointerDevice.js";
import { CanvasView } from "../../../../view/elements/CanvasView.js";
import { FLT_EPSILON_32 } from "../../../math/FLT_EPSILON_32.js";
import { randomIntegerBetween } from "../../../math/random/randomIntegerBetween.js";
import { seededRandom } from "../../../math/random/seededRandom.js";
import { aabb2_clip_line_cohen_sutherland } from "../aabb/aabb2_clip_line_cohen_sutherland.js";
import { shg_query_elements_circle } from "./shg_query_elements_circle.js";
import { shg_query_elements_line } from "./shg_query_elements_line.js";
import { SpatialHashGrid } from "./SpatialHashGrid.js";


const canvas = new CanvasView();

canvas.size.set(window.innerWidth, window.innerHeight);

canvas.link();

document.body.style.margin = 0;
document.body.style.overflow = "hidden";
document.body.appendChild(canvas.el);

const selected_boxes = [1, 3, 9]

const grid = new SpatialHashGrid(16, 16, 32);

const render_scale = 2;

const BOX_COUNT = 50;

const boxes = new Float32Array(BOX_COUNT * 4);

const random = seededRandom();

function populate_boxes(
    random,
    bounds_x0, bounds_y0, bounds_x1, bounds_y1
) {

    for (let i = 0; i < BOX_COUNT; i++) {

        const offset = i * 4;


        const width = randomIntegerBetween(random, 10, 20);
        const height = randomIntegerBetween(random, 10, 20);

        const x0 = randomIntegerBetween(random, Math.ceil(bounds_x0), Math.floor(bounds_x1 - width));
        const y0 = randomIntegerBetween(random, Math.ceil(bounds_y0), Math.floor(bounds_y1 - height));

        const x1 = x0 + width;
        const y1 = y0 + height;

        boxes[offset] = x0;
        boxes[offset + 1] = y0;
        boxes[offset + 2] = x1;
        boxes[offset + 3] = y1;

        // add box to grid
        const box = grid.element_allocate();

        grid.element_set_user_data(box, i);
        grid.element_set_bounds_primitive(box, x0, y0, x1, y1);

        grid.element_insert(box);

    }
}

function init() {
    populate_boxes(
        random,
        0, 0,
        grid.size_x * grid.scale, grid.size_y * grid.scale
    );

    query.link(canvas);

}

function render_boxes() {
    const ctx = canvas.context2d;

    // draw background boxes

    for (let i = 0; i < BOX_COUNT; i++) {

        if (selected_boxes.includes(i)) {
            continue;
        }

        const offset = i * 4;
        const x0 = boxes[offset] * render_scale;
        const y0 = boxes[offset + 1] * render_scale;
        const x1 = boxes[offset + 2] * render_scale;
        const y1 = boxes[offset + 3] * render_scale;

        const w = x1 - x0;
        const h = y1 - y0;

        ctx.fillStyle = "rgba(0,184,255,1)";
        ctx.fillRect(x0, y0, w, h);

    }

    // draw selected boxes

    for (let i = 0; i < BOX_COUNT; i++) {
        if (!selected_boxes.includes(i)) {
            continue;
        }

        const offset = i * 4;
        const x0 = boxes[offset] * render_scale;
        const y0 = boxes[offset + 1] * render_scale;
        const x1 = boxes[offset + 2] * render_scale;
        const y1 = boxes[offset + 3] * render_scale;

        const w = x1 - x0;
        const h = y1 - y0;

        ctx.fillStyle = "rgba(238,0,255,1)";
        // ctx.strokeStyle = "rgb(238,0,255)";
        // ctx.lineWidth = 4;

        // ctx.strokeRect(x0, y0, w, h);
        ctx.fillRect(x0, y0, w, h);

    }
}

function render_grid() {
    const scale = grid.scale * render_scale;
    canvas2d_draw_grid({
        ctx: canvas.context2d,
        width: grid.size_x * scale + 1,
        height: grid.size_y * scale + 1,
        spacing: scale,
        color: "rgba(0,0,0,0.2)"
    });
}

class Query {
    #view = null

    /**
     *
     * @param {CanvasRenderingContext2D} ctx
     * @param {SpatialHashGrid} grid
     * @param {number} render_scale
     */
    render(ctx, grid, render_scale) {

    }

    /**
     *
     * @param {CanvasRenderingContext2D} ctx
     * @param {SpatialHashGrid} grid
     * @param {number} render_scale
     */
    render_debug(ctx, grid, render_scale) {

    }

    /**
     *
     * @param {number[]} result
     * @param {number} result_offset
     * @param {SpatialHashGrid} grid
     * @returns {number}
     */
    run(result, result_offset, grid) {
        return 0;
    }

    /**
     *
     * @param {View} view
     */
    link(view) {
        if (this.#view !== view) {
            this.unlink();
        }

        this.#view = view;
    }

    unlink() {
        this.#view = null;
    }
}

class LineQuery extends Query {
    #line = [0, 0, 0, 0];

    render_debug(ctx, grid, render_scale) {

        const visited = shg_query_elements_line.visited;
        if (visited === undefined) {
            return;
        }
        const n = visited.length;

        const scale = grid.scale * render_scale;

        for (let i = 0; i < n; i += 2) {
            const x = visited[i];
            const y = visited[i + 1];

            ctx.fillStyle = "rgba(0,0,255,0.3)";

            ctx.fillRect(x * scale, y * scale, scale, scale);
        }

    }

    render(ctx, grid, render_scale) {
        const line = this.#line;


        ctx.lineWidth = 2;
        ctx.strokeStyle = "rgb(255,47,0)";

        ctx.beginPath();
        ctx.moveTo(line[0] * render_scale, line[1] * render_scale);
        ctx.lineTo(line[2] * render_scale, line[3] * render_scale);
        ctx.closePath();
        ctx.stroke();
    }

    run(result, result_offset, grid) {
        const line = this.#line;
        const clipped_line = [];

        if (!aabb2_clip_line_cohen_sutherland(
            clipped_line, 0,
            0, 0, grid.size_x * grid.scale - FLT_EPSILON_32, grid.size_y * grid.scale - FLT_EPSILON_32,
            line[0], line[1], line[2], line[3]
        )) {
            // line is outsize of the grid
            return 0;
        }

        const els = [];
        const hits = shg_query_elements_line(
            els, 0,
            grid,
            clipped_line[0], clipped_line[1],
            clipped_line[2], clipped_line[3]
        );


        for (let i = 0; i < hits; i++) {
            const id = grid.element_get_user_data(els[i]);

            result[i + result_offset] = id;
        }

        return hits;
    }

    link(view) {
        super.link(view);

        const line = this.#line;
        let anchor = 0;

        view.el.addEventListener(MouseEvents.Click, (evt) => {
            const index = anchor * 2;

            line[index + 0] = evt.clientX / render_scale;
            line[index + 1] = evt.clientY / render_scale;

            anchor = (anchor + 1) % 2;
        });

    }
}

class CircleQuery extends Query {
    #circle = [0, 0, 0];

    link(view) {
        super.link(view);

        const pointer = new PointerDevice(view.el);

        pointer.start();

        pointer.on.drag.add((position, origin) => {
            this.#circle[0] = origin.x / render_scale;
            this.#circle[1] = origin.y / render_scale;

            this.#circle[2] = position.distanceTo(origin) / render_scale;
        });
    }

    render_debug(ctx, grid, render_scale) {

        const visited = shg_query_elements_circle.visited;
        if (visited === undefined) {
            return;
        }
        const n = visited.length;

        const scale = grid.scale * render_scale;

        for (let i = 0; i < n; i += 2) {
            const x = visited[i];
            const y = visited[i + 1];

            ctx.fillStyle = "rgba(0,0,255,0.3)";

            ctx.fillRect(x * scale, y * scale, scale, scale);
        }

    }

    render(ctx, grid, render_scale) {

        const c = this.#circle;
        ctx.beginPath();
        ctx.arc(c[0] * render_scale, c[1] * render_scale, c[2] * render_scale, 0, Math.PI * 2);
        ctx.closePath();

        ctx.lineWidth = 3;
        ctx.strokeStyle = "red";
        ctx.stroke();

    }

    run(result, result_offset, grid) {
        const els = [];
        const hits = shg_query_elements_circle(
            els, 0,
            grid,
            this.#circle[0], this.#circle[1],
            this.#circle[2]
        );


        for (let i = 0; i < hits; i++) {
            const id = grid.element_get_user_data(els[i]);

            result[i + result_offset] = id;
        }

        return hits;
    }
}

const query = new CircleQuery();
// const query = new LineQuery();

function render() {

    canvas.context2d.clearRect(0, 0, canvas.size.x, canvas.size.y);

    render_grid();
    query.render_debug(canvas.context2d, grid, render_scale);
    render_boxes();
    query.render(canvas.context2d, grid, render_scale);
}


function update() {

    requestAnimationFrame(update);

    selected_boxes.splice(0, selected_boxes.length)

    query.run(selected_boxes, 0, grid);

    render();
}

init();
update();

window.addEventListener("resize", () => {

    canvas.size.set(window.innerWidth, window.innerHeight);

});
