import { RepeatWrapping, TextureLoader } from "three";
import Vector2 from '../../../core/geom/Vector2.js';

const texture_cache = new Map();

/**
 *
 * @param {string} url
 * @returns {Texture}
 */
function loadCloudTexture(url) {
    const existing = texture_cache.get(url);

    if (existing !== undefined) {
        return existing;
    }

    const textureLoader = new TextureLoader();
    const t = textureLoader.load(url);
    t.wrapS = RepeatWrapping;
    t.wrapT = RepeatWrapping;
    t.repeat.set(1, 1);

    texture_cache.set(url, t);

    return t;
}


class Clouds {
    /**
     *
     * @type {boolean}
     * @private
     */
    __enabled = false;
    /**
     *
     * @type {Array}
     * @private
     */
    materials = [];
    time = 0;


    __speed0 = new Vector2();
    __speed1 = new Vector2();
    __speed2 = new Vector2();

    //how fast clouds reform
    variability = 0.37;

    constructor() {
        this.setSpeed(0.5, -0.5);
    }

    /**
     *
     * @param {boolean} val
     */
    set enabled(val) {

        if (val === this.__enabled) {
            return;
        }
        this.__enabled = val;
        this.materials.forEach(this.writeOneEnabled.bind(this));
    }

    /**
     *
     * @returns {boolean}
     */
    get enabled() {
        return this.__enabled;
    }

    /**
     *
     * @param {number} x
     * @param {number} y
     */
    setSpeed(x, y) {
        const variability = this.variability;

        this.__speed0.set(x, y);
        this.__speed1.set(x * (1 - variability), y * (1 + variability));
        this.__speed2.set(x * (1 + variability), y * (1 - variability));
    }

    /**
     *
     * @param {THREE.Material} mat
     */
    writeOneCloudSpeed(mat) {
        const uniforms = mat.uniforms;

        uniforms.v_CloudsSpeed_0.value.copy(this.__speed0);
        uniforms.v_CloudsSpeed_1.value.copy(this.__speed1);
        uniforms.v_CloudsSpeed_2.value.copy(this.__speed2);
    }

    /**
     *
     * @param {THREE.Material} mat
     */
    writeOneEnabled(mat) {
        mat.defines.SHADOWMAP_CLOUDS = this.__enabled;
        mat.needsUpdate = true;
    }

    /**
     *
     * @param {THREE.Material} mat
     */
    writeOneTime(mat) {
        const uniforms = mat.uniforms;
        uniforms.f_CloudsTime.value = this.time;
    }


    writeAllTime() {
        const materials = this.materials;
        const l = materials.length;

        for (let i = 0; i < l; i++) {
            const m = materials[i];
            this.writeOneTime(m);
        }
    }

    /**
     *
     * @param {number} timeDelta
     */
    update(timeDelta) {
        this.time += timeDelta;
        this.writeAllTime();
    }

    /**
     *
     * @param {THREE.Material} mat
     */
    writeOneCloudTextures(mat) {
        const uniforms = mat.uniforms;
        uniforms.t_Clouds_0.value = loadCloudTexture("data/textures/noise/tile_256.png");
        uniforms.t_Clouds_1.value = loadCloudTexture("data/textures/noise/tile_256.png");
        uniforms.t_Clouds_2.value = loadCloudTexture("data/textures/noise/tile_256.png");
    }

    /**
     *
     * @param {THREE.Material} mat
     */
    writeOneCloudUniforms(mat) {

        const uniforms = mat.uniforms;
        uniforms.v_CloudsSize_0.value.set(161, 161);
        uniforms.v_CloudsSize_1.value.set(83, 83);
        uniforms.v_CloudsSize_2.value.set(23, 23);

        uniforms.f_CloudsAmount.value = 0.8;
        uniforms.f_CloudsIntensity.value = 0.2;

        this.writeOneCloudSpeed(mat);
    }

    /**
     *
     * @param {THREE.Material} material
     * @returns {boolean}
     */
    removeMaterial(material) {
        const i = this.materials.indexOf(material);
        if (i === -1) {
            return false;
        }

        this.materials.splice(i, 1);

        return true;
    }

    /**
     *
     * @param {THREE.Material} material
     */
    addMaterial(material) {
        if (
            typeof material.defines === "object"
            && material.defines.hasOwnProperty("SHADOWMAP_CLOUDS")
        ) {
            this.writeOneTime(material);
            this.writeOneEnabled(material);
            this.writeOneCloudTextures(material);
            this.writeOneCloudUniforms(material);

            this.materials.push(material);
        } else {
            console.warn("material doesn't support clouds, ignoring");
        }
    }
}


export default Clouds;
