import { assert } from "../../../../core/assert.js";
import { array_shift_back } from "../../../../core/collection/array/array_shift_back.js";
import {
    compute_delaunay_tetrahedral_mesh
} from "../../../../core/geom/3d/tetrahedra/delaunay/compute_delaunay_tetrahedral_mesh.js";
import {
    tetrahedral_mesh_build_from_grid
} from "../../../../core/geom/3d/tetrahedra/delaunay/grid/tetrahedral_mesh_build_from_grid.js";
import { TetrahedralMesh } from "../../../../core/geom/3d/tetrahedra/TetrahedralMesh.js";


/**
 * Irradiance field representation
 * @see "Irradiance Volumes" by G.Greger, 1998
 */
export class LightProbeVolume {
    #version = 0;

    /**
     *
     * @type {number[]}
     */
    #probe_positions = [];

    #mesh = new TetrahedralMesh();

    /**
     * Probe irradiance color data
     * Base-3 spherical harmonics, 9 coefficients per color channel for a total of 27 coefficients per probe
     * @type {number[]}
     */
    #sh3_rgb = [];

    /**
     * Resolution of an individual depth map for a probe
     * Depth map is stored as a 2d texture of size RESOLUTION x RESOLUTION
     * @type {number}
     */
    #probe_depth_resolution = 12;

    /**
     * Octahedral-encoded depth map for each probe, 2 channels for mean and mean squared for variance recovery in the shader
     * @type {number[]}
     */
    #probe_depth = [];

    #probe_count = 0;

    /**
     *
     * @return {number}
     */
    get version() {
        return this.#version;
    }

    clear() {
        if (this.#probe_positions.length > 0) {
            this.#probe_positions = [];
        }

        if (this.#sh3_rgb.length > 0) {
            this.#sh3_rgb = [];
        }

        if (this.#probe_depth.length > 0) {
            this.#probe_depth = [];
        }

        this.#mesh.clear();

        this.#probe_count = 0;

        this.#version++;
    }

    /**
     *
     * @return {TetrahedralMesh}
     */
    get mesh() {
        return this.#mesh;
    }

    get points() {
        return this.#probe_positions;
    }

    get harmonics() {
        return this.#sh3_rgb;
    }

    get depth_map_resolution() {
        return this.#probe_depth_resolution;
    }

    /**
     *
     * @param {number} v
     */
    set depth_map_resolution(v) {
        assert.isNonNegativeInteger(v, 'v');

        this.#probe_depth_resolution = v;
    }

    get depth() {
        return this.#probe_depth;
    }

    get count() {
        return this.#probe_count;
    }

    set count(v) {
        this.#probe_count = v;
        this.#ensure_capacity(v);
    }

    /**
     *
     * @param {number} capacity
     */
    #ensure_capacity(capacity) {
        const current_capacity = this.#probe_positions.length / 3;

        if (current_capacity >= capacity) {
            return;
        }

        const new_capacity = Math.max(
            current_capacity + 16,
            Math.ceil(current_capacity * 1.2),
            capacity
        );

        const new_positions = new Float32Array(new_capacity * 3);
        new_positions.set(this.#probe_positions);

        const new_depth = new Float32Array(new_capacity * this.#compute_depth_element_size());
        new_depth.set(this.#probe_depth);

        const new_color = new Float32Array(new_capacity * 27);
        new_color.set(this.#sh3_rgb);

        this.#probe_positions = new_positions;
        this.#probe_depth = new_depth;
        this.#sh3_rgb = new_color;
    }

    /**
     *
     * @param {number} x
     * @param {number} y
     * @param {number} z
     * @returns {number}
     */
    add_point(x, y, z) {
        const i = this.#probe_count;

        this.#ensure_capacity(i + 1);

        const i3 = i * 3;

        this.#probe_positions[i3] = x;
        this.#probe_positions[i3 + 1] = y;
        this.#probe_positions[i3 + 2] = z;

        for (let j = 0; j < 9 * 3; j++) {
            this.#sh3_rgb[i3 * 9 + j] = 1; // fill with white
        }

        this.#probe_count++;
        this.#version++;

        return i;
    }

    #compute_depth_element_size() {
        return this.#probe_depth_resolution * this.#probe_depth_resolution * 2;
    }

    /**
     *
     * @param {number} index
     */
    remove_point(index) {
        array_shift_back(this.#probe_positions, (index + 1) * 3, 3, (this.#probe_count - index - 1) * 3);
        array_shift_back(this.#sh3_rgb, (index + 1) * 27, 27, (this.#probe_count - index - 1) * 27);

        const depth_element_count = this.#compute_depth_element_size();
        array_shift_back(this.#probe_depth, (index + 1) * depth_element_count, depth_element_count, (this.#probe_count - index - 1) * depth_element_count);

        this.#probe_count--;

        this.#version++;
    }

    /**
     *
     * @param {AABB3} bounds
     * @param {Vector3|{x:number, y:number, z:number}} resolution
     */
    build_grid(bounds, resolution) {
        this.#probe_count = tetrahedral_mesh_build_from_grid(
            this.#mesh, this.#probe_positions,
            bounds, resolution.x, resolution.y, resolution.z
        );
    }

    /**
     * Build tetrahedral mesh
     */
    build_mesh() {
        this.#mesh.clear();

        const t0 = performance.now();
        compute_delaunay_tetrahedral_mesh(this.#mesh, this.#probe_positions, this.#probe_count);

        const t1 = performance.now();

        // optional step to improve memory utilization
        this.#mesh.compact();

        const t2 = performance.now();

        console.log(`Tetrahedral mesh (${this.#probe_count} points, ${this.#mesh.count} tets) build took ${t2 - t0}ms`);
    }


    incrementVersion() {
        this.#version++;
    }

}

