export interface VertexBufferOptions {
  /**
   * WebGL2RenderingContext this layout will be attached to, these cannot be reused across contexts.
   */
  gl: WebGL2RenderingContext,
  /**
   * Size in number of floats, so [4.2, 4.0, 2.1] is size = 3
   *
   * Ignored if data is passed directly
   */
  size?: number;
  /**
   * If the vertices never change switching 'static' can be more efficient on the gpu
   *
   * Default is 'dynamic'
   */
  type?: 'static' | 'dynamic';

  /**
   * Optionally pass pre-seeded data, size parameter is ignored
   */
  data?: Float32Array
}

/**
 * Helper around vertex buffer to simplify creating and uploading geometry
 *
 * Under the hood uses Float32Array
 */
export class VertexBuffer {
  private _gl: WebGL2RenderingContext;

  /**
   * Access to the webgl buffer handle
   */
  public readonly buffer: WebGLBuffer;
  /**
   * Access to the raw data of the vertex buffer
   */
  public readonly bufferData: Float32Array;

  /**
   * If the vertices never change switching 'static' can be more efficient on the gpu
   *
   * Default is 'dynamic'
   */
  public type: 'static' | 'dynamic' = 'dynamic';

  constructor(options: VertexBufferOptions) {
    const { gl, size, type, data } = options;
    this._gl = gl;
    this.buffer = this._gl.createBuffer();
    if (!data && !size) {
      throw Error('Must either provide data or a size to the VertexBuffer');
    }

    if (!data) {
      this.bufferData = new Float32Array(size);
    } else {
      this.bufferData = data;
    }
    this.type = type ?? this.type;
    // Allocate buffer
    gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
    gl.bufferData(gl.ARRAY_BUFFER, this.bufferData, this.type === 'static' ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW);
  }

  /**
   * Bind this vertex buffer
   */
  bind() {
    const gl = this._gl;
    gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
  }

  unbind() {
    const gl = this._gl;
    gl.bindBuffer(gl.ARRAY_BUFFER, null);
  }

  /**
   * Upload vertex buffer geometry to the GPU
   */
  upload(count?: number) {
    const gl = this._gl;
    gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
    if (count) {
      gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.bufferData, 0, count);
    } else {
      // TODO always use bufferSubData? need to perf test it
      gl.bufferData(gl.ARRAY_BUFFER, this.bufferData, this.type === 'static' ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW);
    }
  }

  dispose() {
    const gl = this._gl;
    gl.deleteBuffer(this.buffer);
    this._gl = null;
  }
}