import { Mesh } from '../../mesh'
import { BoundingBox } from '../../mesh/aabb'
import { mat4, vec3 } from 'gl-matrix'
import { subv3, mulv3, addv3, divv3, normalv3, crossv3 } from '../../utility'
import Screen from '../../screen'

const lodMax = 8

class PlanetGenerator {
  constructor (resolution, radius, noise) {
    this.resolution = resolution
    this.radius = radius
    this.noise = noise
  }
}

class CubeFace {
  constructor (parent, level, pos, normal, generator) {
    this.parent = parent
    this.children = []

    this.normal = normal

    this.level = level

    this.generated = false
    this.generator = generator

    // Calculate left (x) and forward (z) vectors from the normal (y)
    this.left = [normal[1], normal[2], normal[0]]
    this.forward = crossv3(normal, this.left)

    this.position = pos

    // Center the face
    if (level === 0) {
      this.position = subv3(this.position, mulv3(this.left, generator.radius / 2))
      this.position = subv3(this.position, mulv3(this.forward, generator.radius / 2))
    }

    this.generate()
  }

  generate () {
    if (this.generated) return

    let VERTICES = this.generator.resolution
    let count = VERTICES * VERTICES
    let vertices = new Array(count * 3)
    let normals = new Array(count * 3)
    let textureCoords = new Array(count * 2)
    let indices = new Array(6 * (VERTICES - 1) * (VERTICES - 1))

    let radius = this.generator.radius
    let divisionLevel = Math.pow(2, this.level)

    for (let i = 0, vertexPointer = 0; i < VERTICES; i++) {
      for (let j = 0; j < VERTICES; j++, vertexPointer++) {
        // Vertex index (0 - 1)
        let iindex = i / (VERTICES - 1)
        let jindex = j / (VERTICES - 1)

        // From the left and forward vectors, we can calculate an oriented vertex
        let iv = divv3(mulv3(mulv3(this.left, iindex), radius), divisionLevel)
        let jv = divv3(mulv3(mulv3(this.forward, jindex), radius), divisionLevel)

        // Add the scaled left and forward to the centered origin
        let vertex = addv3(this.position, addv3(iv, jv))

        // Normalize and multiply by radius to create a spherical mesh
        let normal = normalv3(vertex)
        let pos = mulv3(normal, (radius))

        vertices[vertexPointer * 3] = pos[0]
        vertices[vertexPointer * 3 + 1] = pos[1]
        vertices[vertexPointer * 3 + 2] = pos[2]
        normals[vertexPointer * 3] = normal[0]
        normals[vertexPointer * 3 + 1] = normal[1]
        normals[vertexPointer * 3 + 2] = normal[2]
        textureCoords[vertexPointer * 2] = j * (1 / VERTICES)
        textureCoords[vertexPointer * 2 + 1] = i * (1 / VERTICES)

        if (i === VERTICES / 2 && j === VERTICES / 2) {
          this.center = pos
        }
      }
    }

    for (let gz = 0, pointer = 0; gz < VERTICES - 1; gz++) {
      for (let gx = 0; gx < VERTICES - 1; gx++) {
        let topLeft = (gz * VERTICES) + gx
        let topRight = topLeft + 1
        let bottomLeft = ((gz + 1) * VERTICES) + gx
        let bottomRight = bottomLeft + 1
        indices[pointer++] = topLeft
        indices[pointer++] = bottomLeft
        indices[pointer++] = topRight
        indices[pointer++] = topRight
        indices[pointer++] = bottomLeft
        indices[pointer++] = bottomRight
      }
    }

    this.mesh = Mesh.construct(Screen.gl, vertices, indices, textureCoords, normals)
    this.generated = true
  }

  dispose () {
    this.generated = false
    if (this.mesh) {
      // this.mesh.dispose(Screen.gl)
      this.mesh = null
    }
  }

  merge () {
    if (!this.children.length) return

    for (let i in this.children) {
      let ch = this.children[i]

      ch.merge()
      ch.dispose()
    }

    this.children = []
    this.generate()
  }

  subdivide () {
    if (this.level === lodMax) return

    let lv = this.level + 1
    let stepLeft = mulv3(this.left, this.generator.radius / Math.pow(2, lv))
    let stepForward = mulv3(this.forward, this.generator.radius / Math.pow(2, lv))

    this.children = [
      new CubeFace(this, lv, this.position, this.normal, this.generator),
      new CubeFace(this, lv, addv3(this.position, stepForward), this.normal, this.generator),
      new CubeFace(this, lv, addv3(this.position, stepLeft), this.normal, this.generator),
      new CubeFace(this, lv, addv3(this.position, addv3(stepLeft, stepForward)), this.normal, this.generator)
    ]

    this.dispose()
  }

  draw (gl, shader) {
    if (!this.mesh) {
      for (let i in this.children) {
        this.children[i].draw(gl, shader)
      }
      return
    }

    this.mesh.prepare(gl, shader)
    this.mesh.draw(gl, shader)
  }

  update (camera, dt) {
    if (!this.center) return
    let camToOrigin = vec3.distance(camera.pos, this.center)
    let divisionLevel = Math.pow(2, this.level)
    let splitDistance = this.generator.radius / divisionLevel

    if (camToOrigin < splitDistance * 1.5 && this.children.length === 0) {
      this.subdivide()
      return
    } else if (camToOrigin > splitDistance * 2 && this.children.length > 0) {
      this.merge()
      return
    }

    if (this.children.length > 0) {
      for (let i in this.children) {
        this.children[i].update(camera, dt)
      }
    }
  }
}

class CubePlanet {
  constructor (origin, generator) {
    this.origin = origin
    this.generator = generator

    this.transform = mat4.create()
    mat4.fromTranslation(this.transform, origin)

    let hs = generator.radius / 2

    this.faces = [
      new CubeFace(this, 0, [0, 0, -hs], [0, 0, -1], generator), // front
      new CubeFace(this, 0, [0, 0, hs], [0, 0, 1], generator), // back

      new CubeFace(this, 0, [-hs, 0, 0], [-1, 0, 0], generator), // left
      new CubeFace(this, 0, [hs, 0, 0], [1, 0, 0], generator), // right

      new CubeFace(this, 0, [0, hs, 0], [0, 1, 0], generator), // top
      new CubeFace(this, 0, [0, -hs, 0], [0, -1, 0], generator) // bottom
    ]
  }

  update (camera, dt) {
    for (let i in this.faces) {
      this.faces[i].update(camera, dt)
    }
  }

  draw (gl, shader) {
    // Set model transform matrix uniform
    const transformLocation = shader.getUniformLocation(gl, 'uModelMatrix')
    gl.uniformMatrix4fv(transformLocation, false, this.transform)

    for (let i in this.faces) {
      this.faces[i].draw(gl, shader)
    }
  }
}

export { CubePlanet, CubeFace, PlanetGenerator }
