<template>
    <v-navigation-drawer width="133">
        <v-btn-toggle variant="tonal" density="comfortable" class="ma-3" v-model="renderType" color="light-blue-darken-4">
            <v-btn value="ST" icon="mdi-video-3d-variant"></v-btn>
            <v-btn value="VR" icon="mdi-google-cardboard"></v-btn>
            <v-btn value="AG" icon="mdi-glasses"></v-btn>
        </v-btn-toggle>
        <v-btn-toggle variant="tonal" density="comfortable" class="ma-3" v-model="mode" color="green-darken-4">
            <v-btn class="text-none" value="translate" icon="mdi-cursor-move"></v-btn>
            <v-btn class="text-none" value="rotate" icon="mdi-rotate-orbit"></v-btn>
            <v-btn class="text-none" value="scale" icon="mdi-resize"></v-btn>
        </v-btn-toggle>
        <v-btn-toggle variant="tonal" v-model="cameraType" class="ma-3" density="comfortable" color="orange-darken-4">
            <v-btn value="perspective" icon="mdi-cone"></v-btn>
            <v-btn value="orthographic" icon="mdi-cone-off"></v-btn>
        </v-btn-toggle>
        <div v-if="currentObject">
            <v-card subtitle="Position" class="ma-1" color="light-blue-darken-4">
                <v-number-input :precision="null" controlVariant="stacked" hide-details density="compact" label="x" v-model="currentObject.position.x" :step="0.1"></v-number-input>
                <v-number-input :precision="null" controlVariant="stacked" hide-details density="compact" label="y" v-model="currentObject.position.y" :step="0.1"></v-number-input>
                <v-number-input :precision="null" controlVariant="stacked" hide-details density="compact" label="z" v-model="currentObject.position.z" :step="0.1"></v-number-input>
            </v-card>
            <v-card subtitle="Rotation" class="ma-1" color="green-darken-4">
                <v-number-input :precision="null" controlVariant="stacked" hide-details density="compact" label="x" v-model="rotation.x" :step="0.1"></v-number-input>
                <v-number-input :precision="null" controlVariant="stacked" hide-details density="compact" label="y" v-model="rotation.y" :step="0.1"></v-number-input>
                <v-number-input :precision="null" controlVariant="stacked" hide-details density="compact" label="z" v-model="rotation.z" :step="0.1"></v-number-input>
            </v-card>
            <v-card subtitle="Scale" class="ma-1" color="pink-darken-4">
                <template v-slot:append>
                    <v-btn :icon="uniformScale ? 'mdi-lock-outline' : 'mdi-lock-open-variant-outline'" size="x-small" variant="text" density="compact" @click="uniformScale = !uniformScale"></v-btn>
                </template>
                <v-number-input :precision="null" controlVariant="stacked" hide-details density="compact" label="x" v-model="currentObject.scale.x" :step="0.01"></v-number-input>
                <v-number-input :precision="null" controlVariant="stacked" hide-details density="compact" label="y" v-model="currentObject.scale.y" :step="0.01"></v-number-input>
                <v-number-input :precision="null" controlVariant="stacked" hide-details density="compact" label="z" v-model="currentObject.scale.z" :step="0.01"></v-number-input>
            </v-card>
            <v-card subtitle="Info" v-if="objectInfo" class="ma-1" color="purple-darken-4">
                <div class="d-flex flex-column text-caption px-3">
                    <div>Width: {{ (currentObject.scale.x * objectInfo.width).toFixed(2) }}</div>
                    <div>Height: {{ (currentObject.scale.y * objectInfo.height).toFixed(2) }}</div>
                    <div>Depth: {{ (currentObject.scale.z * objectInfo.depth).toFixed(2) }}</div>
                </div>
            </v-card>
        </div>
    </v-navigation-drawer>
    <div class="container my-3 position-relative game-designer-canvas">
        <div ref="target" @click="targetClick" @pointerdown="targetPointerDown"></div>
        <div class="renderer-gizmo"></div>
    </div>
    <v-toolbar density="compact">
        <v-slide-group show-arrows>
            <v-slide-group-item v-for="(a, i) in objectAnimations" :key="i" v-slot="{ isSelected }">
                <v-btn :color="isSelected ? 'primary' : undefined" class="ma-2"
                    :prepend-icon="'mdi-' + (a.playing ? 'stop' : 'play')" rounded @click="toggleAnimation(a)">
                    {{ a.name }}
                </v-btn>
            </v-slide-group-item>
        </v-slide-group>
    </v-toolbar>
    <v-navigation-drawer location="right">
        <v-list selectable color="primary" @update:selected="loadScene($event[0])" :items="scenes"></v-list>
        <v-list selectable @update:selected="selectObject($event[0])" v-model:selected="selectedObject" color="secondary">
            <v-list-item v-for="(v, k) in flatObjects" :title="v.value.__title" :subtitle="v.key" :value="k" :class="`ml-${v.value.__level * 3}`">
                <template v-slot:prepend>
                    <v-btn variant="plain" density="comfortable" size="small" v-if="v.value.__o"
                        :icon="`mdi-eye${v.value.__o.visible ? '' : '-off'}`"
                        @click.stop="v.value.__o.visible = !v.value.__o.visible"></v-btn>
                    <!-- <v-icon :icon="components[item.name].icon" size="small"></v-icon> -->
                </template>
                <template v-slot:append>
                    <v-img :src="`/asset/thumb/${v.value.__type}.webp`" width="30"></v-img>
                </template>
            </v-list-item>
        </v-list>
    </v-navigation-drawer>
</template>

<script>
import { GameEngine } from '@/lib/GameEngine';
import { GameManager } from '@/lib/GameManager';
import { markRaw, computed } from 'vue';
import { Vector3, Matrix4, Quaternion } from 'three';
import Utils from '#/app/Utils';

let engine = null, manager = null;

export default {
    props:{
        modelValue: Object,
    },
    data(){
        return {
            env: 'GameDesigner',
            scenes: [],
            currentScene: null,
            sceneObjects: {},
            flatObjects: [],
            currentObject: null,
            selectedObject: [],
            objectAnimations: [],
            mode: 'translate',
            pointerDownTime: 0,
            renderType: 'ST',
            cameraType: 'perspective',
            uniformScale: true,
            rotation: {}
        }
    },

    async mounted(){
        engine = new GameEngine();
        await engine.init(this.$refs.target, { 
            xr: true, 
            gizmo: true, 
            stats: true, 
            mode: 'GameDesigner'
        });
        engine.perspectiveCamera.far = 1000;
        engine.perspectiveCamera.updateProjectionMatrix();
        const that = this;
        ['x', 'y', 'z'].forEach(c=>{
            this.rotation[c] = computed({
                get(){
                    return Utils.rad2deg(that.currentObject.rotation[c]);
                },
                set(v){
                    that.currentObject.rotation[c] = Utils.deg2rad(v);
                }
            })
        });
        manager = await new GameManager(engine, this.modelValue, null, {
            onObjectLoad:(sceneObjects, data, object3d, source)=>{
                ['position', 'scale', 'rotation'].forEach(p=>{
                    sceneObjects[data.id][p] = object3d[p].clone();
                })
                sceneObjects[data.id].__o = markRaw(object3d);
                sceneObjects[data.id].__animations = markRaw(source.animations || []);
                sceneObjects[data.id].__title = data.title;
                sceneObjects[data.id].__type = data.type;
                sceneObjects[data.id].__level = data.level || 0;
                object3d.__pn_id = this.flatObjects.length;
                this.flatObjects.push({
                    key: data.id, value: sceneObjects[data.id]
                });
            }
        });
        this.scenes = manager.scenarioData.scenes.map(s=>({
            title: s.data.title,
            value: s.data.id
        }))
        window.addEventListener('resize', this.resize);
        engine.addEventListener('controller', this.onController);
    },

    async unmounted(){
        this.debug('Disposing scene')
        window.removeEventListener('resize', this.resize);
        engine.transformControls.removeEventListener('change', this.update)
        engine.tm?.setGame(null);
        engine.dispose();
        this.debug('Disposed scene', JSON.stringify(engine.renderer.info.memory));
        engine = null;
        manager = null;
    },

    computed:{
        objectInfo(){
            if (this.currentObject.__o.userData?.bbox){
                let b = this.currentObject.__o.userData.bbox;
                return {
                    width: b.max.x - b.min.x,
                    height: b.max.y - b.min.y,
                    depth: b.max.z - b.min.z
                }
            }
        }
    },

    watch:{
        mode(n){
            engine.transformControls.setMode(n)
        },
        renderType(v){
            engine.renderType = v;
        },
        cameraType(v){
            if (v == 'perspective'){
                engine.setCameraPerspective();
            }else{
                engine.setCameraOrthographic();
            }
        },
        'currentObject':{
            deep: true,
            handler(v){
                this.handleScale(this.currentObject, this.currentObject.__o)
            }
        }
    },

    methods:{
        async loadScene(sceneId){
            this.flatObjects = [];
            await manager.loadScene(sceneId)
            this.currentScene = manager.scenarioData.scenes.find(sc=>sc.data.id == sceneId);
            this.sceneObjects = this.modelValue.scenes?.[sceneId]?.objects
            engine.transformControls.addEventListener('objectChange', this.handleTransformControlsChange)
        },

        handleScale(src, dst){
                if (this.uniformScale){
                    for (let p of ['x', 'y', 'z']){
                        if (src.scale[p] != dst.scale[p]) {
                            let k = src.scale[p]/dst.scale[p];
                            ['x', 'y', 'z'].filter(c=>src.scale[c] == dst.scale[c]).forEach(c=>{
                                src.scale[c] = dst.scale[c] * k;
                                engine.transformControls._scaleStart[c] = src.scale[c];
                            })
                            break;
                        }
                    }
                }
                ['position', 'scale', 'rotation'].forEach(p=>{
                    dst[p].copy(src[p]);
                })
        },

        handleTransformControlsChange(){
            this.handleScale(this.currentObject.__o, this.currentObject);
        },

        selectObject(oid){
            this.currentObject = this.flatObjects[oid]?.value;
            this.selectedObject = [oid];
            engine.transformControls.attach(this.currentObject.__o);
            let position = new Vector3(), quat = new Quaternion();
            let mtx = new Matrix4();
            this.currentObject.__o.getWorldPosition(position);
            engine.orbitControls.target.copy(position);
            engine.gizmo.target.copy(position);
            mtx.lookAt(engine.camera.position, position, engine.camera.up);
            quat.setFromRotationMatrix(mtx);
            engine.motionQueue.add({
                o: engine.camera,
                a: {quaternion: { x: quat.x, y:quat.y, z: quat.z, w: quat.w } },
                t: 0.5
            })
            //engine.camera.updateProjectionMatrix();
            this.objectAnimations = this.currentObject?.__animations?.map(a => ({
                name: a.name, id: a.uuid, a
            }));
        },

        onController(event){
            engine.transformControls.getRaycaster().setFromXRController(event.data.target);
            switch (event.data.type) {
                case 'selectstart':
                    engine.transformControls.pointerDown(null);
                    break;
                case 'selectend':
                    engine.transformControls.pointerUp(null);
                    break;
                case 'move':
                    engine.transformControls.pointerHover(null);
                    engine.transformControls.pointerMove(null);
                    break;
                case 'select':
                    this.intersect();
                    break;
            }
        },

        targetClick(e){
            if (performance.now() - this.pointerDownTime < 200){
                let mouse = engine.getMouseVector(e, this.$refs.target);
                engine.transformControls.getRaycaster().setFromCamera(mouse, engine.camera);
                this.intersect()
            }
        },

        intersect(){
            let objects = this.flatObjects.map(o=>o.value.__o).filter(o => o.visible);
            let intersects = engine.transformControls.getRaycaster().intersectObjects(objects, true);
            intersects.forEach(o => {
                while (o.object && !objects.includes(o.object)) {
                    o.object = o.object.parent;
                }
            })
            if (intersects.length){
                this.selectObject(intersects[0].object.__pn_id)
            }else{
                engine.transformControls.detach();
            }
        },

        targetPointerDown(){
            this.pointerDownTime = performance.now();
        },

        targetPointer(e, t){
            engine.onPointer(e, this.$refs.target, t);
        },

        async toggleAnimation(animation){
            animation.playing = !animation.playing;
            engine.playAnimation(engine.scene, animation.a, animation.playing);
        },

        resize(){
            let r = this.$refs.target;
            engine.resize(r.clientWidth, r.clientHeight);
        },
    }
}
</script>