/**
 *       _  ___   Another      
 *      / |/ (_)______ __ _____
 *     /    / / __(_-</ // (_-<
 *    /_/|_/_/\__/___/\_, /___/ 
 *                   /___/     
 *       production !
 * 
 * Licensed under the MIT License: 
 * This code is free to use and modify, 
 * as long as the copyright notice and license are kept.
 */
import * as THREE from 'three' 
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' 
import * as TWEEN from 'three/examples/jsm/libs/tween.module.js' 

export class kfArena{
    
    constructor(canvasEl, agentSprites){
        Object.assign(this, app.helpers.helpers3D)
        this.agentSprites = app.Assets.Store.json.agentSprites
        this.canvasEl = canvasEl
        this.agentSprites = agentSprites
        this.renderer = null
        this.mode = '3D'
        this.sceneSize = app.Assets.Store.json.arenaConfig.arenaSize
        this.initScene()
        this.raycaster = new THREE.Raycaster()
        this.agents = {}
        this.onclickAgent = null
    }
    
    initScene(){
        // Scene
        this.scene = new THREE.Scene()

        // Camera
        this.camera = new THREE.PerspectiveCamera(75, this.canvasEl.clientWidth / this.canvasEl.clientHeight, 0.1, 1000)
        this.camera.position.set(3, 3, 5)
        this.camera.lookAt(0, 0, 0)
        this.camera.layers.enable(1)
        this.camera.layers.enable(2)
        
        // 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.grid = new THREE.GridHelper(this.sceneSize.x, this.sceneSize.x, 0x8888AA, 0x8888AA)
        this.grid.layers.set(1)
        this.scene.add(this.grid)        
        
        // Base plane
        const planeGeo = new THREE.PlaneGeometry(100, 100)
        const planeMat = new THREE.MeshBasicMaterial({
            color: 0x000055,
            opacity: 0.3,
            transparent: true, // needed for opacity < 1 to take effect
            side: THREE.DoubleSide
        })
        this.basePlane = new THREE.Mesh(planeGeo, planeMat)
        this.basePlane.rotation.x = -Math.PI / 2 // lay it flat (like the grid)
        this.basePlane.position.y=-0.01 // to avoid artefacts on objets bases
        this.scene.add(this.basePlane)        
        
        this.axes = new THREE.AxesHelper(this.sceneSize.x/2)
        this.axes.layers.set(2)
        this.scene.add(this.axes)        
        
        this.renderer = new THREE.WebGLRenderer({ antialias: true, canvas: this.canvasEl, stencil: true })

        this.canvasEl.addEventListener('click', this.onSceneClick.bind(this))      
    }
    
    startRendering(){
        this.addControls()       
        this.render()
    }
    
    render() {
        TWEEN.update()
        this.animateHighlight3DObj()
        const resized = this.resizeRendererToDisplaySize()

        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
    }
    
    addControls(){
        this.controls = new OrbitControls(this.camera, this.canvasEl)
        if(this.mode=='2D'){
            this.controls.maxPolarAngle = 0 // Math.PI / 2
            this.controls.minPolarAngle = 0 // Math.PI / 2
        } else if(this.mode=='3D'){
        }
        
        this.controls.mouseButtons = {
            LEFT: THREE.MOUSE.ROTATE,     // keep orbit on left
            MIDDLE: THREE.MOUSE.PAN,      // pan with middle-click
            RIGHT: THREE.MOUSE.DOLLY      // zoom with right-click
        }
    }
        
    
    onSceneClick(event){ // ray from the mouse, through the camera lens, to find the first (most foreground) object
        if(typeof(this.onclickAgent) != 'function') return
        const normalizedPointer = new THREE.Vector2()
        const rect = this.canvasEl.getBoundingClientRect()       
        normalizedPointer.x = ((event.clientX - rect.left) / rect.width) * 2 - 1
        normalizedPointer.y = -((event.clientY - rect.top) / rect.height) * 2 + 1
        this.raycaster.setFromCamera(normalizedPointer, this.camera)
        const intersects = this.raycaster.intersectObjects(this.scene.children, true)
        if (intersects.length > 0) {
            let hit = null
            for(let i=0; i<intersects.length; i++){
                hit = this.getNamedParent(intersects[i].object)
                if((!hit) || (!hit.name)) continue
                if(Object.keys(this.agents).includes(hit.name)) break
            }
            if(hit && Object.keys(this.agents).includes(hit.name)) this.onclickAgent(hit)
        } 
    }
    
    
    addAgent(typeId, aid, properties, values){  
        const agentSprite = this.agentSprites.find(item => item.atp_id==typeId)
        if(!agentSprite) {
            console.warn(`No sprite for type: ${typeId}`)
            return
        }
        const agentObj = this.agentFromJSON(aid, agentSprite.asp_3d)

        agentObj.position.set(values.position.x, values.position.z, values.position.y )
        this.scene.add(agentObj)
        //Speed vector
        const objAboveHead = this.getObjectTopCenter(this.scene.getObjectByName(aid))
        this.createArrow(objAboveHead, {
            x: values.speed.x, 
            y: values.speed.z, 
            z:values.speed.y },
            {   name: `_speed_${aid}`,
                color: 0xff0055,
                scale: 0.5,
            }) 

        this.agents[aid] = {
            type: typeId,
            props: properties,
            values: values,
        }
    }
    
    removeAgent(aid){ 
        const obj3d = this.scene.getObjectByName(aid)
        this.clearHighlight3DObj(obj3d, this.scene)
        this.scene.remove(obj3d)
        const arrow3d = this.scene.getObjectByName(`_speed_${aid}`)
        if(arrow3d) {
            this.clearHighlight3DObj(arrow3d, this.scene)
            this.scene.remove(arrow3d)
        }
        if(aid in this.agents) delete(this.agents[aid])
    }
    
    moveAgent(aid, position){
        const obj3d = this.scene.getObjectByName(aid)
        this.clearHighlight3DObj(obj3d, this.scene)
        const dx = Number(position.x) - obj3d.position.x
        const dy = Number(position.z) - obj3d.position.y
        const dz = Number(position.y) - obj3d.position.z
        if(this.moveAgentTween) this.moveAgentTween.stop()
        this.moveAgentTween = new TWEEN.Tween(obj3d.position)
            .to({   x: Number(position.x), 
                    y: Number(position.z),
                    z: Number(position.y), 
                }, 500)
            .easing(TWEEN.Easing.Quadratic.InOut)
            .onComplete(() => { this.highlight3DObj(obj3d, this.scene) } )
            .start()
        const arrow3d = this.scene.getObjectByName(`_speed_${aid}`)
        if(arrow3d){
            if(this.moveArrowTween) this.moveArrowTween.stop()
            this.moveArrowTween = new TWEEN.Tween(arrow3d.position)
                .to({   x: arrow3d.position.x + dx, 
                        y: arrow3d.position.y + dy, 
                        z: arrow3d.position.z + dz, 
                    }, 500)
                .easing(TWEEN.Easing.Quadratic.InOut)
                .start()
        }
    }

    changeAgentSpeed(aid, speed){
        const objName = `_speed_${aid}`
        const obj3d = this.scene.getObjectByName(objName)
        if(obj3d) this.scene.remove(obj3d)
        const objAboveHead = this.getObjectTopCenter(this.scene.getObjectByName(aid))
        this.createArrow(objAboveHead, {
            x: speed.x, 
            y: speed.z, 
            z: speed.y },
            {   name: objName,
                color: 0xff0055,
                scale: 0.5,
            }) 
    }

    reloadAgents(agents){
        for(const aid in this.agents){
            this.removeAgent(aid)
        }
        for(const agent of agents){
            this.addAgent(agent.ekfs_atp_id, agent.aid, agent.atp_props, {...agent.gps_values, ...agent.store_values})
        }
    }


    // getAllAgents(){
    //     const agents = []
    //     scene.traverse(o => o.name && names.push(o.name))
}


// Make this module available to common JS
if(!app.LoadedModules) app.LoadedModules = {}
app.LoadedModules.kfArena = kfArena

