import {
    Box3,
    InstancedBufferAttribute,
    InstancedBufferGeometry,
    OctahedronBufferGeometry,
    ShaderMaterial,
    Sphere
} from "three";
import { BinaryDataType } from "../../../../../../../core/binary/type/BinaryDataType.js";
import { AABB3 } from "../../../../../../../core/geom/3d/aabb/AABB3.js";
import { aabb3_from_v3_array } from "../../../../../../../core/geom/3d/aabb/aabb3_from_v3_array.js";
import { v3_length } from "../../../../../../../core/geom/vec3/v3_length.js";
import Entity from "../../../../../../ecs/Entity.js";
import { Transform } from "../../../../../../ecs/transform/Transform.js";
import { ShadedGeometry } from "../../../../../ecs/mesh-v2/ShadedGeometry.js";
import { ShadedGeometryFlags } from "../../../../../ecs/mesh-v2/ShadedGeometryFlags.js";
import { AttributeSpec } from "../../../../../geometry/AttributeSpec.js";
import { geometry_copy } from "../../../../../geometry/instancing/geometry_copy.js";
import { AttributeDataTexture } from "../../../../../texture/AttributeDataTexture.js";
import { makeOctahedralDepthAtlas } from "../../../../gi/material/makeOctahedralDepthAtlas.js";
import { octahedral_depth_to_atlas, vector_buffer_to_attribute_texture } from "../../../probe_volume_to_textures.js";
import fragment from './visualise.frag.glsl'
import vertex from './visualise.vert.glsl'

/**
 *
 * @param {LightProbeVolume} lpv
 */
function makeMaterial(lpv) {
    const depth_atlas = makeOctahedralDepthAtlas();
    const positions = new AttributeDataTexture(
        AttributeSpec.fromJSON({
            name: "positions",
            type: BinaryDataType.Float32,
            itemSize: 3,
            normalized: false
        }),
        256, 1
    );
    vector_buffer_to_attribute_texture(positions, lpv.points, lpv.count, 3);
    octahedral_depth_to_atlas(lpv, depth_atlas.image.data, depth_atlas.image.width);

    const material = new ShaderMaterial({
        fragmentShader: [fragment].join('\n'),
        vertexShader: vertex,
        lights: false,
        uniforms: {
            lpv_t_probe_positions: {
                value: positions.texture
            },
            lpv_t_probe_depth: {
                value: depth_atlas
            },
            lpv_u_probe_depth_resolution:{
                value: lpv.depth_map_resolution
            }
        }
    });

    return material;
}

/**
 *
 * @param {THREE.BufferGeometry} geometry
 * @param {AABB3} bounds
 */
function buffer_geometry_set_bounds_by_aabb3(geometry, bounds) {
    geometry.boundingBox = new Box3()
    geometry.boundingSphere = new Sphere()

    geometry.boundingBox.min.set(bounds.x0, bounds.y0, bounds.z0);
    geometry.boundingBox.max.set(bounds.x1, bounds.y1, bounds.z1);

    bounds.getCenter(geometry.boundingSphere.center);
    geometry.boundingSphere.radius = v3_length(bounds.getHalfExtentsX(), bounds.getHalfExtentsY(), bounds.getHalfExtentsZ());
}

export class VLPDepthMapVisualisation {

    /**
     * @type {LightProbeVolume}
     */
    #volume


    set volume(lpv) {
        this.#volume = lpv;
    }

    /**
     *
     * @param {LightProbeVolume} lpv
     * @returns {VLPDepthMapVisualisation}
     */
    static from(lpv) {
        const r = new VLPDepthMapVisualisation();

        r.volume = lpv;

        return r;
    }

    build() {

        const geometry = new InstancedBufferGeometry();

        const volume = this.#volume;

        geometry.instanceCount = volume.count;

        geometry_copy(geometry, new OctahedronBufferGeometry(0.4, 4));

        const positions_attribute = new InstancedBufferAttribute(new Float32Array(volume.points), 3);
        geometry.setAttribute("instancePosition", positions_attribute);

        const bounds = new AABB3();

        aabb3_from_v3_array(bounds, volume.points, volume.points.length);

        const instance_bounds = new AABB3();

        const geo_position_data = geometry.getAttribute('position').array;
        aabb3_from_v3_array(instance_bounds, geo_position_data, geo_position_data.length);

        // extend by bounds of a single instance
        bounds.x0 += instance_bounds.x0;
        bounds.y0 += instance_bounds.y0;
        bounds.z0 += instance_bounds.z0;

        bounds.x1 += instance_bounds.x1;
        bounds.y1 += instance_bounds.y1;
        bounds.z1 += instance_bounds.z1;

        buffer_geometry_set_bounds_by_aabb3(geometry, bounds);

        const sg = ShadedGeometry.from(geometry, makeMaterial(volume));

        sg.setFlag(ShadedGeometryFlags.DrawMethodLocked);

        const entity = new Entity();

        entity.add(new Transform());
        entity.add(sg);

        return entity;

    }

}