import { Cache } from "../../../../../core/cache/Cache.js";
import Signal from "../../../../../core/events/signal/Signal.js";
import Vector2 from "../../../../../core/geom/Vector2.js";
import { computeStringHash } from "../../../../../core/primitives/strings/computeStringHash.js";
import { string_compute_byte_size } from "../../../../../core/primitives/strings/string_compute_byte_size.js";
import { GameAssetType } from "../../../../asset/GameAssetType.js";
import { Sampler2D } from "../../../../graphics/texture/sampler/Sampler2D.js";
import { sampler2d_ensure_uint8_RGBA } from "../../../../graphics/texture/sampler/sampler2d_ensure_uint8_RGBA.js";

/**
 *
 * @type {Cache<String,Sampler2D>}
 */
const cache = new Cache({
    maxWeight: 304857600,
    keyHashFunction: computeStringHash,
    keyWeigher: string_compute_byte_size,
    /**
     *
     * @param {Sampler2D} sampler
     * @returns {number}
     */
    valueWeigher(sampler) {
        return sampler.computeByteSize();
    }
});

export class TerrainLayer {

    /**
     *
     * @type {String}
     */
    textureDiffuseURL = "";

    /**
     *
     * @type {Sampler2D}
     */
    diffuse = Sampler2D.uint8(4, 1, 1);

    /**
     *
     * @type {Vector2}
     */
    size = new Vector2(1, 1);

    /**
     * Additional free-form JSON metadata
     * @type {Object}
     */
    extra = {};

    /**
     *
     * @type {Signal}
     */
    onChanged = new Signal();


    toJSON() {
        return {
            textureDiffuseURL: this.textureDiffuseURL,
            size: this.size.toJSON(),
            extra: this.extra
        };
    }

    fromJSON({ textureDiffuseURL, size, extra = {} }) {
        this.textureDiffuseURL = textureDiffuseURL;
        this.size.fromJSON(size);
        this.extra = extra;
    }

    /**
     *
     * @param {string} url
     * @param {number} width
     * @param {number} height
     */
    static from(url, width, height) {
        const r = new TerrainLayer();

        r.textureDiffuseURL = url;
        r.size.set(width, height);

        return r;
    }

    /**
     *
     * @param {TerrainLayerDescription} d
     * @returns {TerrainLayer}
     */
    static fromDescription(d) {
        return TerrainLayer.from(
            d.diffuse,
            d.size.x,
            d.size.y
        );
    }

    /**
     *
     * @param {AssetManager} assetManager
     * @returns {Promise}
     */
    loadTextureData(assetManager) {

        const path = this.textureDiffuseURL;

        /**
         *
         * @type {Sampler2D|null}
         */
        const cached = cache.get(path);

        if (cached !== null) {

            console.log('Using cached terrain layer data for ', path);

            this.diffuse.resize(cached.width, cached.height, false);

            this.diffuse.data.set(cached.data);

            this.onChanged.send0();

            return Promise.resolve();

        }

        const assetPromise = assetManager.promise(path, GameAssetType.Image);

        return assetPromise
            .then(asset => {
                const image = asset.create();

                const source_data = image.data;
                const source_item_size = image.itemSize;

                // RGBA (4 channels) for AMD GPU compatibility
                const destination_item_size = 4;

                const source = new Sampler2D(source_data, source_item_size, image.width, image.height);

                // Convert to uint8 RGBA, handling any source format
                const rgba = sampler2d_ensure_uint8_RGBA(source);

                if (this.diffuse === null || this.diffuse.width !== image.width || this.diffuse.height !== image.height) {
                    this.diffuse = Sampler2D.uint8(destination_item_size, image.width, image.height);
                }

                this.diffuse.data.set(rgba.data);

                this.onChanged.send0();

                // write to cache
                const cacheCopy = Sampler2D.uint8(destination_item_size, image.width, image.height);

                cacheCopy.data.set(this.diffuse.data);

                cache.put(path, cacheCopy);

            });
    }
}

/**
 * @readonly
 * @type {boolean}
 */
TerrainLayer.prototype.isTerrainLayer = true;