import {
    BufferGeometry,
    Float32BufferAttribute,
    Group,
    Line,
    LineBasicMaterial,
    Mesh as ThreeMesh,
    MeshLambertMaterial,
    TetrahedronBufferGeometry
} from "three";
import Vector3 from "../../../core/geom/Vector3.js";
import Entity from "../../../engine/ecs/Entity.js";
import GUIElement from "../../../engine/ecs/gui/GUIElement.js";
import HeadsUpDisplay from "../../../engine/ecs/gui/hud/HeadsUpDisplay.js";
import ViewportPosition from "../../../engine/ecs/gui/position/ViewportPosition.js";
import Renderable from "../../../engine/ecs/renderable/Renderable.js";
import { obtainTerrain } from "../../../engine/ecs/terrain/util/obtainTerrain.js";
import { Transform } from "../../../engine/ecs/transform/Transform.js";
import LabelView from "../../../view/common/LabelView.js";
import { MarkerNodeMatcherAny } from "../matcher/MarkerNodeMatcherAny.js";

function formatValue(v) {

    if (typeof v === "number" && v % 1 !== 0) {
        return v.toPrecision(3);
    }

    return v;

}

function makeCircleGeometry(radius, arc) {

    const geometry = new BufferGeometry();
    const vertices = [];

    for (let i = 0; i <= 64 * arc; ++i) {

        vertices.push(Math.cos(i / 32 * Math.PI) * radius, 0, Math.sin(i / 32 * Math.PI) * radius);

    }

    geometry.setAttribute('position', new Float32BufferAttribute(vertices, 3));

    return geometry;

}

/**
 *
 * @param {GridData} grid
 * @param {EntityComponentDataset} ecd
 * @param {MarkerNodeMatcher} marker_filter
 * @param {boolean} [display_data]
 */
export function visualizeMarkers({
                                     grid,
                                     ecd,
                                     marker_filter = MarkerNodeMatcherAny.INSTANCE,
                                     display_data = true
                                 }) {

    /**
     *
     * @type {MarkerNode[]}
     */
    const markers = [];

    grid.markers.getRawData(markers);

    const n = markers.length;

    const m0 = new MeshLambertMaterial({ color: 0xFFFFFF });
    const m1 = new MeshLambertMaterial({ color: 0xFF0000, transparent: true, opacity: 0.5 });

    const m3 = new LineBasicMaterial({ color: 0xFF0000, transparent: true, opacity: 0.3, depthTest: false });

    const m_line = new LineBasicMaterial({
        color: 0xffffff,
        linewidth: 1,
        transparent: true,
        depthTest: false,
        opacity: 0.5,
        linecap: 'round', //ignored by WebGLRenderer
        linejoin: 'round' //ignored by WebGLRenderer
    });

    /**
     *
     * @type {Terrain}
     */
    const terrain = obtainTerrain(ecd);

    const marker_radius = 0.05;
    const geometry_0 = new TetrahedronBufferGeometry(marker_radius, 2);

    const geometry_cylinder = makeCircleGeometry(1, 1);

    const v3 = new Vector3();

    for (let i = 0; i < n; i++) {
        const markerNode = markers[i];

        if (!marker_filter.match(markerNode)) {
            // reject marker
            continue;
        }

        const g = new Group();

        const mark_0 = new ThreeMesh(geometry_0, m0);

        const mark_1 = new ThreeMesh(geometry_0, m1);

        terrain.mapPointGrid2World(markerNode.position.x, markerNode.position.y, v3);

        mark_1.position.copy(markerNode.transform.position);
        mark_1.position.sub(v3);

        const mark_size = new Line(geometry_cylinder, m3);
        mark_size.scale.set(markerNode.size, markerNode.size, markerNode.size);


        g.add(mark_0);

        if (mark_1.position.distanceToSquared(mark_0.position) > marker_radius * marker_radius * 0.2) {
            // only draw second marker if they are sufficiently far apart

            const line_geometry = new BufferGeometry();
            line_geometry.setFromPoints([
                mark_0.position,
                mark_1.position
            ]);

            const mark_line = new Line(line_geometry, m_line);

            g.add(mark_1);
            g.add(mark_line);
        }

        g.add(mark_size);

        const entityBuilder = new Entity();

        const renderable = new Renderable(g);
        renderable.computeBoundsFromObject();

        if (display_data) {
            const props = [];

            props.push(`TYPE: ${markerNode.type}`);

            for (const propertyKey in markerNode.properties) {
                let propValue = formatValue(markerNode.properties[propertyKey]);

                props.push(`${propertyKey}:${propValue}`);
            }

            props.push(`# [x: ${formatValue(markerNode.position.x)}, y: ${formatValue(markerNode.position.y)}]`);
            props.push(`# [size: ${formatValue(markerNode.size)}]`);

            for (let j = 0; j < markerNode.tags.length; j++) {
                const tag = markerNode.tags[j];

                props.push(`# T: ${tag}`);
            }


            const v = new LabelView(props.join('\n'), { classList: ['__debug-plaque'] });
            v.css({
                position: 'absolute',
                whiteSpace: 'pre',
                left: 0,
                top: 0
            })

            entityBuilder
                .add(new HeadsUpDisplay())
                .add(new ViewportPosition())
                .add(GUIElement.fromView(v))
            ;
        }

        const t = new Transform();

        t.position.copy(v3);


        entityBuilder
            .add(renderable)
            .add(t)
            .build(ecd);

    }

}
