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

class ParticleTexture {
  constructor (texture, rows) {
    this.texture = texture
    this.rows = rows
  }

  get id () {
    return this.texture.id
  }
}

class Particle {
  constructor (texture, pos, vel, gravity, life, rot, scale) {
    this.texture = texture
    this.pos = pos
    this.vel = vel
    this.gravity = gravity
    this.life = life
    this.rot = rot
    this.scale = scale
    this.elapsed = 0

    this.texOffset1 = [0.0, 0.0]
    this.texOffset2 = [0.0, 0.0]
    this.texBlend = 0.0

    this.distance = 0
  }

  _createOffset (index) {
    let col = Math.floor(index % this.texture.rows)
    let row = Math.floor(index / this.texture.rows)
    return [ col / this.texture.rows, row / this.texture.rows ]
  }

  _updateTexture () {
    let lifeFactor = this.elapsed / this.life
    let stages = this.texture.rows * this.texture.rows
    let progression = lifeFactor * stages
    let index1 = Math.floor(progression)
    let index2 = (index1 < stages - 1) ? index1 + 1 : index1
    this.texBlend = progression % 1
    this.texOffset1 = this._createOffset(index1)
    this.texOffset2 = this._createOffset(index2)
  }

  update (dt, gravity) {
    this.vel[1] += gravity * this.gravity * dt
    let change = [this.vel[0] * dt, this.vel[1] * dt, this.vel[2] * dt]
    this.pos = [this.pos[0] + change[0], this.pos[1] + change[1], this.pos[2] + change[2]]
    this._updateTexture()
    this.elapsed += dt
    return this.elapsed < this.life
  }
}

class ParticleSystem {
  constructor (renderer) {
    this.renderer = renderer
    this.particles = []
  }

  add (particle) {
    this.particles.push(particle)
  }

  update (dt, gravity) {
    let alive = []
    for (let i in this.particles) {
      let particle = this.particles[i]
      let stillAlive = particle.update(dt, gravity)
      if (!stillAlive) continue
      alive.push(particle)
    }
    this.particles = alive
  }

  draw (gl, cam) {
    if (!this.renderer) return
    this.renderer.draw(gl, this, cam)
  }
}

class ParticleRenderer {
  async initialize (game) {
    this.shader = await game.shaders.createShaderFromFiles(game.gl, 'particles', false)
    this.createMesh(game.gl)
  }

  createMesh (gl) {
    this.mesh = Mesh.constructFromVertices(gl, [-0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, -0.5], 2)
  }

  createModelMatrix (shader, viewMatrix, particle) {
    let modelMatrix = mat4.create()
    mat4.translate(modelMatrix, modelMatrix, particle.pos)
    modelMatrix[0] = viewMatrix[0]
    modelMatrix[1] = viewMatrix[4]
    modelMatrix[2] = viewMatrix[8]
    modelMatrix[4] = viewMatrix[1]
    modelMatrix[5] = viewMatrix[5]
    modelMatrix[6] = viewMatrix[9]
    modelMatrix[8] = viewMatrix[2]
    modelMatrix[10] = viewMatrix[10]
    mat4.rotate(modelMatrix, modelMatrix, particle.rot * Math.PI / 180, [0.0, 0.0, 1.0])
    mat4.scale(modelMatrix, modelMatrix, [particle.scale, particle.scale, particle.scale])
    return modelMatrix
  }

  draw (gl, particles, cam) {
    particles = (particles instanceof ParticleSystem) ? particles.particles : particles
    let textures = []
    for (let i in particles) {
      let particle = particles[i]
      if (textures.indexOf(particle.texture) !== -1) continue
      textures.push(particle.texture)
    }

    this.shader.use(gl)
    const modelLoc = this.shader.getUniformLocation(gl, 'uModelMatrix')
    const off1Loc = this.shader.getUniformLocation(gl, 'uAtlasOffset1')
    const off2Loc = this.shader.getUniformLocation(gl, 'uAtlasOffset2')
    const atlasLoc = this.shader.getUniformLocation(gl, 'uAtlasInfo')

    cam.draw(gl, this.shader)

    gl.enable(gl.BLEND)
    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
    gl.depthMask(false)

    this.mesh.prepare(gl, this.shader)
    for (let i in textures) {
      let texture = textures[i]
      gl.activeTexture(gl.TEXTURE0)
      gl.bindTexture(gl.TEXTURE_2D, texture.id)
      for (let j in particles) {
        let particle = particles[j]
        if (particle.texture !== texture) continue

        let mat = this.createModelMatrix(this.shader, cam.view, particle)
        gl.uniformMatrix4fv(modelLoc, false, mat)
        gl.uniform2fv(off1Loc, particle.texOffset1)
        gl.uniform2fv(off2Loc, particle.texOffset2)
        gl.uniform2fv(atlasLoc, [texture.rows, particle.texBlend])

        this.mesh.draw(gl, this.shader, gl.TRIANGLE_STRIP)
      }
    }
    this.mesh.postdraw(gl, this.shader)

    gl.disable(gl.BLEND)
    gl.depthMask(true)
  }
}

export { ParticleTexture, Particle, ParticleSystem, ParticleRenderer }
