import * as THREE from 'three' 
import * as TWEEN from 'three/examples/jsm/libs/tween.module.js' 
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';


class HelperBot{

    constructor(canvasSelector){
        this.renderer = null
        this.canvasEl  = document.querySelector(canvasSelector)
        this.initScene()
        window.bot=this
    }
    
    initScene(){
        
        this.canvasEl .width = window.innerWidth
        this.canvasEl .height = window.innerHeight

        this.scene = new THREE.Scene()

        this.camera = new THREE.PerspectiveCamera(30, this.canvasEl.clientWidth / this.canvasEl.clientHeight, 0.1, 1000)
        this.camera.position.set(0, 1, 10)
        this.camera.lookAt(0, 0, 0)

        // Lights
        const dLight1 = new THREE.DirectionalLight(0xffffff, 1.5)
        dLight1.position.set(5, 5, 5)
        this.scene.add(dLight1)
        const dLight2 = new THREE.DirectionalLight(0xffffff, 1.5)
        dLight2.position.set(-5, -5, 5)
        this.scene.add(dLight2)
        this.scene.add(new THREE.AmbientLight(0xffffff, 1))



        
        this.renderer = new THREE.WebGLRenderer({
            canvas: this.canvasEl ,
            alpha: true   
        })

        this.renderer.setSize(this.canvasEl .width, this.canvasEl .height)
        this.renderer.setPixelRatio(window.devicePixelRatio)
        this.renderer.setClearColor(0x000000, 0)  


        this.threeClock = new THREE.Clock()
        const loader = new GLTFLoader().setPath('/app/assets/3dModels/')
        loader.load('mecha2.glb', async gltf => {
            this.mecha = new THREE.Group()
            this.scene.add(this.mecha)

            this.mechaModel = gltf.scene
            this.mecha.add(this.mechaModel)

            this.mechaModel.scale.set(5, 5, 5)
            this.mechaModel.rotation.set(0, 0, 0)
            this.mechaModel.position.set(0, -5.5, 0)
            this.mechaModel.updateMatrix()
            this.mechaModel.rotateY(-Math.PI) // 180deg so facing user

            // GPU compilation (optimization)
            await this.renderer.compileAsync(this.mechaModel, this.camera, this.scene)

            this.mechaModel.traverse(obj => {
                if (obj.isMesh && obj.material && obj.material.color) {
                    const c = obj.material.color.clone()
                    const boost = 0.1
                    obj.material.emissive = c
                    obj.material.emissiveIntensity = boost
                }
            })

            if (gltf.animations && gltf.animations.length > 0) {
                this.mixer = new THREE.AnimationMixer(this.mechaModel)
                const action = this.mixer.clipAction(gltf.animations[0])
                action.play()
            }


            this.leftArm = this.mechaModel.getObjectByName('ArmL1_01')
            this.animate = true
        })


        this.resizeWindow() 
        window.addEventListener('resize', this.resizeWindow.bind(this))
        this.render()

    }

    resizeWindow() {
        const w = window.innerWidth
        const h = window.innerHeight

        this.canvasEl .width = w
        this.canvasEl .height = h

        this.renderer.setSize(w, h)
        this.renderer.setPixelRatio(window.devicePixelRatio)

        this.camera.aspect = w / h
        this.camera.updateProjectionMatrix()
    }

    startRendering(){
        this.addControls()       
        this.render()
    }
    
    render() {
        TWEEN.update()
        const resized = this.resizeRendererToDisplaySize()

        const delta = this.threeClock.getDelta()
        if(this.mixer && this.animate) this.mixer.update(delta)

        this.renderer.render(this.scene, this.camera)
        requestAnimationFrame(this.render.bind(this))
    }

    resizeRendererToDisplaySize() {
        const width = this.canvasEl.clientWidth
        const height = this.canvasEl.clientHeight
        if (this.canvasEl.width !== width || this.canvasEl.height !== height) {
            this.renderer.setSize(width, height, false)
            return true
        }
        return false
    }    

    translateBot(position, delay=500, endcb, updatecb){
        if(this.movingBotTween) this.movingBotTween.end()
        this.movingBotTween = new TWEEN.Tween(this.mecha.position)
            .to({   x: Number(position.x), 
                    y: Number(position.z),
                    z: Number(position.y), 
                }, delay)
            .easing(TWEEN.Easing.Sinusoidal.InOut)
        if(typeof(endcb)=='function') this.movingBotTween.onComplete(endcb)
        if(typeof(updatecb)=='function') this.movingBotTween.onUpdate(updatecb)
        this.movingBotTween.start()
    }

    rotateBot(rotation, delay=500, endcb, updatecb){
        //TODO : respect existing Z

        if(this.rotatingBotTween) this.rotatingBotTween.end()
        this.rotatingBotTween = new TWEEN.Tween(this.mecha.rotation)
            .to({   x: Number(rotation.x*Math.PI/180), 
                    y: Number(rotation.z*Math.PI/180),
                    z: Number(rotation.y*Math.PI/180), 
                }, delay)
            .easing(TWEEN.Easing.Sinusoidal.InOut)
        if(typeof(endcb)=='function') this.rotatingBotTween.onComplete(endcb)
        if(typeof(updatecb)=='function') this.rotatingBotTween.onUpdate(updatecb)
        this.rotatingBotTween.start()
    }

    propelMove(position){
        //TODO : respect existing Z rotation

        const moveDelay = 1000 
        const dx = this.mecha.position.x - position.x
        const dy = this.mecha.position.z - position.y
        const dz = this.mecha.position.y - position.z
        const delay = Math.max(Math.abs(dx), Math.abs(dy), Math.abs(dz)) * moveDelay
        const ax = (dy>0.5) ? -10 : ((dy<-0.5) ? 10 : 0 )
        const ay = (dx>0.5) ? 10 : ((dx<-0.5) ? -10 : 0 )
        //TODO : fix jerks when rotation tweens overlap
        // console.log( this.mecha.position.x, this.mecha.position.y)
        // console.log(dx,dy,ax,ay)

        const rotationDelay = delay/4; 
        if(rotationDelay>400) this.rotateBot({x: ax, y: ay, z: 0}, rotationDelay)

        let chkpt1 = true, chkpt2 = true, chkpt3 = true
        this.animate = false; console.log('stopped!')
        const startTime = Date.now()
        this.translateBot(position, delay, () => {
            this.animate = true ; console.log('started!')
        }, 
        () => { 
            const t = (Date.now()-startTime)/delay
            if(chkpt1 && (t >= 0.2)){ //Time to stop accelerating
                if(rotationDelay>400) this.rotateBot({x: 0, y: 0, z: 0}, rotationDelay)
                chkpt1 = false 
            }
            if(chkpt2 && (t >= 0.7)){ //Time to brake
                if(rotationDelay>400) this.rotateBot({x: -ax, y: -ay, z: 0}, rotationDelay)
                chkpt2 = false
            }
            if(chkpt3 && (t >= 0.9)){ //Time to restore
                if(rotationDelay>400) this.rotateBot({x: 0, y: 0, z: 0}, rotationDelay)
                chkpt3 = false
            }
        })
    }
}

app.registerClass('HelperBot', HelperBot)