import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.112.1/build/three.module.js';
import {PointerLockControls} from 'https://cdn.jsdelivr.net/npm/three@0.112.1/examples/jsm/controls/PointerLockControls.js';
import {OrbitControls} from 'https://cdn.jsdelivr.net/npm/three@0.112.1/examples/jsm/controls/OrbitControls.js';


export const controls = (function() {

  class _OrbitControls {
    constructor(params) {
      this._params = params;
      this._Init(params);
    }

    _Init(params) {
      this._controls = new OrbitControls(params.camera, params.domElement);
      this._controls.target.set(0, 0, 0);
      this._controls.update();
    }

    Update() {
    }
  }

  // FPSControls was adapted heavily from a threejs example. Movement control
  // and collision detection was completely rewritten, but credit to original
  // class for the setup code.
  class _FPSControls {
    constructor(params) {
      this._cells = params.cells;
      this._Init(params);
    }

    _Init(params) {
      this._params = params;
      this._radius = 2;
      this._enabled = false;
      this._move = {
        forward: false,
        backward: false,
        left: false,
        right: false,
        up: false,
        down: false,
      };
      this._standing = true;
      this._velocity = new THREE.Vector3(0, 0, 0);
      this._decceleration = new THREE.Vector3(-10, -10, -10);
      this._acceleration = new THREE.Vector3(50000, 50000, 50000);

      this._SetupPointerLock();

      this._controls = new PointerLockControls(
          params.camera, document.body);
      params.scene.add(this._controls.getObject());

      document.addEventListener('keydown', (e) => this._onKeyDown(e), false);
      document.addEventListener('keyup', (e) => this._onKeyUp(e), false);

      this._InitGUI();
    }

    _InitGUI() {
      this._params.guiParams.camera = {
        acceleration_x: 50000,
      };

      const rollup = this._params.gui.addFolder('Camera.FPS');
      rollup.add(this._params.guiParams.camera, "acceleration_x", 50.0, 50000.0).onChange(
        () => {
          this._acceleration.set(
            this._params.guiParams.camera.acceleration_x,
            this._params.guiParams.camera.acceleration_x,
            this._params.guiParams.camera.acceleration_x);
        });
    }

    _onKeyDown(event) {
      switch (event.keyCode) {
        case 38: // up
        case 87: // w
          this._move.forward = true;
          break;
        case 37: // left
        case 65: // a
          this._move.left = true;
          break;
        case 40: // down
        case 83: // s
          this._move.backward = true;
          break;
        case 39: // right
        case 68: // d
          this._move.right = true;
          break;
        case 33: // PG_UP
          this._move.up = true;
          break;
        case 34: // PG_DOWN
          this._move.down = true;
          break;
      }
    }

    _onKeyUp(event) {
      switch(event.keyCode) {
        case 38: // up
        case 87: // w
          this._move.forward = false;
          break;
        case 37: // left
        case 65: // a
          this._move.left = false;
          break;
        case 40: // down
        case 83: // s
          this._move.backward = false;
          break;
        case 39: // right
        case 68: // d
          this._move.right = false;
          break;
        case 33: // PG_UP
          this._move.up = false;
          break;
        case 34: // PG_DOWN
          this._move.down = false;
          break;
      }
    }

    _SetupPointerLock() {
      const hasPointerLock = (
          'pointerLockElement' in document ||
          'mozPointerLockElement' in document ||
          'webkitPointerLockElement' in document);
      if (hasPointerLock) {
        const lockChange = (event) => {
          if (document.pointerLockElement === document.body ||
              document.mozPointerLockElement === document.body ||
              document.webkitPointerLockElement === document.body ) {
            this._enabled = true;
            this._controls.enabled = true;
          } else {
            this._controls.enabled = false;
          }
        };
        const lockError = (event) => {
          console.log(event);
        };

        document.addEventListener('pointerlockchange', lockChange, false);
        document.addEventListener('webkitpointerlockchange', lockChange, false);
        document.addEventListener('mozpointerlockchange', lockChange, false);
        document.addEventListener('pointerlockerror', lockError, false);
        document.addEventListener('mozpointerlockerror', lockError, false);
        document.addEventListener('webkitpointerlockerror', lockError, false);

        document.getElementById('target').addEventListener('click', (event) => {
          document.body.requestPointerLock = (
              document.body.requestPointerLock ||
              document.body.mozRequestPointerLock ||
              document.body.webkitRequestPointerLock);

          if (/Firefox/i.test(navigator.userAgent)) {
            const fullScreenChange = (event) => {
              if (document.fullscreenElement === document.body ||
                  document.mozFullscreenElement === document.body ||
                  document.mozFullScreenElement === document.body) {
                document.removeEventListener('fullscreenchange', fullScreenChange);
                document.removeEventListener('mozfullscreenchange', fullScreenChange);
                document.body.requestPointerLock();
              }
            };
            document.addEventListener(
                'fullscreenchange', fullScreenChange, false);
            document.addEventListener(
                'mozfullscreenchange', fullScreenChange, false);
            document.body.requestFullscreen = (
                document.body.requestFullscreen ||
                document.body.mozRequestFullscreen ||
                document.body.mozRequestFullScreen ||
                document.body.webkitRequestFullscreen);
            document.body.requestFullscreen();
          } else {
            document.body.requestPointerLock();
          }
        }, false);
      }
    }

    _FindIntersections(boxes, position) {
      const sphere = new THREE.Sphere(position, this._radius);

      const intersections = boxes.filter(b => {
        return sphere.intersectsBox(b);
      });

      return intersections;
    }

    Update(timeInSeconds) {
      if (!this._enabled) {
        return;
      }

      const frameDecceleration = new THREE.Vector3(
          this._velocity.x * this._decceleration.x,
          this._velocity.y * this._decceleration.y,
          this._velocity.z * this._decceleration.z
      );
      frameDecceleration.multiplyScalar(timeInSeconds);

      this._velocity.add(frameDecceleration);

      if (this._move.forward) {
        this._velocity.z -= this._acceleration.z * timeInSeconds;
      }
      if (this._move.backward) {
        this._velocity.z += this._acceleration.z * timeInSeconds;
      }
      if (this._move.left) {
        this._velocity.x -= this._acceleration.x * timeInSeconds;
      }
      if (this._move.right) {
        this._velocity.x += this._acceleration.x * timeInSeconds;
      }
      if (this._move.up) {
        this._velocity.y += this._acceleration.y * timeInSeconds;
      }
      if (this._move.down) {
        this._velocity.y -= this._acceleration.y * timeInSeconds;
      }

      const controlObject = this._controls.getObject();

      const oldPosition = new THREE.Vector3();
      oldPosition.copy(controlObject.position);

      const forward = new THREE.Vector3(0, 0, 1);
      forward.applyQuaternion(controlObject.quaternion);
      forward.normalize();

      const updown = new THREE.Vector3(0, 1, 0);

      const sideways = new THREE.Vector3(1, 0, 0);
      sideways.applyQuaternion(controlObject.quaternion);
      sideways.normalize();

      sideways.multiplyScalar(this._velocity.x * timeInSeconds);
      updown.multiplyScalar(this._velocity.y * timeInSeconds);
      forward.multiplyScalar(this._velocity.z * timeInSeconds);

      controlObject.position.add(forward);
      controlObject.position.add(sideways);
      controlObject.position.add(updown);

      oldPosition.copy(controlObject.position);
    }
  };

  class _ShipControls {
    constructor(params) {
      this._Init(params);
    }

    _Init(params) {
      this._params = params;
      this._radius = 2;
      this._enabled = false;
      this._move = {
        forward: false,
        backward: false,
        left: false,
        right: false,
        up: false,
        down: false,
        rocket: false,
      };
      this._velocity = new THREE.Vector3(0, 0, 0);
      this._decceleration = new THREE.Vector3(-0.001, -0.0001, -1);
      this._acceleration = new THREE.Vector3(100, 0.1, 25000);

      document.addEventListener('keydown', (e) => this._onKeyDown(e), false);
      document.addEventListener('keyup', (e) => this._onKeyUp(e), false);

      this._InitGUI();
    }

    _InitGUI() {
      this._params.guiParams.camera = {
        acceleration_x: 100,
        acceleration_y: 0.1,
      };

      const rollup = this._params.gui.addFolder('Camera.Ship');
      rollup.add(this._params.guiParams.camera, "acceleration_x", 50.0, 25000.0).onChange(
        () => {
          this._acceleration.x = this._params.guiParams.camera.acceleration_x;
        });
      rollup.add(this._params.guiParams.camera, "acceleration_y", 0.001, 0.1).onChange(
        () => {
          this._acceleration.y = this._params.guiParams.camera.acceleration_y;
        });
    }

    _onKeyDown(event) {
      switch (event.keyCode) {
        case 87: // w
          this._move.forward = true;
          break;
        case 65: // a
          this._move.left = true;
          break;
        case 83: // s
          this._move.backward = true;
          break;
        case 68: // d
          this._move.right = true;
          break;
        case 33: // PG_UP
          this._acceleration.x *= 1.1;
          break;
        case 34: // PG_DOWN
          this._acceleration.x *= 0.9;
          break;
        case 32: // SPACE
          this._move.rocket = true;
          break;
        case 38: // up
        case 37: // left
        case 40: // down
        case 39: // right
          break;
      }
    }

    _onKeyUp(event) {
      switch(event.keyCode) {
        case 87: // w
          this._move.forward = false;
          break;
        case 65: // a
          this._move.left = false;
          break;
        case 83: // s
          this._move.backward = false;
          break;
        case 68: // d
          this._move.right = false;
          break;
        case 33: // PG_UP
          break;
        case 34: // PG_DOWN
          break;
        case 32: // SPACE
          this._move.rocket = false;
          break;
        case 38: // up
        case 37: // left
        case 40: // down
        case 39: // right
          break;
      }
    }

    Update(timeInSeconds) {
      const frameDecceleration = new THREE.Vector3(
          this._velocity.x * this._decceleration.x,
          this._velocity.y * this._decceleration.y,
          this._velocity.z * this._decceleration.z
      );
      frameDecceleration.multiplyScalar(timeInSeconds);

      this._velocity.add(frameDecceleration);

      const controlObject = this._params.camera;
      const _Q = new THREE.Quaternion();
      const _A = new THREE.Vector3();
      const _R = controlObject.quaternion.clone();

      if (this._move.forward) {
        _A.set(1, 0, 0);
        _Q.setFromAxisAngle(_A, -Math.PI * timeInSeconds * this._acceleration.y);
        _R.multiply(_Q);
      }
      if (this._move.backward) {
        _A.set(1, 0, 0);
        _Q.setFromAxisAngle(_A, Math.PI * timeInSeconds * this._acceleration.y);
        _R.multiply(_Q);
      }
      if (this._move.left) {
        _A.set(0, 0, 1);
        _Q.setFromAxisAngle(_A, Math.PI * timeInSeconds * this._acceleration.y);
        _R.multiply(_Q);
      }
      if (this._move.right) {
        _A.set(0, 0, 1);
        _Q.setFromAxisAngle(_A, -Math.PI * timeInSeconds * this._acceleration.y);
        _R.multiply(_Q);
      }
      if (this._move.rocket) {
        this._velocity.z -= this._acceleration.x * timeInSeconds;
      }

      controlObject.quaternion.copy(_R);

      const oldPosition = new THREE.Vector3();
      oldPosition.copy(controlObject.position);

      const forward = new THREE.Vector3(0, 0, 1);
      forward.applyQuaternion(controlObject.quaternion);
      //forward.y = 0;
      forward.normalize();

      const updown = new THREE.Vector3(0, 1, 0);

      const sideways = new THREE.Vector3(1, 0, 0);
      sideways.applyQuaternion(controlObject.quaternion);
      sideways.normalize();

      sideways.multiplyScalar(this._velocity.x * timeInSeconds);
      updown.multiplyScalar(this._velocity.y * timeInSeconds);
      forward.multiplyScalar(this._velocity.z * timeInSeconds);

      controlObject.position.add(forward);
      controlObject.position.add(sideways);
      controlObject.position.add(updown);

      oldPosition.copy(controlObject.position);
    }
  };

  return {
    ShipControls: _ShipControls,
    FPSControls: _FPSControls,
    OrbitControls: _OrbitControls,
  };
})();
