// Copyright 2011 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

WebGLTestUtils = (function() {
  /**
   * Converts a WebGL enum to a string
   * @param {!WebGLContext} gl The WebGLContext to use.
   * @param {number} value The enum value.
   * @return {string} The enum as a string.
   */
  var glEnumToString = function(gl, value) {
    for (var p in gl) {
      if (gl[p] == value) {
        return p;
      }
    }
    return '0x' + value.toString(16);
  };

  var lastError = '';

  /**
   * Returns the last compiler/linker error.
   * @return {string} The last compiler/linker error.
   */
  var getLastError = function() {
    return lastError;
  };

  // clang-format off

  /**
   * A vertex shader for a single texture.
   * @type {string}
   */
  var simpleTextureVertexShader = [
    'attribute vec4 vPosition;',  //
    'attribute vec2 texCoord0;',
    'varying vec2 texCoord;',
    'void main() {',
    '    gl_Position = vPosition;',
    '    texCoord = texCoord0;',
    '}'
  ].join('\n');

  /**
   * A fragment shader for a single texture.
   * @type {string}
   */
  var simpleTextureFragmentShader = [
    'precision mediump float;',
    'uniform sampler2D tex;',
    'varying vec2 texCoord;',
    'void main() {',
    '    gl_FragData[0] = texture2D(tex, texCoord);',
    '}'
  ].join('\n');

  // clang-format on

  /**
   * Creates a simple texture vertex shader.
   * @param {!WebGLContext} gl The WebGLContext to use.
   * @return {!WebGLShader}
   */
  var setupSimpleTextureVertexShader = function(gl) {
    return loadShader(gl, simpleTextureVertexShader, gl.VERTEX_SHADER);
  };

  /**
   * Creates a simple texture fragment shader.
   * @param {!WebGLContext} gl The WebGLContext to use.
   * @return {!WebGLShader}
   */
  var setupSimpleTextureFragmentShader = function(gl) {
    return loadShader(gl, simpleTextureFragmentShader, gl.FRAGMENT_SHADER);
  };

  /**
   * Creates a program, attaches shaders, binds attrib locations, links the
   * program and calls useProgram.
   * @param {!Array.<!WebGLShader>} shaders The shaders to attach .
   * @param {!Array.<string>} opt_attribs The attribs names.
   * @param {!Array.<number>} opt_locations The locations for the attribs.
   */
  var setupProgram = function(gl, shaders, opt_attribs, opt_locations) {
    var realShaders = [];
    var program = gl.createProgram();
    for (var ii = 0; ii < shaders.length; ++ii) {
      var shader = shaders[ii];
      if (typeof shader == 'string') {
        var element = document.getElementById(shader);
        if (element) {
          shader = loadShaderFromScript(gl, shader);
        } else {
          shader = loadShader(
              gl, shader, ii ? gl.FRAGMENT_SHADER : gl.VERTEX_SHADER);
        }
      }
      gl.attachShader(program, shader);
    }
    if (opt_attribs) {
      for (var ii = 0; ii < opt_attribs.length; ++ii) {
        gl.bindAttribLocation(
            program, opt_locations ? opt_locations[ii] : ii, opt_attribs[ii]);
      }
    }
    gl.linkProgram(program);

    // Check the link status
    var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
    if (!linked) {
      gl.deleteProgram(program);
      return null;
    }

    gl.useProgram(program);
    return program;
  };

  /**
   * Creates a simple texture program.
   * @param {!WebGLContext} gl The WebGLContext to use.
   * @param {number} opt_positionLocation The attrib location for position.
   * @param {number} opt_texcoordLocation The attrib location for texture
   *     coords.
   * @return {WebGLProgram}
   */
  var setupSimpleTextureProgram = function(
      gl, opt_positionLocation, opt_texcoordLocation) {
    opt_positionLocation = opt_positionLocation || 0;
    opt_texcoordLocation = opt_texcoordLocation || 1;
    var vs = setupSimpleTextureVertexShader(gl);
    var fs = setupSimpleTextureFragmentShader(gl);
    if (!vs || !fs) {
      return null;
    }
    var program = setupProgram(
        gl, [vs, fs], ['vPosition', 'texCoord0'],
        [opt_positionLocation, opt_texcoordLocation]);
    if (!program) {
      gl.deleteShader(fs);
      gl.deleteShader(vs);
    }
    gl.useProgram(program);
    return program;
  };

  /**
   * Creates buffers for a textured unit quad and attaches them to vertex
   * attribs.
   * @param {!WebGLContext} gl The WebGLContext to use.
   * @param {number} opt_positionLocation The attrib location for position.
   * @param {number} opt_texcoordLocation The attrib location for texture
   *     coords.
   * @return {!Array.<WebGLBuffer>} The buffer objects that were
   *      created.
   */
  var setupUnitQuad = function(gl, opt_positionLocation, opt_texcoordLocation) {
    opt_positionLocation = opt_positionLocation || 0;
    opt_texcoordLocation = opt_texcoordLocation || 1;
    var objects = [];

    var vertexObject = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject);
    gl.bufferData(
        gl.ARRAY_BUFFER, new Float32Array([
          1.0, 1.0, 0.0, -1.0, 1.0, 0.0, -1.0, -1.0, 0.0, 1.0, 1.0, 0.0, -1.0,
          -1.0, 0.0, 1.0, -1.0, 0.0
        ]),
        gl.STATIC_DRAW);
    gl.enableVertexAttribArray(opt_positionLocation);
    gl.vertexAttribPointer(opt_positionLocation, 3, gl.FLOAT, false, 0, 0);
    objects.push(vertexObject);

    var vertexObject = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject);
    gl.bufferData(
        gl.ARRAY_BUFFER,
        new Float32Array(
            [1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0]),
        gl.STATIC_DRAW);
    gl.enableVertexAttribArray(opt_texcoordLocation);
    gl.vertexAttribPointer(opt_texcoordLocation, 2, gl.FLOAT, false, 0, 0);
    objects.push(vertexObject);
    return objects;
  };

  /**
   * Creates a program and buffers for rendering a textured quad.
   * @param {!WebGLContext} gl The WebGLContext to use.
   * @param {number} opt_positionLocation The attrib location for position.
   * @param {number} opt_texcoordLocation The attrib location for texture
   *     coords.
   * @return {!WebGLProgram}
   */
  var setupTexturedQuad = function(
      gl, opt_positionLocation, opt_texcoordLocation) {
    var program = setupSimpleTextureProgram(
        gl, opt_positionLocation, opt_texcoordLocation);
    setupUnitQuad(gl, opt_positionLocation, opt_texcoordLocation);
    return program;
  };

  /**
   * Draws a previously setup quad.
   * @param {!WebGLContext} gl The WebGLContext to use.
   * @param {!Array.<number>} opt_color The color to fill clear with before
   *        drawing. A 4 element array where each element is in the range 0 to
   *        255. Default [255, 255, 255, 255]
   */
  var drawQuad = function(gl, opt_color) {
    opt_color = opt_color || [255, 255, 255, 255];
    gl.clearColor(
        opt_color[0] / 255, opt_color[1] / 255, opt_color[2] / 255,
        opt_color[3] / 255);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    gl.drawArrays(gl.TRIANGLES, 0, 6);
  };

  /**
   * Links a WebGL program, throws if there are errors.
   * @param {!WebGLContext} gl The WebGLContext to use.
   * @param {!WebGLProgram} program The WebGLProgram to link.
   * @param {function(string): void) opt_errorCallback callback for errors.
   */
  var linkProgram = function(gl, program, opt_errorCallback) {
    // Link the program
    gl.linkProgram(program);

    // Check the link status
    var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
    if (!linked) {
      // something went wrong with the link
      gl.deleteProgram(program);
      return false;
    }

    return true;
  };

  /**
   * Loads a shader.
   * @param {!WebGLContext} gl The WebGLContext to use.
   * @param {string} shaderSource The shader source.
   * @param {number} shaderType The type of shader.
   * @param {function(string): void) opt_errorCallback callback for errors.
   * @return {!WebGLShader} The created shader.
   */
  var loadShader =
      function(gl, shaderSource, shaderType, opt_errorCallback) {
    var errFn = opt_errorCallback || (_ => {});
    // Create the shader object
    var shader = gl.createShader(shaderType);
    if (shader == null) {
      errFn('*** Error: unable to create shader \'' + shaderSource + '\'');
      return null;
    }

    // Load the shader source
    gl.shaderSource(shader, shaderSource);
    var err = gl.getError();
    if (err != gl.NO_ERROR) {
      errFn(
          '*** Error loading shader \'' + shader +
          '\':' + glEnumToString(gl, err));
      return null;
    }

    // Compile the shader
    gl.compileShader(shader);

    // Check the compile status
    var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
    if (!compiled) {
      // Something went wrong during compilation; get the error
      lastError = gl.getShaderInfoLog(shader);
      errFn('*** Error compiling shader \'' + shader + '\':' + lastError);
      gl.deleteShader(shader);
      return null;
    }

    return shader;
  }

  /**
   * Loads shaders from source, creates a program, attaches the shaders and
   * links.
   * @param {!WebGLContext} gl The WebGLContext to use.
   * @param {string} vertexShader The vertex shader.
   * @param {string} fragmentShader The fragment shader.
   * @param {function(string): void) opt_errorCallback callback for errors.
   * @return {!WebGLProgram} The created program.
   */
  var loadProgram = function(
      gl, vertexShader, fragmentShader, opt_errorCallback) {
    var program = gl.createProgram();
    gl.attachShader(
        program,
        loadShader(gl, vertexShader, gl.VERTEX_SHADER, opt_errorCallback));
    gl.attachShader(
        program,
        loadShader(gl, fragmentShader, gl.FRAGMENT_SHADER, opt_errorCallback));
    return linkProgram(gl, program, opt_errorCallback) ? program : null;
  };

  return {
    drawQuad: drawQuad,
    getLastError: getLastError,
    glEnumToString: glEnumToString,
    loadProgram: loadProgram,
    loadShader: loadShader,
    setupProgram: setupProgram,
    setupSimpleTextureFragmentShader: setupSimpleTextureFragmentShader,
    setupSimpleTextureProgram: setupSimpleTextureProgram,
    setupSimpleTextureVertexShader: setupSimpleTextureVertexShader,
    setupTexturedQuad: setupTexturedQuad,
    setupUnitQuad: setupUnitQuad,
  };
}());
