import { Mesh } from '../mesh'
import { mat4 } from 'gl-matrix'

const VERTEX_SHADER = `
precision mediump float;
attribute vec2 aVertexPosition;
varying vec2 uv;
uniform mat4 uTransformationMatrix;
void main() {
  gl_Position = uTransformationMatrix * vec4(aVertexPosition, 0.0, 1.0);
  uv = vec2((aVertexPosition.x+1.0)/2.0, 1.0 - (aVertexPosition.y+1.0)/2.0);
}
`

const FRAGMENT_SHADER = `
precision mediump float;
varying vec2 uv;
uniform sampler2D texture0;
void main() {
  gl_FragColor = texture2D(texture0, uv);
}
`

class Dim4 {
  constructor (scX, ofX, scY, ofY) {
    this.scX = scX
    this.ofX = ofX
    this.scY = scY
    this.ofY = ofY
    this._update()
  }

  static fromTable (tbl) {
    let sx = 0.0
    let ox = 0.0
    let sy = 0.0
    let oy = 0.0
    if (tbl.length === 2) {
      let x = tbl[0]
      let y = tbl[1]

      if (typeof x === 'object' && x.length === 2) {
        sx = x[0]
        ox = x[1]
      } else if (typeof x === 'number') {
        sx = x
      }

      if (typeof y === 'object' && y.length === 2) {
        sx = y[0]
        ox = y[1]
      } else if (typeof y === 'number') {
        sx = y
      }
    } else if (tbl.length === 4) {
      sx = tbl[0]
      ox = tbl[1]
      sy = tbl[2]
      oy = tbl[3]
    }
    return new Dim4(sx, ox, sy, oy)
  }

  toTable () {
    return [this.scX, this.ofX, this.scY, this.ofY]
  }

  _update () {
    this[0] = this.scX
    this[1] = this.ofX
    this[2] = this.scY
    this[3] = this.ofY
  }
}

class Node2D {
  constructor (pos, size, rotation) {
    // Translation
    this.gpos = [0.0, 0.0]
    this.pos = pos || new Dim4(0.0, 0.0, 0.0, 0.0)

    // Scaling
    this.gscale = [1.0, 1.0]
    this.size = size || new Dim4(1.0, 0.0, 1.0, 0.0)

    // Rotation in degrees
    this.rotation = rotation || 0.0

    this.parent = null
    this.children = []

    this.transform = mat4.create()

    this.updateTransform()
  }

  updateTransform () {
    let matrix = mat4.create()
    mat4.translate(matrix, matrix, [this.pos[0], this.pos[2], 0.0])
    mat4.scale(matrix, matrix, [this.size[0], this.size[2], 1.0])
    if (this.rotation !== 0.0) {
      mat4.rotate(matrix, matrix, this.rotation * Math.PI / 180, [0.0, 0.0, 1.0])
    }

    // Add parent's transform to this
    if (this.parent) {
      mat4.mul(matrix, this.parent.transform, matrix)
    }

    // Set the matrix
    this.transform = matrix

    // Update children's transforms
    for (let i in this.children) {
      let child = this.children[i]
      if (!(child instanceof Node2D)) continue
      child.updateTransform()
    }
  }

  // Getters
  get position () {
    return this.pos
  }

  get translation () {
    return this.pos
  }

  // Draw base
  draw (gl, shader, quad) {
    // Nothing to draw here, so just draw children
    for (let i in this.children) {
      let child = this.children[i]
      if (!(child instanceof Node2D)) continue
      child.draw(gl, shader, quad)
    }
  }

  update (dt) {
    // Update children
    for (let i in this.children) {
      let child = this.children[i]
      if (!(child instanceof Node2D)) continue
      child.update(dt)
    }
  }

  addChild (ch) {
    // Recursive add in case of table
    if (ch && typeof ch === 'object' && ch.length) {
      for (let i in ch) {
        this.addChild(ch[i])
      }
    }

    if (!(ch instanceof Node2D)) return
    this.children.push(ch)
  }

  setParent (p) {
    if (!(p instanceof Node2D)) return
    this.parent = p
  }
}

class GUIImage extends Node2D {
  constructor (texture, pos, size, rotation) {
    super(pos, size, rotation)
    this.texture = texture
    this.active = true
  }

  draw (gl, shader, quad) {
    super.draw(gl, shader, quad)
    if (!this.active) return
    gl.activeTexture(gl.TEXTURE0)
    if (!this.texture.type) {
      gl.bindTexture(gl.TEXTURE_2D, this.texture)
    } else {
      gl.bindTexture(this.texture.type, this.texture.id)
    }

    // Set transformation matrix
    const transformLocation = shader.getUniformLocation(gl, 'uTransformationMatrix')
    gl.uniformMatrix4fv(transformLocation, false, this.transform)

    // Draw the quad
    quad.draw(gl, shader, gl.TRIANGLE_STRIP)
  }
}

class GUIRenderer {
  async initialize (game) {
    this.shader = await game.shaders.createShader(game.gl, 'gui', VERTEX_SHADER, FRAGMENT_SHADER)
    this.createQuad(game.gl)
  }

  // Reusable quad mesh for rendering GUIs
  createQuad (gl) {
    if (this.quad) return this.quad
    this.quad = Mesh.constructFromVertices(gl, [-1, 1, -1, -1, 1, 1, 1, -1], 2)
    return this.quad
  }

  draw (gl, cam, nodes) {
    if (typeof nodes !== 'object') nodes = [ nodes ]
    this.shader.use(gl)
    this.quad.prepare(gl, this.shader)
    gl.enable(gl.BLEND)
    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
    gl.disable(gl.DEPTH_TEST)
    for (let i in nodes) {
      nodes[i].draw(gl, this.shader, this.quad)
    }
    gl.enable(gl.DEPTH_TEST)
    gl.disable(gl.BLEND)
  }
}

export { Node2D, Dim4, GUIRenderer, GUIImage }
