import { Frustum, OrthographicCamera } from "three";
import FacingDirectionSystem from "../../../../../model/game/ecs/system/FacingDirectionSystem.js";
import { makeEngineOptionsModel } from "../../../../../model/game/options/makeEngineOptionsModel.js";
import { enableEditor } from "../../../../editor/enableEditor.js";
import { RingBuffer } from "../../../core/collection/RingBuffer.js";
import { convex_hull_jarvis_2d } from "../../../core/geom/2d/convex-hull/convex_hull_jarvis_2d.js";
import Vector2 from "../../../core/geom/Vector2.js";
import Vector3 from "../../../core/geom/Vector3.js";
import { computeStatisticalMean } from "../../../core/math/statistics/computeStatisticalMean.js";
import { CanvasView } from "../../../view/elements/CanvasView.js";
import Entity from "../../ecs/Entity.js";
import { FogOfWarRevealerSystem } from "../../ecs/fow/FogOfWarRevealerSystem.js";
import { FogOfWarSystem } from "../../ecs/fow/FogOfWarSystem.js";
import GUIElement from "../../ecs/gui/GUIElement.js";
import GUIElementSystem from "../../ecs/gui/GUIElementSystem.js";
import HeadsUpDisplaySystem from "../../ecs/gui/hud/HeadsUpDisplaySystem.js";
import ViewportPosition from "../../ecs/gui/position/ViewportPosition.js";
import ViewportPositionSystem from "../../ecs/gui/position/ViewportPositionSystem.js";
import { InverseKinematicsSystem } from "../../ecs/ik/InverseKinematicsSystem.js";
import RenderSystem from "../../ecs/renderable/RenderSystem.js";
import AnimationSystem from "../../ecs/systems/AnimationSystem.js";
import ScriptSystem from "../../ecs/systems/ScriptSystem.js";
import TimerSystem from "../../ecs/systems/TimerSystem.js";
import ClingToTerrainSystem from "../../ecs/terrain/ecs/cling/ClingToTerrainSystem.js";
import TerrainSystem from "../../ecs/terrain/ecs/TerrainSystem.js";
import { Transform } from "../../ecs/transform/Transform.js";
import { EngineConfiguration } from "../../EngineConfiguration.js";
import { EngineHarness } from "../../EngineHarness.js";
import { GridPosition2TransformSystem } from "../../grid/grid2transform/GridPosition2TransformSystem.js";
import GridPositionSystem from "../../grid/position/GridPositionSystem.js";
import { Transform2GridPositionSystem } from "../../grid/transform2grid/Transform2GridPositionSystem.js";
import InputControllerSystem from "../../input/ecs/systems/InputControllerSystem.js";
import { InputSystem } from "../../input/ecs/systems/InputSystem.js";
import { BehaviorSystem } from "../../intelligence/behavior/ecs/BehaviorSystem.js";
import PathFollowingSystem from "../../navigation/ecs/path_following/PathFollowingSystem.js";
import { SoundEmitterSystem } from "../../sound/ecs/emitter/SoundEmitterSystem.js";
import SoundControllerSystem from "../../sound/ecs/SoundControllerSystem.js";
import SoundListenerSystem from "../../sound/ecs/SoundListenerSystem.js";
import AnimationControllerSystem from "../ecs/animation/AnimationControllerSystem.js";
import { AnimationGraphSystem } from "../ecs/animation/animator/AnimationGraphSystem.js";
import { CameraSystem } from "../ecs/camera/CameraSystem.js";
import { frustum_from_camera } from "../ecs/camera/frustum_from_camera.js";
import TopDownCameraControllerSystem from "../ecs/camera/topdown/TopDownCameraControllerSystem.js";
import { TopDownCameraLanderSystem } from "../ecs/camera/topdown/TopDownCameraLanderSystem.js";
import MeshHighlightSystem from "../ecs/highlight/system/MeshHighlightSystem.js";
import LightSystem from "../ecs/light/LightSystem.js";
import Mesh from "../ecs/mesh/Mesh.js";
import { MeshSystem } from "../ecs/mesh/MeshSystem.js";
import { PathDisplaySystem } from "../ecs/path/PathDisplaySystem.js";
import Trail2DSystem from "../ecs/trail2d/Trail2DSystem.js";
import WaterSystem from "../ecs/water/WaterSystem.js";
import { computeFrustumCorners } from "../render/forward_plus/computeFrustumCorners.js";

const engineHarness = new EngineHarness();

function makeConfig(engine) {

    const config = new EngineConfiguration();

    /**
     *
     * @type {SoundEngine}
     */
    const sound = engine.sound;
    const graphics = engine.graphics;
    const assetManager = engine.assetManager;
    const devices = engine.devices;

    const guiSystem = new GUIElementSystem(engine.gui.view, engine);
    const headsUpDisplaySystem = new HeadsUpDisplaySystem(graphics);

    config.addManySystems(
        new ScriptSystem(),
        new FacingDirectionSystem(),
        new PathFollowingSystem(),
        new SoundEmitterSystem(assetManager, sound.destination, sound.context),
        new SoundControllerSystem(),
        new SoundListenerSystem(sound.context),
        new TimerSystem(),
        guiSystem,
        new AnimationSystem(graphics.viewport.size),
        new TopDownCameraControllerSystem(graphics),
        new TopDownCameraLanderSystem(),
        new CameraSystem(engine.graphics),
        new MeshSystem(engine),
        new ClingToTerrainSystem(),
        new TerrainSystem(graphics, assetManager),
        new WaterSystem(graphics),
        new Trail2DSystem(engine),
        new ViewportPositionSystem(graphics.viewport.size),
        new GridPosition2TransformSystem(),
        new Transform2GridPositionSystem(),
        new GridPositionSystem(),
        new InputControllerSystem(devices),
        new InputSystem(devices),
        new MeshHighlightSystem(engine),
        new LightSystem(engine, {
            shadowResolution: 1024
        }),
        new AnimationControllerSystem(),
        new AnimationGraphSystem(graphics.viewport.size),
        headsUpDisplaySystem,
        new FogOfWarSystem(graphics),
        new FogOfWarRevealerSystem(0),
        new BehaviorSystem(engine),
        new InverseKinematicsSystem(),
        new PathDisplaySystem(engine),
    );

    // Plugins


    // Knowledge

    config.knowledge.push();

    return config;
}

/**
 *
 * @param {Engine} engine
 */
function makeCameraClippingDebug(engine) {
    const canvasView = new CanvasView();
    canvasView.size.set(500, 500);

    const ctx = canvasView.context2d;

    const scale = new Vector3(4, 4, 4);
    const offset = new Vector2(-150, -50);

    function fillRect(x, y, w, h) {
        ctx.fillRect(x * scale.x + offset.x, y * scale.y + offset.y, w * scale.x, h * scale.y);
    }

    function strokeRect(x, y, w, h) {
        ctx.strokeRect(x * scale.x + offset.x, y * scale.y + offset.y, w * scale.x, h * scale.y);
    }

    function lineTo(x, y) {
        ctx.lineTo(x * scale.x + offset.x, y * scale.y + offset.y);
    }

    function moveTo(x, y) {
        ctx.moveTo(x * scale.x + offset.x, y * scale.y + offset.y);
    }


    function drawAABB(aabb) {
        ctx.lineWidth = 1;
        ctx.strokeStyle = 'rgba(50,255,50,0.5)';

        const x0 = aabb.x0;
        const z0 = aabb.z0;

        const w = aabb.x1 - x0;
        const h = aabb.z1 - z0;

        strokeRect(x0, z0, w, h);
        // fillRect(x0, z0, w, h);
    }

    function drawPoint(x, y, z, color = 'red', point_size = 2) {
        ctx.fillStyle = color;
        ctx.strokeStyle = 'white';
        ctx.lineWidth = 1;

        const ps_2 = point_size / 2;

        strokeRect(x - ps_2, z - ps_2, point_size, point_size);
        fillRect(x - ps_2, z - ps_2, point_size, point_size);
    }

    function drawConvexHull(vertices, fillColor, strokeColor) {

        const jarvis_vertices = convex_hull_jarvis_2d(vertices.map(v => [v.x, v.z]).flat(), vertices.length);

        ctx.fillStyle = fillColor;
        ctx.strokeStyle = strokeColor;

        ctx.beginPath();
        moveTo(vertices[jarvis_vertices[0]].x, vertices[jarvis_vertices[0]].z);
        // drawPoint(jarvis_vertices[0].x, jarvis_vertices[0].y, jarvis_vertices[0].z, 'red');

        for (let i = 0; i < jarvis_vertices.length; i++) {
            const last = vertices[jarvis_vertices[i]];

            lineTo(last.x, last.z);

            // drawPoint(last.x, last.y, last.z, 'red');
        }

        ctx.closePath();
        ctx.stroke();
        ctx.fill();
    }

    /**
     *
     * @param {Camera} camera
     */
    function drawCamera(camera) {
        const frustum = new Frustum();

        frustum_from_camera(camera, frustum);

        const points = [];

        const planes = frustum.planes;
        computeFrustumCorners(points, planes);

        const vertices = [];
        for (let i = 0; i < points.length; i += 3) {
            const v = new Vector3();

            v.set(points[i], points[i + 1], points[i + 2]);

            vertices.push(v);
        }

        const vertices_down = [
            vertices[0],
            vertices[1],
            vertices[4],
            vertices[5]
        ];

        const vertices_up = [
            vertices[2],
            vertices[3],
            vertices[6],
            vertices[7]
        ];

        ctx.strokeStyle = 'rgba(52,255,0,0.8)';
        ctx.lineWidth = 1;
        ctx.beginPath();
        moveTo(vertices[0].x, vertices[0].z);
        lineTo(vertices[2].x, vertices[2].z);
        ctx.stroke();
        ctx.beginPath();
        moveTo(vertices[1].x, vertices[1].z);
        lineTo(vertices[3].x, vertices[3].z);
        ctx.stroke();

        ctx.strokeStyle = 'rgba(238,0,255,0.8)';
        ctx.lineWidth = 1;
        ctx.beginPath();
        moveTo(vertices[4].x, vertices[4].z);
        lineTo(vertices[6].x, vertices[6].z);
        ctx.stroke();
        ctx.beginPath();
        moveTo(vertices[5].x, vertices[5].z);
        lineTo(vertices[7].x, vertices[7].z);
        ctx.stroke();

        drawConvexHull(vertices_down, 'rgba(230,255,0,0.1)', 'rgb(230,255,0,0.8)');
        drawConvexHull(vertices_up, 'rgb(255,111,0,0.1)', 'rgb(255,111,0,0.8)');
    }

    // function drawBVH() {
    //     engine.graphics.layers.traverse(layer => {
    //         layer.bvh.traverseLeavesPreOrderUsingStack(drawAABB);
    //     });
    // }

    const special_hits_0 = [];
    const special_hits_1 = [];
    const special_hits_2 = [];

    // ThreeClippingPlaneComputingBVHVisitor.DEBUG_POINTS_0.add((x, y, z) => {
    //     special_hits_0.push(new Vector3(x, y, z));
    // });
    //
    // ThreeClippingPlaneComputingBVHVisitor.DEBUG_POINTS_1.add((x, y, z) => {
    //     special_hits_1.push(new Vector3(x, y, z));
    // });
    // ThreeClippingPlaneComputingBVHVisitor.DEBUG_POINTS_2.add((x, y, z) => {
    //     special_hits_2.push(new Vector3(x, y, z));
    // });

    const buffer = new RingBuffer(100);

    engine.graphics.on.postRender.add(() => {
        // buffer.push(ThreeClippingPlaneComputingBVHVisitor.VISIT_COUNT_LEAF);

        ctx.clearRect(0, 0, canvasView.size.x, canvasView.size.y);

        ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';
        ctx.fillRect(0, 0, canvasView.size.x, canvasView.size.y);

        drawBVH();

        drawCamera(engine.graphics.camera);

        for (let i = 0; i < special_hits_0.length; i++) {
            const hit = special_hits_0[i];

            drawPoint(hit.x, hit.y, hit.z, 'rgb(0,226,255)');
        }

        special_hits_0.splice(0, special_hits_0.length);

        for (let i = 0; i < special_hits_1.length; i++) {
            const hit = special_hits_1[i];

            drawPoint(hit.x, hit.y, hit.z, 'red');
        }

        special_hits_1.splice(0, special_hits_1.length);

        for (let i = 0; i < special_hits_2.length; i++) {
            const hit = special_hits_2[i];

            drawPoint(hit.x, hit.y, hit.z, 'rgb(255,184,0)');
        }

        special_hits_2.splice(0, special_hits_2.length);

        // ThreeClippingPlaneComputingBVHVisitor.VISIT_COUNT_LEAF = 0;
    });

    setInterval(() => {

        const b_checks = computeStatisticalMean(buffer.data, 0, buffer.count);

        console.log(`Leaf visits, mean: ${b_checks.toFixed(2)}`);
    }, 1000);

    return canvasView;
}

/**
 *
 * @param {EngineHarness} harness
 * @return {Promise<void>}
 */
async function init(harness) {
    const engine = harness.engine;

    engine.entityManager.addSystem(new RenderSystem(engine.graphics));

    makeEngineOptionsModel(engine.options, engine);

    await makeConfig(engine).apply(engine);

    enableEditor(engine);

    await harness.initialize();

    main(engine);
}

/**
 *
 * @param {Engine} engine
 */
function main(engine) {
    EngineHarness.buildBasics({
        engine,
        enableWater: false,
        // enableTerrain: false,
        enableTerrain: true,
        terrainSize: new Vector2(100, 100),
        focus: new Vector3(102, 0, 61),
        pitch: 0.5,
        yaw: 2.2,
        distance: 3
        // pitch: -0.12,
        // yaw: 3.1,
        // distance: 5
    });


    engine.graphics.renderer.setClearColor('rgba(99,99,99,1)');


    new Entity()
        .add(Transform.fromJSON({
            position: {
                x: 100,
                y: 3,
                z: 60
            },
            scale: {
                x: 50,
                y: 6,
                z: 30
            }
        }))
        .add(Mesh.fromJSON({
            url: 'data/models/snaps/cube_blue.gltf',
            castShadow: true,
            receiveShadow: true
        }))
        .build(engine.entityManager.dataset);

    const canvasView = makeCameraClippingDebug(engine);

    new Entity()
        .add(ViewportPosition.fromJSON({
            offset: new Vector2(10, 10)
        }))
        .add(GUIElement.fromView(canvasView))
        .build(engine.entityManager.dataset);


    const oc = new OrthographicCamera(-3, 1, -7, 11, 1, 2);
    const f = new Frustum();

    frustum_from_camera(oc, f);

    console.log(f);
}

init(engineHarness);
