import { IcyNetUser } from '../../common/types/user';
import { Socket } from 'socket.io-client';
import { PonyEntity } from './model/pony';
import { Scene, Vector2, Vector3 } from 'three';
import { ThirdPersonCamera } from './camera';
import { clamp } from '../../common/helper';
import { Renderer } from '../renderer';

export class Player extends PonyEntity {
  public thirdPersonCamera!: ThirdPersonCamera;

  public keydownMap: { [x: string]: boolean } = {};
  private _prevKeydownMap: { [x: string]: boolean } = {};

  /**
   * Normal vector of the currently faced direction
   */
  private _lookVector = new Vector3();

  /**
   * Direction of the current movement.
   * X axis controls angular velocity, Y axis controls linear velocity direction by world coordinates.
   */
  private _direction = new Vector2();

  /**
   * Joystick or other external movement usage.
   */
  private _externalDirection = new Vector2();

  /**
   * Was external movement used this tick
   */
  private _externalForce = false;

  /**
   * Was moving last tick
   */
  private _wasMoving = false;

  /**
   * Was turning last tick
   */
  private _wasTurning = false;

  /**
   * When true, left and right movement will not rotate, instead will move.
   */
  private _cameraLock = false;

  constructor(public user: IcyNetUser) {
    super();
  }

  public static fromUser(user: IcyNetUser, scene: Scene): Player {
    const entity = new Player(user);
    entity.initialize();
    scene.add(entity.container);
    return entity;
  }

  initialize(): void {
    super.initialize();

    window.addEventListener('keydown', (e) => {
      this.keydownMap[e.key.toLowerCase()] = true;
    });

    window.addEventListener('keyup', (e) => {
      this.keydownMap[e.key.toLowerCase()] = false;
    });
  }

  dispose(): void {
    super.dispose();
    this.thirdPersonCamera?.dispose();
  }

  public setCamera(renderer: Renderer) {
    this.thirdPersonCamera = new ThirdPersonCamera(
      renderer.camera,
      this.container,
      renderer.renderer.domElement,
    );
    this.thirdPersonCamera.initialize();
    this.thirdPersonCamera.registerAltMoveFunction((x, y) => {
      this.angularVelocity.set(0, clamp(x * 0.5, -Math.PI, Math.PI), 0);
    });
  }

  public createPacket(socket: Socket): void {
    if (Object.keys(this.changes).length) {
      socket.emit('set.move', this.changes);
      this.changes = {};
    }
  }

  public applyForce(vec: Vector2) {
    this._externalDirection.copy(vec);
    this._externalForce = true;
  }

  public moveCharacter(dt: number) {
    const vector = new Vector2();
    const wasExternalForce = this._externalForce;
    this._externalForce = false;

    if (wasExternalForce) {
      vector.copy(this._externalDirection);
    } else {
      vector.copy(this._direction);
    }

    if (vector.x !== 0 && !this._cameraLock) {
      this.angularVelocity.set(0, Math.PI * vector.x, 0);
      this.changes.angular = this.angularVelocity.toArray();

      this._wasTurning = true;
    } else if (this._wasTurning && !wasExternalForce) {
      this._wasTurning = false;
      this.angularVelocity.set(0, 0, 0);
      this.changes.angular = this.angularVelocity.toArray();
      this.changes.rotation = this.container.rotation.toArray();
    }

    if (vector.y !== 0 || (this._cameraLock && vector.x !== 0)) {
      const directional = this._lookVector
        .clone()
        .multiplyScalar(vector.y * 2.5);

      if (this._cameraLock && vector.x !== 0) {
        const sideways = new Vector3();
        sideways.copy(this._lookVector);
        sideways.applyAxisAngle(new Vector3(0, 1, 0), Math.PI / 2);
        sideways.normalize();
        sideways.multiplyScalar(vector.x * 2.5);
        directional.add(sideways);
      }

      this.velocity.set(directional.x, this.velocity.y, directional.z);
      this.changes.velocity = this.velocity.toArray();

      this._wasMoving = true;
      this.setWalkAnimationState(1);
    } else if (this._wasMoving && !wasExternalForce) {
      this._wasMoving = false;
      this.velocity.set(0, this.velocity.y, 0);
      this.changes.velocity = this.velocity.toArray();
      this.changes.position = this.container.position.toArray();
      this.setWalkAnimationState(0);
    }

    this.changes.rotation = this.container.rotation.toArray();
    this.changes.position = this.container.position.toArray();
  }

  update(dt: number): void {
    this.container.getWorldDirection(this._lookVector);

    if (this.keydownMap['w']) {
      this._direction.y = 1;
    } else if (this.keydownMap['s']) {
      this._direction.y = -1;
    } else {
      this._direction.y = 0;
    }

    if (this.keydownMap['a']) {
      this._direction.x = 1;
    } else if (this.keydownMap['d']) {
      this._direction.x = -1;
    } else {
      this._direction.x = 0;
    }

    if (this.keydownMap[' '] && !this._prevKeydownMap[' ']) {
      this.jump();
    }

    this.moveCharacter(dt);
    super.update(dt);
    this.thirdPersonCamera?.update(dt);

    this._prevKeydownMap = { ...this.keydownMap };
  }

  public setCameraLock(isLocked: boolean) {
    this.thirdPersonCamera?.setLock(isLocked);
    this._cameraLock = isLocked;
  }
}
