
class Mesh {
  static loadToBuffer (gl, type, data, drawtype = gl.STATIC_DRAW) {
    let id = gl.createBuffer()
    gl.bindBuffer(type, id)
    gl.bufferData(type, data, drawtype)
    return id
  }

  static construct (gl, vertices, indices, uvs, normals) {
    // VBO for model vertices
    let pos = Mesh.loadToBuffer(gl, gl.ARRAY_BUFFER, new Float32Array(vertices))

    // Create the mesh, as we have the most important data already buffered
    let mesh = new Mesh()
    mesh.posBuffer = pos
    mesh.vertices = vertices

    // Indices Buffer (EBO)
    if (indices) {
      let ebo = Mesh.loadToBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices))
      mesh.ebo = ebo
      mesh.indices = indices
    } else {
      mesh.vertexCount = vertices.length / 3
      mesh.vertexLayout = 3
    }

    // VBO for model UVs
    if (uvs) {
      mesh.uv = uvs
      mesh.uvs = Mesh.loadToBuffer(gl, gl.ARRAY_BUFFER, new Float32Array(uvs))
    }

    // Normals buffer
    if (normals) {
      mesh.normals = normals
      mesh.nms = Mesh.loadToBuffer(gl, gl.ARRAY_BUFFER, new Float32Array(normals))
    }

    gl.bindBuffer(gl.ARRAY_BUFFER, null)

    return mesh
  }

  static constructFromVertices (gl, vertices, dimensions = 3) {
    let pos = Mesh.loadToBuffer(gl, gl.ARRAY_BUFFER, new Float32Array(vertices))
    let mesh = new Mesh()
    mesh.posBuffer = pos
    mesh.vertices = vertices
    mesh.vertexCount = vertices.length / dimensions
    mesh.vertexLayout = dimensions
    gl.bindBuffer(gl.ARRAY_BUFFER, null)

    return mesh
  }

  static constructFromVerticesUVs (gl, vertices, uvs, dimensions = 2) {
    let pos = Mesh.loadToBuffer(gl, gl.ARRAY_BUFFER, new Float32Array(vertices))
    let mesh = new Mesh()
    mesh.uv = uvs
    mesh.posBuffer = pos
    mesh.vertices = vertices
    mesh.vertexCount = vertices.length / dimensions
    mesh.vertexLayout = dimensions
    mesh.uvs = Mesh.loadToBuffer(gl, gl.ARRAY_BUFFER, new Float32Array(uvs))
    gl.bindBuffer(gl.ARRAY_BUFFER, null)

    return mesh
  }

  bindBuffers (gl, shader) {
    this._bufferCount = 1

    let d = this.vertexLayout || 3
    gl.bindBuffer(gl.ARRAY_BUFFER, this.posBuffer)
    shader.setAttribute(gl, 'aVertexPosition', d, false, d * Float32Array.BYTES_PER_ELEMENT, 0)

    if (this.nms && shader.hasAttribute(gl, 'aNormal')) {
      gl.bindBuffer(gl.ARRAY_BUFFER, this.nms)
      shader.setAttribute(gl, 'aNormal', 3, false, 3 * Float32Array.BYTES_PER_ELEMENT, 0)
      this._bufferCount++
    }

    if (this.uvs && shader.hasAttribute(gl, 'aTexCoords')) {
      gl.bindBuffer(gl.ARRAY_BUFFER, this.uvs)
      shader.setAttribute(gl, 'aTexCoords', 2, false, 2 * Float32Array.BYTES_PER_ELEMENT, 0)
      this._bufferCount++
    }

    this.ebo && gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.ebo)
  }

  prepare (gl, shader) {
    // Bind attrib arrays
    this.bindBuffers(gl, shader)

    // Give materials to shader
    if (this.material) {
      this.material.apply(gl, shader)
    }
  }

  draw (gl, shader, mode = gl.TRIANGLES) {
    if (this.indices) {
      gl.drawElements(mode, this.indices.length, gl.UNSIGNED_SHORT, 0)
    } else {
      gl.drawArrays(mode, 0, this.vertexCount || this.vertices.length / (this.vertexLayout || 3))
    }
  }

  // Unbind all attrib arrays after drawing something
  postdraw (gl, shader) {
    for (let i = 0; i < this._bufferCount; i++) {
      gl.disableVertexAttribArray(i)
    }
  }

  // Make sure no data floats around in memory
  dispose (gl) {
    gl.deleteBuffer(this.posBuffer)
    this.ebo && gl.deleteBuffer(this.ebo)
    this.uvs && gl.deleteBuffer(this.uvs)
    this.nms && gl.deleteBuffer(this.nms)
    this.vertices = null
    this.indices = null
    this.uv = null
    this.normals = null
  }

  recreate (gl) {
    let msh = Mesh.construct(gl, this.vertices, this.indices, this.uv, this.normals)
    if (!msh) return null
    this.dispose(gl)
    return msh
  }
}

export { Mesh }
