import { Mesh, MeshBasicMaterial, NearestFilter, PlaneBufferGeometry } from "three";
import { Cache } from "../../../core/cache/Cache.js";
import { returnOne } from "../../../core/function/returnOne.js";
import { strictEquals } from "../../../core/function/strictEquals.js";
import Vector2 from "../../../core/geom/Vector2.js";
import ObservedValue from "../../../core/model/ObservedValue.js";
import { GameAssetType } from "../../../engine/asset/GameAssetType.js";
import { MinimapWorldLayer } from "./MinimapWorldLayer.js";

export class MinimapTerrainGL extends MinimapWorldLayer {
    /**
     *
     * @param {AssetManager} assetManager
     */
    constructor({ assetManager }) {
        super();

        const textureCache = new Cache({
            maxWeight: 2,
            keyEqualityFunction: strictEquals,
            keyHashFunction: returnOne,
        });

        const geometry = new PlaneBufferGeometry(1, 1, 1, 1);
        const material = new MeshBasicMaterial({ color: 0xFFFFFF });

        const mesh = new Mesh(geometry, material);
        mesh.rotation.x = -Math.PI * 0.5;

        const self = this;

        /**
         *
         * @returns {TerrainPreview}
         */
        function getPreview() {
            const terrain = self.terrain.getValue();

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

            /**
             *
             * @type {TerrainPreview}
             */
            const preview = terrain.preview;

            return preview;
        }

        function setTexture(texture) {


            material.map = texture;


            updateMesh();

            // trigger a render request
            self.needsRender = true;
        }

        function updateTexture() {
            const url = getPreview().url;

            //check cache
            const cachedTexture = textureCache.get(url);

            if (cachedTexture !== null) {
                //use cached texture
                setTexture(cachedTexture);
            } else {

                //get texture from asset manager
                assetManager.promise(url, GameAssetType.Texture)
                    .then(function (asset) {
                        /**
                         *
                         * @type {Texture}
                         */
                        const texture = asset.create();

                        texture.flipY = true;

                        // mipmaps are necessary for a decent performance when zooming out of the map
                        texture.generateMipamps = true;

                        texture.magFilter = NearestFilter; // don't blur pixels

                        material.needsUpdate = true;

                        setTexture(texture);

                        textureCache.put(url, texture);
                    });
            }
        }

        function updateMesh() {
            const terrain = self.terrain.getValue();

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

            const resolution = new Vector2(2048, 2048);

            if (material.map !== null) {
                /**
                 * @type {Image}
                 */
                const image = material.map.image;

                const naturalWidth = image.naturalWidth;
                const naturalHeight = image.naturalHeight;

                resolution.set(naturalWidth, naturalHeight);
            }

            /**
             *
             * @type {TerrainPreview}
             */
            const preview = terrain.preview;

            const scale = preview.scale;
            const offset = preview.offset;

            const mLeft = offset.x * scale.x;
            const mTop = offset.y * scale.y;

            const nX = resolution.x * scale.x;
            const nY = resolution.y * scale.y;

            mesh.scale.set(nX, nY, 1);

            mesh.position.set(-mLeft + nX / 2, 1, -mTop + nY / 2);


            // trigger a render request
            self.needsRender = true;
        }

        this.object = mesh;

        /**
         *
         * @type {ObservedValue<Terrain>}
         */
        this.terrain = new ObservedValue(null);

        /**
         *
         * @param {Terrain} terrain
         * @param {Terrain} oldTerrain
         * @private
         */
        this.__setTerrain = function setTerrain(terrain, oldTerrain) {
            if (terrain !== null) {
                terrain.preview.offset.onChanged.add(updateMesh);
                terrain.preview.scale.onChanged.add(updateMesh);

                updateMesh();
                updateTexture();
            }

            if (oldTerrain !== null) {
                oldTerrain.preview.offset.onChanged.remove(updateMesh);
                oldTerrain.preview.scale.onChanged.remove(updateMesh);
            }
        };


    }

    startup() {
        this.terrain.onChanged.add(this.__setTerrain);

        this.__setTerrain(this.terrain.getValue(), null);
    }

    shutdown() {
        this.terrain.onChanged.remove(this.__setTerrain);

        this.__setTerrain(null, this.terrain.getValue());
    }
}
