import { BoxBufferGeometry, LineBasicMaterial, MeshBasicMaterial, OctahedronBufferGeometry } from "three";
import { makeHelperBoxGeometry } from "../../../../../editor/process/symbolic/makeHelperBoxGeometry.js";
import { makeHelperSphereGeometry } from "../../../../../editor/process/symbolic/makeHelperSphereGeometry.js";
import { assert } from "../../../../core/assert.js";
import { array_copy } from "../../../../core/collection/array/array_copy.js";
import { compose_matrix4_array } from "../../../../core/geom/3d/mat4/compose_matrix4_array.js";
import Quaternion from "../../../../core/geom/Quaternion.js";
import Vector3 from "../../../../core/geom/Vector3.js";
import { DrawMode } from "../../ecs/mesh-v2/DrawMode.js";
import { InstancedMeshGroup } from "../../geometry/instancing/InstancedMeshGroup.js";
import { Lines } from "../Lines.js";

/**
 *
 * @type {number[]}
 */
const scratch_m4 = [];

/**
 *
 * @param {InstancedMeshGroup} group
 * @param {number[]} matrix
 * @param {number[]} color
 */
function add_instance(group, matrix, color) {
    const i = group.count;

    group.setCount(i + 1);
    group.setTransformAt(i, matrix);

    group.setColorByComponentAt(
        i,
        Math.round(color[0] * 255),
        Math.round(color[1] * 255),
        Math.round(color[2] * 255),
        Math.round(color[3] * 255)
    );

    group.requestAttributeUpdate();
}

/**
 * Inspired by IMGUI and Unity's debug renderer
 */
export class GizmoShapeRenderingInterface {
    constructor() {

        /**
         *
         * @type {number[]|Float32Array}
         * @private
         */
        this.__color = new Float32Array([1, 1, 1, 1]);

        this.__boxes_solid = InstancedMeshGroup.from(new BoxBufferGeometry(), new MeshBasicMaterial());
        this.__boxes_wire = InstancedMeshGroup.from(makeHelperBoxGeometry(), new LineBasicMaterial());

        this.__boxes_wire.draw_mode = DrawMode.LineSegments;

        this.__sphere_solid = InstancedMeshGroup.from(new OctahedronBufferGeometry(1, 10), new MeshBasicMaterial());
        this.__sphere_wire = InstancedMeshGroup.from(makeHelperSphereGeometry(), new LineBasicMaterial());

        this.__sphere_wire.draw_mode = DrawMode.Lines;

        // lines are handles different because they are relatively simple and require 2 archor points
        this.__lines = new Lines();
    }

    __get_groups() {
        return [
            this.__boxes_wire,
            this.__boxes_solid,
            this.__sphere_wire,
            this.__sphere_solid,
            this.__lines
        ];
    }

    /**
     *
     * @param {THREE.Object3D[]} destination
     * @param {number} destination_offset
     * @param {number[]} frustum
     */
    collect(destination, destination_offset, frustum) {
        let i = destination_offset;

        const groups = this.__get_groups();

        for (let j = 0; j < groups.length; j++) {
            destination[i++] = groups[j].mesh;
        }

        return i - destination_offset;
    }

    clear() {
        const groups = this.__get_groups();

        for (let j = 0; j < groups.length; j++) {
            groups[j].setCount(0);
        }

    }

    /**
     * Color used for drawing shapes, draw_x will take on the currently set color when called
     * RGBA
     * @param {number[]} color
     */
    set color(color) {
        assert.defined(color, 'color');
        assert.isArray(color, 'color');
        assert.greaterThanOrEqual(color.length, 4);

        array_copy(color, 0, this.__color, 0, 4);
    }

    /**
     * RGBA
     * @return {number[]}
     */
    get color() {
        return this.__color;
    }

    /**
     *
     * @param {number[]} center
     * @param {number[]} size
     */
    draw_solid_cube(center, size) {
        compose_matrix4_array(scratch_m4, Vector3.fromArray(center), Quaternion.identity, Vector3.fromArray(size));
        add_instance(this.__boxes_solid, scratch_m4, this.__color);
    }

    /**
     *
     * @param {number[]} center
     * @param {number[]} size
     */
    draw_wire_cube(center, size) {
        compose_matrix4_array(scratch_m4, Vector3.fromArray(center), Quaternion.identity, Vector3.fromArray(size));
        add_instance(this.__boxes_wire, scratch_m4, this.__color);
    }

    /**
     *
     * @param {number[]} from
     * @param {number[]} to
     */
    draw_line(from, to) {
        this.__lines.add_line(from, to, this.__color);
    }


    /**
     *
     * @param {number[]} center
     * @param {number} radius
     */
    draw_solid_sphere(center, radius) {
        compose_matrix4_array(scratch_m4, Vector3.fromArray(center), Quaternion.identity, Vector3.fromScalar(radius));
        add_instance(this.__sphere_solid, scratch_m4, this.__color);
    }

    /**
     *
     * @param {number[]} center
     * @param {number} radius
     */
    draw_wire_sphere(center, radius) {
        compose_matrix4_array(scratch_m4, Vector3.fromArray(center), Quaternion.identity, Vector3.fromScalar(radius));
        add_instance(this.__sphere_wire, scratch_m4, this.__color);
    }
}

GizmoShapeRenderingInterface.INSTANCE = new GizmoShapeRenderingInterface();
