import { Group, Vector3, Matrix4, Quaternion, RepeatWrapping, Vector2} from 'three';
import { InteractiveObject } from '../InteractiveObject';

class MazeObject {
    constructor(engine, def, params){
        let room = new Group();
        let root = room;
        this.object = room;
        const bbox = {l:0, r:0, f:0}
        const { wallSize, tubeSize, scale } = params

        let _tf = {
            rotation: {
                r: 3 * Math.PI / 2, f: 0, l: Math.PI / 2, b: Math.PI
            },
            pNext: {
                r: [-wallSize/2, wallSize/2],
                f: [0, wallSize],
                l: [wallSize/2, wallSize/2]
            }
        };

        let o = {};

        function addPhysics(matrix, position, size, placement = 'side', isSensor = false, userData){
            let quat = new Quaternion().setFromRotationMatrix(matrix);
            let v = new Vector3(...position).applyMatrix4(matrix);
            if (typeof size == 'number'){
                size = { width: 0.1, height:1, depth:size/2 }
            }
            let po = engine.physics.add(
                {position: v}, 'fixed', false, undefined, 'cuboid',
                { ...size, isSensor, userData }
            )
            if (placement == 'front') {
                quat.multiply(new Quaternion(0, 0.7071068, 0, 0.7071068)) //rotate by 90deg
            }
            po.rigidBody.setRotation(quat, true)
            return po;
        }

        function addRoom(elements, def, offsetZ){

            let e = elements.map(e=>o[e].clone())

            e[0].position.set(0, 0, offsetZ + wallSize/2);

            e[1].rotateY(_tf.rotation[elements[1] == 'door' ? 'f' : 'b']);

            e[2].rotateY(_tf.rotation.r);
            e[3].rotateY(_tf.rotation.f);
            e[4].rotateY(_tf.rotation.l);

            e[1].position.set(0, 0, offsetZ + 0);
            e[2].position.set(-wallSize/2, 0, offsetZ + wallSize/2);
            e[3].position.set(0, 0, offsetZ + wallSize);
            e[4].position.set(wallSize/2, 0, offsetZ + wallSize/2);

            if (elements[1] == 'wall'){
                addPhysics(def.matrix, [0, 0, offsetZ], wallSize, 'front')
            }

            if (elements[2] == 'wall'){
                addPhysics(def.matrix, [-wallSize/2, 0, offsetZ + wallSize/2], wallSize)
            }

            if (elements[3] == 'wall'){
                addPhysics(def.matrix, [0, 0, offsetZ + wallSize], wallSize, 'front')
            }

            if (elements[4] == 'wall'){
                addPhysics(def.matrix, [wallSize/2, 0, offsetZ + wallSize/2], wallSize)
            }

            e.forEach(g => {
                g.applyMatrix4(def.matrix);
                root.add(g);
                bbox.l = Math.min(g.position.x, bbox.l)
                bbox.r = Math.max(g.position.x, bbox.r)
                bbox.f = Math.max(g.position.z, bbox.f)
                if (g.userData?.['io.maxDistance']){
                    engine.hideIfFar(g, g.userData?.['io.maxDistance']);
                }
            });
            
            //the floor:
            addPhysics(def.matrix, [0, -0.1, offsetZ + wallSize/2], {width:wallSize/2, height:0.1, depth: wallSize/2})
        }

        this.mazeObject = function(def, room, step = 0) {
            if (!def.matrix){
                def.matrix = new Matrix4();
            }
            let offsetZ = 0, e;
            def.len = def.len || 0;
            if (step == 0) {
                addRoom(['floor', 'wall', 'wall', 'door', 'wall'], def, -wallSize)
            }
            if (def.userData?.answer !== undefined){
                addPhysics(def.matrix, [0,0,0], wallSize, 'front', true, def.userData)
            }
            for (let i = 0; i < def.len; i++) {
                let t = o.tunnel.clone();
                t.position.set(0, 0, i * tubeSize);
                def.matrix && t.applyMatrix4(def.matrix);
                if (t.userData?.['io.maxDistance']){
                    engine.hideIfFar(t, t.userData?.['io.maxDistance']);
                }
                root.add(t);
            }
            offsetZ = def.len * tubeSize;
            let sz = { width: Math.max(0.1, (tubeSize-o.tunnel.userData.size.x)/2), height:1, depth:offsetZ/2 }
            addPhysics(def.matrix, [tubeSize / 2, 0.6, offsetZ/2], sz)
            addPhysics(def.matrix, [-tubeSize / 2, 0.6, offsetZ/2], sz)
            //the floor:
            addPhysics(def.matrix, [0, -0.1, offsetZ/2], {width:tubeSize/2, height:0.1, depth: offsetZ/2})


            addRoom(['floor', 'door', def.r ? 'door' : 'wall', def.f ? 'door' : 'wall', def.l ? 'door' : 'wall'], def, offsetZ)
            if (def.userData?.qid !== undefined || def.userData?.finish){
                addPhysics(def.matrix, [0,0,offsetZ + wallSize/2], { width: wallSize/2, height: wallSize/2, depth: wallSize/2}, 'side', true, def.userData)
            }

            if (def.userData?.corner){
                addPhysics(def.matrix, [0,0,-wallSize/2], { width: wallSize/2, height: wallSize/2, depth: wallSize/2}, 'side', true, {corner: def.userData.corner})
            }
            //console.log('loadingggg', def.objects)
            def.objects?.forEach(async obj => {
                obj.room = room;
                let go = await new InteractiveObject(engine, obj)
                go.object.scale.multiplyScalar(wallSize)
                go.object.position.multiplyScalar(wallSize)
                go.object.applyMatrix4(def.matrix);
                go.forwardEvents?.(params.io);
                root.add(go.object);
            });
            
            def.room = room;
            ['r', 'f', 'l'].forEach((d, i) => {
                if (!def[d]) return;
                let mtx = new Matrix4();
                mtx.makeRotationY(_tf.rotation[d]);
                mtx.setPosition(_tf.pNext[d][0], 0, _tf.pNext[d][1] + offsetZ);
                let rr = new Group();
                root.add(rr);
                def[d].matrix = mtx.premultiply(def.matrix);
                rr.applyMatrix4(def[d].matrix);
                rr.updateMatrixWorld();
                this.mazeObject(def[d], rr, step + 1);
            });
        };
        
        this.load = async function(){
            let mazeAsset = await engine.load(params.mazeFile, '/static/meshes/quiz/');
            ['tunnel', 'wall', 'door', 'floor', 'surface'].forEach(e => {
                o[e] = mazeAsset.scene.getObjectByName(e);
                if (o[e]){
                    o[e].scale.setScalar(scale);
                    let bb = engine.meshUtils.getBoundingBox(o[e]);
                    o[e].userData.size = engine.meshUtils.getBoundingBoxSize(bb);
                }
            });
            this.mazeObject(def, room);

            if (o.surface){
                let floorSize = { width: bbox.r - bbox.l + 10*scale, height: 1, depth: bbox.f + 10*scale }
                const floor = o.surface.clone();
                const mt = floor.material.clone();
                ['map', 'normalMap'].forEach(m=>{
                    if (!mt[m]) return //console.log('missing', m);
                    mt[m] = mt[m].clone();
                    mt[m].wrapS = mt[m].wrapT = RepeatWrapping;
                    mt[m].repeat = new Vector2(Math.floor(floorSize.width/wallSize), Math.floor(floorSize.depth/wallSize));
                    mt[m].needsUpdate = true;
                });
                mt.needsUpdate = true;
                floor.material = mt;
                floor.position.set((bbox.l + bbox.r)/2, 0, bbox.f/2);
                floor.scale.set(floorSize.width, scale, floorSize.depth)
                root.add(floor);
            }
        }
    }
}

export { MazeObject }