import Screen from './screen'
import { Node } from './components'
import { glMatrix, mat4, vec2, vec3 } from 'gl-matrix'

const SPEED = 100.0
const SENSITIVTY = 100.0
const FOV = 45.0
const ZNEAR = 0.1
const ZFAR = 10000.0

class Camera extends Node {
  constructor (pos, rotation) {
    super(pos, null, rotation)
    this.fov = FOV
    this.speed = SPEED
    this.sensitivity = SENSITIVTY

    // Create an empty projection matrix
    this.projection = mat4.create()

    // Helping vectors for calculating the view matrix
    this.up = vec3.create()
    this.front = vec3.fromValues(0.0, 0.0, -1.0)
    this.right = vec3.create()
    this.worldUp = vec3.fromValues(0.0, 1.0, 0.0)

    this.nearPlane = ZNEAR
    this.farPlane = ZFAR

    this.updateTransform()
  }

  processKeyboard (direction, delta) {
    let newSpeed = this.speed * delta
    let velocity = vec3.fromValues(newSpeed, newSpeed, newSpeed)
    let vec = vec3.create()

    if (direction === 0) {
      vec3.multiply(vec, this.front, velocity)
      vec3.add(this.pos, this.pos, vec)
    }

    if (direction === 1) {
      vec3.multiply(vec, this.front, velocity)
      vec3.sub(this.pos, this.pos, vec)
    }

    if (direction === 2) {
      vec3.multiply(vec, this.right, velocity)
      vec3.sub(this.pos, this.pos, vec)
    }

    if (direction === 3) {
      vec3.multiply(vec, this.right, velocity)
      vec3.add(this.pos, this.pos, vec)
    }
  }

  processMouseMove (offset, constrain = true) {
    let fst = vec2.fromValues(offset.x * -this.sensitivity, offset.y * this.sensitivity)
    this.rotation[0] += glMatrix.toRadian(fst[0])
    this.rotation[1] += glMatrix.toRadian(fst[1])

    // Make sure that when pitch is out of bounds, screen doesn't get flipped
    if (constrain) {
      if (this.rotation[1] > glMatrix.toRadian(89.0)) {
        this.rotation[1] = glMatrix.toRadian(89.0)
      }

      if (this.rotation[1] < -glMatrix.toRadian(89.0)) {
        this.rotation[1] = -glMatrix.toRadian(89.0)
      }
    }

    this.updateTransform()
  }

  // Calculate the vertices required for the view matrix
  updateTransform () {
    // Prevent premature call (from super class)
    if (!this.front || !this.worldUp) return

    // Calculate the new Front vector
    let front = vec3.create()

    front[0] = Math.cos(this.rotation[0]) * Math.cos(this.rotation[1])
    front[1] = Math.sin(this.rotation[1])
    front[2] = Math.sin(this.rotation[0]) * Math.cos(this.rotation[1])

    vec3.normalize(this.front, front)

    // Also re-calculate the Right and Up vector
    // Normalize the vectors, because their length gets closer to 0 the more you look up or down which results in slower movement.
    let rightCross = vec3.create()
    let upCross = vec3.create()

    vec3.cross(rightCross, this.front, this.worldUp)
    vec3.normalize(this.right, rightCross)

    vec3.cross(upCross, this.right, this.front)
    vec3.normalize(this.up, upCross)
  }

  updateProjection (gl) {
    mat4.perspective(this.projection, this.fov, Screen.aspectRatio, this.nearPlane, this.farPlane)
  }

  // Calculate the view matrix on-the-go
  // Really no advantage in storing this
  get view () {
    let mat = mat4.create()
    let center = vec3.create()
    vec3.add(center, this.pos, this.front)
    mat4.lookAt(mat, this.pos, center, this.up)
    return mat
  }

  // Override the default draw method because we don't need to draw the camera,
  // instead set the projection and view matrices
  draw (gl, shader) {
    const projloc = shader.getUniformLocation(gl, 'uProjectionMatrix')
    const viewloc = shader.getUniformLocation(gl, 'uViewMatrix')

    gl.uniformMatrix4fv(projloc, false, this.projection)
    gl.uniformMatrix4fv(viewloc, false, this.view)
  }
}

export default Camera
