import {
    BoxBufferGeometry,
    MeshBasicMaterial,
    MeshStandardMaterial,
    OctahedronGeometry,
    SphereBufferGeometry
} from "three";
import { SurfacePoint3 } from "../../../../../core/geom/3d/SurfacePoint3.js";
import Quaternion from "../../../../../core/geom/Quaternion.js";
import Vector2 from "../../../../../core/geom/Vector2.js";
import Vector3 from "../../../../../core/geom/Vector3.js";
import { randomFloatBetween } from "../../../../../core/math/random/randomFloatBetween.js";
import { randomFromArray } from "../../../../../core/math/random/randomFromArray.js";
import { randomIntegerBetween } from "../../../../../core/math/random/randomIntegerBetween.js";
import { seededRandom } from "../../../../../core/math/random/seededRandom.js";
import ButtonView from "../../../../../view/elements/button/ButtonView.js";
import Entity from "../../../../ecs/Entity.js";
import GUIElement from "../../../../ecs/gui/GUIElement.js";
import GUIElementSystem from "../../../../ecs/gui/GUIElementSystem.js";
import ViewportPosition from "../../../../ecs/gui/position/ViewportPosition.js";
import ViewportPositionSystem from "../../../../ecs/gui/position/ViewportPositionSystem.js";
import { Transform } from "../../../../ecs/transform/Transform.js";
import { EngineHarness } from "../../../../EngineHarness.js";
import { BehaviorComponent } from "../../../../intelligence/behavior/ecs/BehaviorComponent.js";
import { BehaviorSystem } from "../../../../intelligence/behavior/ecs/BehaviorSystem.js";
import { OrbitingBehavior } from "../../../../intelligence/behavior/ecs/OrbitingBehavior.js";
import {
    MicronShadedGeometryRenderAdapter
} from "../../../micron/plugin/shaded_geometry/MicronShadedGeometryRenderAdapter.js";
import { MicronRenderContext } from "../../../micron/render/v1/MicronRenderContext.js";
import { ShadedGeometry } from "../ShadedGeometry.js";
import { ShadedGeometryFlags } from "../ShadedGeometryFlags.js";
import { ShadedGeometrySystem } from "../ShadedGeometrySystem.js";

const eh = new EngineHarness();

const DRAW_METHOD_PLAIN = 0;
const DRAW_METHOD_INSTANCED = 1;

function makeMovingShadedGeometry() {
    const b = new OrbitingBehavior();
    b.offset.set(4, 1, 4);
    b.radius = 2;
    b.rate = 1;

    const sg = ShadedGeometry.from(new OctahedronGeometry(0.5, 5), new MeshStandardMaterial({ color: 0xFF0000 }));

    sg.draw_method = DRAW_METHOD_INSTANCED;

    return new Entity()
        .add(new Transform())
        .add(sg)
        .add(BehaviorComponent.from(b));
}

/**
 *
 * @param {Engine} engine
 */
async function main(engine) {

    const em = engine.entityManager;

    const sgs = new ShadedGeometrySystem(engine);

    const mrc = new MicronRenderContext();
    sgs.register_render_adapter(2, () => {
        const adapter = new MicronShadedGeometryRenderAdapter();
        adapter.ctx = mrc;

        return adapter;
    });

    await em.addSystem(sgs);
    await em.addSystem(new BehaviorSystem(engine));

    EngineHarness.buildBasics({
        engine,
        yaw: -0.8,
        pitch: 1,
        distance: 46,
        focus: new Vector3(10, 0, 10)
    });


    const ecd = em.dataset;

    const material = new MeshStandardMaterial({
        color: 0xFFFFFF,
        transparent: true,
        opacity: 0.5
    });

    // material.visible = false;

    window.material = material;

    const random = seededRandom(0);

    const INSTANCE_COUNT = 100;

    const geometries = [];

    for (let i = 0; i < 1; i++) {
        geometries.push(new BoxBufferGeometry());
    }

    for (let i = 0; i < INSTANCE_COUNT; i++) {
        const geometry = randomFromArray(random, geometries);
        const sg = ShadedGeometry.from(geometry, material);

        // sg.draw_method = DRAW_METHOD_PLAIN;
        // sg.draw_method = DRAW_METHOD_INSTANCED;
        // sg.draw_method = 2;
        sg.draw_method = randomIntegerBetween(random, 0, 2);
        // sg.draw_method = random() > 0.01 ? DRAW_METHOD_INSTANCED : DRAW_METHOD_PLAIN;
        sg.clearFlag(ShadedGeometryFlags.CastShadow | ShadedGeometryFlags.ReceiveShadow);

        new Entity()
            .add(Transform.fromJSON({
                position: {
                    x: randomFloatBetween(random, 0, 20),
                    y: randomFloatBetween(random, 0, 10),
                    z: randomFloatBetween(random, 0, 20)
                },
                rotation: Quaternion.random(random),
                scale: 1
            }))
            .add(sg)
            .build(ecd);
    }

    // makeMovingShadedGeometry().build(ecd);

    // debug_button(engine, () => {
    //     const dot = convert_bvh_to_dot_format_string(engine.graphics.layers.getLayerByName('shaded-geometry').bvh);
    //     downloadAsFile(dot, 'bvh.dot', 'text');
    //
    // }, 'save dot file');

    const raycast_marker = new Entity()
        .add(ShadedGeometry.from(new SphereBufferGeometry(1), new MeshBasicMaterial({
            color: 0xFF0000
        })))
        .add(Transform.fromJSON({
            scale: 0.07
        }));

    raycast_marker.build(engine.entityManager.dataset);

    engine.devices.pointer.position.onChanged.add((p_x, p_y) => {
        const p = new Vector2(p_x, p_y);
        const ndc = new Vector2();
        const ray_origin = new Vector3();
        const ray_direction = new Vector3();

        engine.graphics.normalizeViewportPoint(p, ndc);
        engine.graphics.viewportProjectionRay(ndc.x, ndc.y, ray_origin, ray_direction);

        /**
         *
         * @type {ShadedGeometrySystem}
         */
        const sgm = engine.entityManager.getSystem(ShadedGeometrySystem);

        const sp = new SurfacePoint3();

        sgm.raycastNearest(sp, ray_origin.x, ray_origin.y, ray_origin.z, ray_direction.x, ray_direction.y, ray_direction.z, ((entity, mesh) => {
            return (entity !== raycast_marker.id);
        }));

        const t = raycast_marker.getComponent(Transform);

        t.position.copy(sp.position);
    });
}


const entityManager = engine.entityManager;

/**
 *
 * @param {Engine} engine
 * @param {function} action
 * @param {string} [name]
 * @param {EntityComponentDataset} ecd
 */
function debug_button(engine, action, name = "Button", ecd = entityManager.dataset) {
    if (entityManager.getSystem(ViewportPositionSystem) === null) {
        entityManager.addSystem(new ViewportPositionSystem(engine.graphics.viewport.size));
    }

    if (entityManager.getSystem(GUIElementSystem) === null) {
        entityManager.addSystem(new GUIElementSystem(engine.gui.view, engine));
    }

    new Entity()
        .add(ViewportPosition.fromJSON({}))
        .add(GUIElement.fromView(new ButtonView({
            action,
            name,
            css: {
                background: 'rgba(255,255,255,0.8)',
                border: '1px solid black',
                borderRadius: '4px',
                padding: '2px',
            }
        })))
        .build(ecd);
}

eh.initialize().then(main);
