import { HandRenderer, HandFitPlugin, FingersFitPlugin, RingFitPlugin,
    FingersGeometry, OccluderMaterial, RefractionMaterial,
    isMesh, isMeshStandardMaterial } from "@geenee/bodyrenderers-three";
import { CanvasMode } from "@geenee/armature";
import { Scene } from "three/src/scenes/Scene";
import { Object3D } from "three/src/core/Object3D";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader";
import { EquirectangularReflectionMapping } from "three/src/constants";
import { Color } from "three/src/math/Color";

export class RingRenderer extends HandRenderer {
    // Scene
    protected aligner: HandFitPlugin;
    protected handfit: FingersFitPlugin;
    protected ringfit: RingFitPlugin;
    protected geometry?: FingersGeometry;
    protected hand?: Object3D;
    protected ring?: Object3D;

    constructor(
        container: HTMLElement,
        mode?: CanvasMode,
        mirror?: boolean,
        protected url?: string) {
        super(container, mode, mirror);
        this.aligner = new HandFitPlugin();
        this.addPlugin(this.aligner);
        this.handfit = new FingersFitPlugin();
        this.addPlugin(this.handfit);
        this.ringfit = new RingFitPlugin();
        this.addPlugin(this.ringfit);
    }

    async load() {
        if (this.loaded || !this.scene)
            return;
        await this.setupScene(this.scene);
        await super.load();
    }

    async setupScene(scene: Scene) {
        const environment = await new RGBELoader().loadAsync("environment1.hdr");
        environment.mapping = EquirectangularReflectionMapping;
        scene.environment = environment;
        scene.environmentIntensity = 0.65;
        // Hand geometry
        const occluderMat = new OccluderMaterial();
        this.geometry = FingersFitPlugin.buildGeometry(scene, occluderMat);
        this.handfit.setGeometry(this.geometry);
        // Hand occluder
        const gltf = await new GLTFLoader().loadAsync("hand.glb");
        this.hand = gltf.scene;
        this.hand.traverse((m) => {
            if (!isMesh(m))
                return
            if (/body/i.test(m.name)) {
                let material = m.material;
                m.material = new OccluderMaterial("occluderMaterial");
                m.renderOrder = -1;
                if (!(material instanceof Array))
                    material = [material];
                material.forEach((mat) => mat.dispose());
            }
        });
        scene.add(this.hand);
        this.aligner.setNode(this.hand);
        // Model
        return this.setModel(this.url);
    }

    async setModel(url?: string) {
        this.ringfit.setNode();
        this.ring && this.disposeObject(this.ring);
        delete this.ring;
        this.url = url;
        const { scene } = this;
        if (!this.url || !scene)
            return;
        const ring = await new GLTFLoader().loadAsync(this.url);
        this.ring = ring.scene;
        this.ring.traverse((m) => {
            if (!isMesh(m))
                return;
            m.receiveShadow = true;
            m.castShadow = true;
            if (/gem/i.test(m.name) && scene.environment) {
                const mat = m.material instanceof Array ? m.material[0] : m.material;
                let color = isMeshStandardMaterial(mat) ? mat.color : new Color(0xe0e0f0);
                if (color.equals(new Color(0xFFFFFF)))
                    color = new Color(0xe0e0f0);
                m.material = new RefractionMaterial(
                    scene.environment, color, 0.95);
            }
        });
        scene.add(this.ring);
        this.ringfit.setNode(this.ring, "middle");
    }
}
