import { max2 } from "../../../../../../core/math/max2.js";
import { ceilPowerOfTwo } from "../../../../../../core/binary/operations/ceilPowerOfTwo.js";
import { ClampToEdgeWrapping, FloatType, NearestFilter, RedFormat, WebGLRenderTarget } from "three";
import { renderScreenSpace } from "../../../utils/renderScreenSpace.js";
import { makeReductionShader } from "./ReductionShader.js";
import Vector2 from "../../../../../../core/geom/Vector2.js";

export class HierarchicalZBuffer {
    constructor() {
        /**
         *
         * @type {THREE.Texture|null}
         * @private
         */
        this.__input_buffer = null;

        /**
         *
         * @type {THREE.WebGLRenderer|null}
         * @private
         */
        this.__renderer = null;

        /**
         *
         * @type {THREE.WebGLRenderTarget[]}
         */
        this.mips = [];

        /**
         *
         * @type {ShaderMaterial}
         * @private
         */
        this.__material = makeReductionShader();


        /**
         *
         * @type {Vector2}
         * @private
         */
        this.__resolution = new Vector2(0, 0);

        /**
         *
         * @type {Vector2}
         * @private
         */
        this.__limits = new Vector2(1, 1);
    }

    /**
     *
     * @return {Vector2}
     */
    get mip_limits() {
        return this.__limits;
    }

    /**
     * @param {THREE.WebGLRenderer} v
     */
    set renderer(v) {
        this.__renderer = v;
    }

    /**
     * @param {THREE.Texture} v
     */
    set input(v) {
        this.__input_buffer = v;
    }

    build_mip_chain() {
        const size_max = ceilPowerOfTwo(max2(this.__resolution.x, this.__resolution.y));
        const mip_depth = Math.log2(size_max);

        const mips = this.mips;
        const existing_mip_depth = mips.length;

        if (existing_mip_depth > mip_depth) {

            for (let i = mip_depth; i < existing_mip_depth; i++) {
                mips[i].dispose();
            }

            mips.splice(mip_depth, existing_mip_depth - mip_depth);

        } else {

            for (let i = existing_mip_depth; i < mip_depth; i++) {

                const renderTarget = new WebGLRenderTarget(1, 1, {
                    minFilter: NearestFilter,
                    magFilter: NearestFilter,
                    format: RedFormat,
                    type: FloatType,
                    internalFormat: 'R32F',
                    depthBuffer: false,
                    stencilBuffer: false,
                    wrapS: ClampToEdgeWrapping,
                    wrapT: ClampToEdgeWrapping,
                    generateMipmaps: false
                });

                renderTarget.texture.name = `Hierarchical Z buffer MIP ${i}`;

                mips[i] = renderTarget;

            }

        }
    }

    /**
     *
     * @param {number} x
     * @param {number} y
     */
    set_resolution(x, y) {
        this.__resolution.set(x, y);

        const mips = this.mips;
        const mip_count = mips.length;

        let _x = ceilPowerOfTwo(x);
        let _y = ceilPowerOfTwo(y);

        for (let i = 0; i < mip_count; i++) {
            const mip = mips[i];

            const _nx = max2(1, _x >> 1);
            const _ny = max2(1, _y >> 1);

            if (mip.width !== _nx || mip.height !== _ny) {
                mip.setSize(_nx, _ny);
            }

            if (_nx === 1 && _x !== _nx) {
                this.__limits.x = i;
            }

            if (_ny === 1 && _y !== _ny) {
                this.__limits.y = i;
            }

            _x = _nx;
            _y = _ny;
        }
    }

    build_mips() {
        const mips = this.mips;

        let t0 = null;
        let t1 = {
            texture: this.__input_buffer
        };

        for (let i = 0; i < mips.length; i++) {
            t0 = t1;
            t1 = mips[i];

            this.reduce_mip(t0.texture, t1);
        }

        this.__input_buffer.generateMipmaps = false;
        this.__input_buffer.mips = mips.map(m => m.texture);
    }

    /**
     * @private
     * @param {THREE.Texture} input
     * @param {THREE.WebGLRenderTarget} output
     */
    reduce_mip(input, output) {

        const material = this.__material;
        material.uniforms.source_texture.value = input;
        material.uniforms.destination_resolution.value.set(output.width, output.height);
        material.uniforms.source_resolution.value.set(input.image.width, input.image.height);

        const renderer = this.__renderer;

        const __rt = renderer.getRenderTarget();

        renderer.setRenderTarget(output);

        renderScreenSpace(renderer, material);

        renderer.setRenderTarget(__rt);
    }

    /**
     * Depth texture with mips
     * @returns {THREE.Texture}
     */
    getTexture() {
        return this.__input_buffer;
    }
}
