import { LoadingCache } from "../../../../core/cache/LoadingCache.js";
import { strictEquals } from "../../../../core/function/strictEquals.js";
import { computeStringHash } from "../../../../core/primitives/strings/computeStringHash.js";
import { GameAssetType } from "../../../asset/GameAssetType.js";
import { Reference } from "../../../reference/v2/Reference.js";
import { Sampler2D } from "../sampler/Sampler2D.js";
import { sampler2d_ensure_uint8_RGBA } from "../sampler/sampler2d_ensure_uint8_RGBA.js";
import { CachingTextureAtlas } from "./CachingTextureAtlas.js";
import { TextureAtlas } from "./TextureAtlas.js";

export class ManagedAtlas {

    /**
     *
     * @type {AssetManager|null}
     */
    #assets = null;

    /**
     *
     * @type {Map<string, Reference>}
     */
    #references = new Map();

    /**
     * Pixel padding
     * @type {number}
     */
    #padding = 4;

    /**
     * @type {LoadingCache<string, AtlasPatch>}
     */
    #patch_loader;

    /**
     *
     * @type {Map<string, AtlasPatch>}
     */
    #url_patch_lookup = new Map();
    /**
     *
     * @type {Map<AtlasPatch,string[]>}
     */
    #patch_url_lookup = new Map();

    /**
     *
     * @param {AssetManager} assetManager
     * @constructor
     */
    constructor(assetManager) {
        this.#assets = assetManager;

        /**
         * pre-allocate space on the atlas to avoid some initial re-sizing
         * @type {TextureAtlas}
         */
        const atlas = new TextureAtlas(64);


        this.__caching_atlas = new CachingTextureAtlas({ atlas });

        /**
         *
         * @type {TextureAtlas}
         */
        this.atlas = atlas;

        atlas.on.removed.add(this.#remove_from_lookup, this);

        this.#patch_loader = new LoadingCache({
            keyHashFunction: computeStringHash,
            keyEqualityFunction: strictEquals,
            load: async (url) => {
                const sampler = await this.#resolve_texture(url);

                const patch = this.__caching_atlas.add(sampler, this.#padding);

                this.#add_to_lookup(url, patch);

                return patch;
            }
        });
    }

    /**
     *
     * @param {AtlasPatch} patch
     */
    #remove_from_lookup(patch) {
        const urls = this.#patch_url_lookup.get(patch);

        for (let i = 0; i < urls.length; i++) {
            const url = urls[i];

            this.#url_patch_lookup.delete(url);
        }
    }

    /**
     *
     * @param {string} url
     * @param {AtlasPatch} patch
     */
    #add_to_lookup(url, patch) {

        this.#url_patch_lookup.set(url, patch);

        let urls = this.#patch_url_lookup.get(patch);
        if (urls === undefined) {
            urls = [];
            this.#patch_url_lookup.set(patch, urls);
        }
        urls.push(url);
    }

    /**
     *
     * @param {string} url
     * @returns {Promise<Sampler2D>}
     */
    async #resolve_texture(url) {
        const asset = await this.#assets.promise(url, GameAssetType.Image);
        const source = asset.create();

        return sampler2d_ensure_uint8_RGBA(source);
    }

    reset() {
        this.#references.clear();
        this.__caching_atlas.reset();
        this.#url_patch_lookup.clear();
    }

    /**
     *
     * @param {string} key
     * @returns {Promise<Reference<AtlasPatch>>}
     */
    async acquire(url) {
        const reference = new Reference();

        let patch = this.#url_patch_lookup.get(url);

        if (patch === undefined) {
            // patch is not active, need to load it
            patch = await this.#patch_loader.get(url);
        }

        reference.bind(patch);

        return reference;
    }

    /**
     *
     * @param {string} key
     */
    release(key) {
        throw  new Error('Method is deprecated, use "Reference.release" instead');
    }
}
