import { AnimationMixer, Vector3 } from 'three';
import { PointerControls } from './PointerControls';
import { getBoundingBox, getBoundingBoxSize } from './MeshUtils';
import { QueryFilterFlags } from '@dimforge/rapier3d';

class Hero{
    walkDirection = new Vector3()
    rotateAngle = new Vector3(0, 1, 0)
    #cameraZ = 5/1.77

    get cameraZ(){
        return this.#cameraZ;
    }

    set cameraZ(v){
        this.#cameraZ = Math.min(Math.max(v, 1), 10);
        if (this.#cameraZ == 1){
            this.#cameraZ = 0;
            this.lockControls();
        }
    }
    
    fadeDuration = 0.2
    characterGapOffset = 0.01;

    lerp = (x, y, a) => x * (1 - a) + y * a;

    constructor(engine, io){
        this.source = io.source;
        this.model = io.object
        //this.data = data;
        
        this.engine = engine;

        this.mixer = new AnimationMixer(this.model);
        engine.mixers.push( this.mixer );

        this.animationsMap = {};
        this.source.animations.forEach(a=>{
            this.animationsMap[a.name] = this.mixer.clipAction(a);
        })  

        this.pointerControls = new PointerControls(engine.camera, this.model, engine.renderer.domElement);
        engine.hero = this;

        // let bb = this.model.userData.bbox;
        // let size = getBoundingBoxSize(bb);
        // let center = getBoundingBoxCenterPoint(bb, this.model.userData.object.position)
        let bb = getBoundingBox(io.object);
        this.size = getBoundingBoxSize(bb);
        // let center = getBoundingBoxCenterPoint(bb, io.object.position)

        // console.log('hero', size, center, size.y - 2*this.characterGapOffset)

        this.runVelocity = this.size.y * 5
        this.walkVelocity = this.size.y * 3

        this.po = engine.physics.add(this.model, 'kinematicPositionBased', false, undefined, 'capsule', { radius: this.size.y/4, halfHeight: this.size.y/4})
        this.po.collider.setTranslationWrtParent({x: 0, y: this.size.y/2, z: 0});
        
        this.currentAction = 'idle';
        this.animationsMap[this.currentAction].play()
        
        this.characterController = this.engine.physics.world.createCharacterController(this.characterGapOffset);
        //this.characterController.setUp({x:0, y:1, z:0});
        this.po.rigidBody.setTranslation(this.model.position)
        this.po.characterController = this.characterController;
        //this.characterController.enableSnapToGround(0.5);
        // // Don’t allow climbing slopes larger than 45 degrees.
        // this.characterController.setMaxSlopeClimbAngle(45 * Math.PI / 180);
        // Automatically slide down on slopes smaller than 30 degrees.
        // this.characterController.setMinSlopeSlideAngle(30 * Math.PI / 180);
        // this.characterController.enableAutostep(0.5, 0.2, true);
        //this.characterController.setApplyImpulsesToDynamicBodies(true);
        // this.characterController.setCharacterMass(50);

        this.orbitControl = this.engine.orbitControls
        this.camera = this.engine.camera
        this.cameraY = this.size.y * 1.5;

        this.direction = this.model.rotation.y;
        //this.directionVelocity = 0;
        this.actionStart = 0;
        this.cameraDelta = 0;
        this.cameraIdleDelta = 0;
        this.ready = true;
    }

    lockControls(){
        this.pointerControls.controls.lock(true);
    }

    update(delta){        
        if (this.ready && this.engine.physics.started && !this.disableInput && !this.engine.renderer.xr.isPresenting) {
            let pc = this.pointerControls;
            pc.update();
            this.updateCharacterControls(delta, pc)
        }
    }

    destroy(){
        delete this.engine.hero;
        this.engine.mixers.splice(this.engine.mixers.indexOf(this.mixer), 1);
    }

    updateCharacterControls(delta) {
        let pc = this.pointerControls;
        if (pc.controls.isLocked && this.model.visible){
            this.model.visible = false;
            this.camera.rotation.reorder('YZX');
        }
        if (!pc.controls.isLocked && !this.model.visible){
            this.model.visible = true;
            this.#cameraZ = 5/1.77
            this.camera.fov = 45;
            this.camera.updateProjectionMatrix();
        }
        let input = this.getInput()

        let play = this.currentAction || 'idle', velocity = this.walkVelocity;
        this.fadeDuration = 0.2;
        if (input[1] && pc.running) {
            play = 'run';
            velocity = this.runVelocity
        } else if (input[1] > 0) {
            play = 'walk'
        } else if (input[1] < 0) {
            play = 'backward'
            velocity = this.walkVelocity / 3
        } else if (input[0] < 0) {
            play = 'right'
        } else if (input[0] > 0) {
            play = 'left'
        } else if (!this.currentAction.startsWith('idle')){
            play = 'idle'
        } else if (this.actionStart == -1){
            this.actionStart = 0;
            play = 'idle';
        }

        if (this.currentAction.startsWith('idle') && play.startsWith('idle') && this.actionStart > 10){
            let idx = 1 + parseInt(this.currentAction.split('.')[1] || 0);
            if (this.animationsMap[`idle.${idx}`]) {
                play = `idle.${idx}`
            }else{
                play = 'idle'
            }
            this.fadeDuration = 1;
        }

        if (this.currentAction.startsWith('idle') && play != 'idle' && !pc.controls.isLocked){
            this.cameraIdleDelta += delta * 0.33;
        }else {
            this.cameraIdleDelta = 0;
        }

        if (this.currentAction != play) {
            const toPlay = this.animationsMap[play]
            const current = this.animationsMap[this.currentAction]

            current.fadeOut(this.fadeDuration)
            //toPlay.timeScale = 1;
            toPlay.reset().fadeIn(this.fadeDuration).play();

            this.currentAction = play
            this.actionStart = 0;
        }

        this.actionStart += delta;
        this.cameraDelta += delta * ( pc.cameraLeft * -1 +  pc.cameraRight * 1)
        this.walkDirection.setScalar(0);

        if (pc.kb.KeyR && this.cameraY < 5){
            this.cameraY+=delta;
        }
        if (pc.kb.KeyF && this.cameraY > 1){
            this.cameraY-=delta;
        }

        if (pc.motion){
            if (!pc.controls.isLocked){
                //this.directionVelocity = this.directionVelocity * 2.5 * Math.abs(input[0])
                this.direction += input[0] * delta * 2.5 //this.directionVelocity;
                this.walkDirection.set(0, 0, input[1])
                this.walkDirection.applyAxisAngle(this.rotateAngle, this.direction)
            }else{
                this.direction = this.camera.rotation.y + Math.PI;
                this.walkDirection.set(input[0], 0, input[1])
                this.walkDirection.applyAxisAngle(this.rotateAngle, this.direction)
                //console.log(this.camera.rotation.y * 180/Math.PI);
                // this.camera.getWorldDirection(this.walkDirection);
                // this.walkDirection.applyAxisAngle(this.rotateAngle, input[0]*Math.PI/2 + (input[1]<0 ? Math.PI : 0))
                //this.walkDirection.multiplyScalar(input[1]);
            }
            this.walkDirection.normalize();
            this.model.rotation.y = this.direction;
        }

        this.walkDirection.x = this.walkDirection.x * velocity * delta// + this.model.position.x
        this.walkDirection.y = -delta * 0.5 * 9.8; //gravity!!!
        this.walkDirection.z = this.walkDirection.z * velocity * delta// + this.model.position.z

        //const translation = this.rigidBody.translation();

        this.characterController.computeColliderMovement(
            this.po.collider,    // The collider we would like to move.
            this.walkDirection, // The movement we would like to apply if there wasn’t any obstacle.
            QueryFilterFlags['EXCLUDE_SENSORS']
        );

        // for (let i = 0; i < this.characterController.numComputedCollisions(); i++) {
        //     let collision = this.characterController.computedCollision(i);
        //     //console.log(collision)
        //     // Do something with that collision information.
        // }

        let correctedMovement = this.characterController.computedMovement();
        //console.log(correctedMovement);

        let v = new Vector3();
        v.copy(this.po.rigidBody.translation());
        v.add(correctedMovement)

        this.po.rigidBody.setNextKinematicTranslation(v);

        this.model.position.copy(this.po.rigidBody.nextTranslation())

        let cameraPosition = new Vector3().copy(this.camera.position)
        let cameraDesiredPosition = new Vector3(
            this.model.position.x + this.#cameraZ* Math.sin(this.model.rotation.y + Math.PI + this.cameraDelta + this.cameraIdleDelta),
            this.model.position.y + (pc.controls.isLocked? this.size.y*0.9 : this.cameraY),
            this.model.position.z + this.#cameraZ* Math.cos(this.model.rotation.y + Math.PI + this.cameraDelta + this.cameraIdleDelta)
        )

        cameraPosition.lerp(cameraDesiredPosition, delta*2)
        this.camera.position.copy(cameraPosition)
        if (!pc.controls.isLocked){
            this.orbitControl.target.set(
                this.model.position.x, 
                this.cameraY -this.size.y * 0.5 + this.model.position.y, 
                this.model.position.z
            )
            this.camera.lookAt(this.orbitControl.target)
        }
    }

    getInput() {
        return [
            this.pointerControls.moveLeft * 1 + this.pointerControls.moveRight * -1, 
            this.pointerControls.moveForward * 1 + this.pointerControls.moveBackward * -1
        ]
    }

    idleReset(){
        this.actionStart = -1;
        //this.currentAction = 'idle'
    }

}

export { Hero }