import { ClampToEdgeWrapping, DataTexture, FloatType, LinearFilter, RedFormat } from "three";
import { BvhClient } from "../../../../core/bvh2/bvh3/BvhClient.js";
import { Color } from "../../../../core/color/Color.js";
import Vector1 from "../../../../core/geom/Vector1.js";
import { NumericInterval } from "../../../../core/math/interval/NumericInterval.js";
import { threeUpdateTransform } from "../../util/threeUpdateTransform.js";
import { WATER_SIZE } from "./WATER_SIZE.js";

class Water {
    level = new Vector1(0);
    /**
     *
     * @type {Color}
     */
    color = new Color(0, 0.3, 0.5);


    /**
     * Defines what is considered as shore as well as how long is the transition between shore and deep water
     * @type {NumericInterval}
     */
    shoreDepthTransition = new NumericInterval(0.7, 2);


    /**
     * Color of the water at the shore
     * @type {Color}
     */
    shoreColor = new Color(0.584, 0.792, 0.850);

    /**
     *
     * @type {Vector1}
     */
    waveSpeed = new Vector1(1.8);

    /**
     *
     * @type {Vector1}
     */
    waveAmplitude = new Vector1(0.3);

    /**
     *
     * @type {Vector1}
     */
    waveFrequency = new Vector1(1);

    /**
     * Scattering of light, the higher this value is - the more opaque water will become
     * @type {Vector1}
     */
    scattering = new Vector1(1.2);

    bvh = new BvhClient();

    constructor(options) {

        if (options !== undefined) {
            console.error('Constructor options are deprecated');
            this.fromJSON(options);
        }


        /**
         *
         * @type {ShaderMaterial}
         */
        this.__shader = null;

        /**
         *
         * @type {Object3D}
         * @private
         */
        this.__threeObject = null;

        // monitor changes
        this.color.onChanged.add(this.writeShaderUniforms, this);
        this.shoreDepthTransition.onChanged.add(this.writeShaderUniforms, this);
        this.shoreColor.onChanged.add(this.writeShaderUniforms, this);
        this.waveSpeed.onChanged.add(this.writeShaderUniforms, this);
        this.waveAmplitude.onChanged.add(this.writeShaderUniforms, this);
        this.waveFrequency.onChanged.add(this.writeShaderUniforms, this);
        this.scattering.onChanged.add(this.writeShaderUniforms, this);
    }

    updateTransform() {
        const width = WATER_SIZE;
        const height = WATER_SIZE;

        const mesh = this.__threeObject;

        if (mesh !== null) {

            mesh.rotation.x = Math.PI * 0.5;
            mesh.position.x = width / 4;
            mesh.position.z = height / 4;
            mesh.position.y = this.level.getValue();

            threeUpdateTransform(mesh);
        }

        this.updateBounds();
    }

    updateBounds() {
        const width = WATER_SIZE;
        const height = WATER_SIZE;

        const level = this.level.getValue();

        this.bvh.resize(
            -width / 4,
            level,
            -height / 4,
            width * (3 / 4),
            level,
            height * (3 / 4)
        );
    }

    /**
     *
     * @param {Terrain} terrain
     * @param {number} waterSize
     */
    updateShaderForTerrain(terrain, waterSize) {
        const shader = this.__shader;

        if (shader === null) {
            return;
        }

        const tW = terrain.size.x * terrain.gridScale;
        const tH = terrain.size.y * terrain.gridScale;

        const heightTexture = new DataTexture(terrain.samplerHeight.data, terrain.samplerHeight.width, terrain.samplerHeight.height, RedFormat, FloatType);

        heightTexture.wrapS = ClampToEdgeWrapping;
        heightTexture.wrapT = ClampToEdgeWrapping;

        heightTexture.generateMipmaps = false;

        heightTexture.minFilter = LinearFilter;
        heightTexture.magFilter = LinearFilter;

        heightTexture.flipY = false;

        heightTexture.internalFormat = 'R32F';

        shader.uniforms.tHeightTexture.value = heightTexture;

        shader.uniforms.vHeightUv.value.set(
            -0.25,
            -0.25,
            (waterSize / tW),
            (waterSize / tH)
        );

        shader.uniforms.vHeightTextureResolution.value.set(terrain.samplerHeight.width, terrain.samplerHeight.height);
    }

    writeShaderUniforms() {

        /**
         *
         * @type {ShaderMaterial}
         */
        const shader = this.__shader;

        if (shader === null) {
            //no shader attached
            return;
        }

        const uniforms = shader.uniforms;

        uniforms.waterColor.value.setRGB(this.color.r, this.color.g, this.color.b);
        uniforms.shoreColor.value.setRGB(this.shoreColor.r, this.shoreColor.g, this.shoreColor.b);

        uniforms.fWaveSpeed.value = this.waveSpeed.getValue();

        uniforms.fWaveAmplitude.value = this.waveAmplitude.getValue();

        uniforms.fWaveFrequency.value = this.waveFrequency.getValue();

        uniforms.fScattering.value = this.scattering.getValue();

        uniforms.vShoreDepthTransition.value.set(this.shoreDepthTransition.min, this.shoreDepthTransition.max);


    }

    fromJSON(json) {
        if (typeof json.level === 'number') {
            this.level.fromJSON(json.level);
        }
        if (typeof json.color === 'object') {
            this.color.fromJSON(json.color);
        }
    }

    toJSON() {
        return {
            level: this.level.toJSON(),
            color: this.color.toJSON()
        };
    }
}

Water.typeName = "Water";

export default Water;

