import { THREE } from "threebox-plugin";
import { v4 as uuid } from "uuid";
import ApngLoader from "./apng-loader.js";

export default class Marker {
    constructor(options, scale, zoom) {
        this.uuid = uuid();
        this.id = options.id;
        this.options = options;
        this.scale = scale;
        this.listenerAdded = false;
        this.hasAnimatingTexture = false;
        this.init();
    }

    init() {
        this.initMarkerHeader();
        this.initMarkerBody();
        this.initMarkerBase();
    }

    initMarkerHeader() {
        const headerOptions = this.options.header;
        if (!headerOptions.fragment) return;
        const div = document.createElement("div");
        div.innerHTML = headerOptions.fragment;
        div.dataset.id = this.id;
        for (const key in headerOptions.style) {
            div.style[key] = headerOptions.style[key];
        }
        this.header = tb
            .label({
                htmlElement: div,
                alwaysVisible: true,
                cssClass: `__${this.uuid}__`,
                topMargin: 1
            })
            .setCoords([...this.options.coord, this.options.height]);
        tb.add(this.header);
    }

    initMarkerBody() {
        if (!this.options.body.show) return;
        const projectedHeight = tb
            .projectToWorld([0, 0, 0])
            .distanceTo(tb.projectToWorld([0, 0, this.options.height]));
        const width = this.options.body.width * 0.02;
        const geo = new THREE.CylinderGeometry(
            width,
            width,
            projectedHeight + 1.5,
            20
        );
        const mat = new THREE.MeshBasicMaterial({
            color: this.options.body.color,
        });
        const mesh = new THREE.Mesh(geo, mat);
        this.body = tb
            .Object3D({
                obj: mesh,
            })
            .setCoords([...this.options.coord, 0]);
        this.body.rotateOnAxis(new THREE.Vector3(1, 0, 0), -Math.PI / 2);
        tb.add(this.body);
    }

    async initMarkerBase() {
        const { coord, base: baseOptions } = this.options;
        if (!baseOptions.image) return;
        try {
            // is APNG
            const loader = new ApngLoader();
            const { canvas } = await loader.load(baseOptions.image);
            this.texture = new THREE.CanvasTexture(canvas);
            this.hasAnimatingTexture = true;
        } catch (e) {
            if (
                e === "Not an animated PNG" ||
                e === "Not a PNG file (invalid file signature)"
            ) {
                // is normal image
                this.texture = new THREE.Texture(baseOptions.image);
            } else {
                throw e;
            }
        }

        this.texture.minFilter = THREE.LinearFilter;
        this.texture.needsUpdate = true;

        const geo = new THREE.PlaneGeometry(...baseOptions.size);
        const mat = new THREE.MeshBasicMaterial({
            map: this.texture,
            transparent: true,
            depthWrite: false,
            depthTest: false,
        });
        const mesh = new THREE.Mesh(geo, mat);
        const obj = tb
            .Object3D({ obj: mesh, anchor: "center" })
            .setCoords([coord[0], coord[1], 1]);
        tb.add(obj);
    }

    updateTexture() {
        if (!this.texture) return;
        this.texture.needsUpdate = true;
    }

    updateScale(curZoom) {
        const el = document.querySelector(`div[data-id="${this.id}"]`);
        if (!el) return;
        const scale = this.scale.get(curZoom);
        el.style.transform = `scale(${scale})`;
    }

    addListeners() {
        if (this.listenerAdded) return true;
        const container = document.querySelector(`div[data-id="${this.id}"]`);
        if (!container) return false;
        container.addEventListener("click", (event) => {
            this.options.onclick({
                ...this.options,
                event,
            });
        });
        this.listenerAdded = true;
        return true;
    }
}
