/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/*
 * Util base class to help test a captured canvas element. Initializes the
 * output canvas (used for testing the color of video elements), and optionally
 * overrides the default element |width| and |height|.
 */
function CaptureStreamTestHelper(width, height) {
  this.cout = document.createElement('canvas');
  if (width) {
    this.elemWidth = width;
  }
  if (height) {
    this.elemHeight = height;
  }
  this.cout.width = this.elemWidth;
  this.cout.height = this.elemHeight;
  document.body.appendChild(this.cout);
}

CaptureStreamTestHelper.prototype = {
  /* Predefined colors for use in the methods below. */
  black: { data: [0, 0, 0, 255], name: "black" },
  green: { data: [0, 255, 0, 255], name: "green" },
  red: { data: [255, 0, 0, 255], name: "red" },

  /* Default element size for createAndAppendElement() */
  elemWidth: 100,
  elemHeight: 100,

  /* Request a frame from the stream played by |video|. */
  requestFrame: function (video) {
    info("Requesting frame from " + video.id);
    video.mozSrcObject.requestFrame();
  },

  /* Tests the top left pixel of |video| against |refData|. Format [R,G,B,A]. */
  testPixel: function (video, refData, threshold) {
    var ctxout = this.cout.getContext('2d');
    ctxout.drawImage(video, 0, 0);
    var pixel = ctxout.getImageData(0, 0, 1, 1).data;
    return pixel.every((val, i) => Math.abs(val - refData[i]) <= threshold);
  },

  /*
   * Returns a promise that resolves when the pixel matches. Use |threshold|
   * for fuzzy matching the color on each channel, in the range [0,255].
   */
  waitForPixel: function (video, refColor, threshold, infoString) {
    return new Promise(resolve => {
      info("Testing " + video.id + " against [" + refColor.data.join(',') + "]");
      CaptureStreamTestHelper2D.prototype.clear.call(this, this.cout);
      video.ontimeupdate = () => {
        if (this.testPixel(video, refColor.data, threshold)) {
          ok(true, video.id + " " + infoString);
          video.ontimeupdate = null;
          resolve();
        }
      };
    });
  },

  /*
   * Returns a promise that resolves after |timeout| ms of playback or when a
   * pixel of |video| becomes the color |refData|. The test is failed if the
   * timeout is not reached.
   */
  waitForPixelToTimeout: function (video, refColor, threshold, timeout, infoString) {
    return new Promise(resolve => {
      info("Waiting for " + video.id + " to time out after " + timeout +
           "ms against [" + refColor.data.join(',') + "] - " + refColor.name);
      CaptureStreamTestHelper2D.prototype.clear.call(this, this.cout);
      var startTime = video.currentTime;
      video.ontimeupdate = () => {
        if (this.testPixel(video, refColor.data, threshold)) {
          ok(false, video.id + " " + infoString);
          video.ontimeupdate = null;
          resolve();
        } else if (video.currentTime > startTime + (timeout / 1000.0)) {
          ok(true, video.id + " " + infoString);
          video.ontimeupdate = null;
          resolve();
        }
      };
    });
  },

  /* Create an element of type |type| with id |id| and append it to the body. */
  createAndAppendElement: function (type, id) {
    var e = document.createElement(type);
    e.id = id;
    e.width = this.elemWidth;
    e.height = this.elemHeight;
    if (type === 'video') {
      e.autoplay = true;
    }
    document.body.appendChild(e);
    return e;
  },
}

/* Sub class holding 2D-Canvas specific helpers. */
function CaptureStreamTestHelper2D(width, height) {
  CaptureStreamTestHelper.call(this, width, height);
}

CaptureStreamTestHelper2D.prototype = Object.create(CaptureStreamTestHelper.prototype);
CaptureStreamTestHelper2D.prototype.constructor = CaptureStreamTestHelper2D;

/* Clear all drawn content on |canvas|. */
CaptureStreamTestHelper2D.prototype.clear = function(canvas) {
  var ctx = canvas.getContext('2d');
  ctx.clearRect(0, 0, canvas.width, canvas.height);
};

/* Draw the color |color| to the source canvas |canvas|. Format [R,G,B,A]. */
CaptureStreamTestHelper2D.prototype.drawColor = function(canvas, color) {
  var ctx = canvas.getContext('2d');
  var rgba = color.data.slice(); // Copy to not overwrite the original array
  info("Drawing color " + rgba.join(','));
  rgba[3] = rgba[3] / 255.0; // Convert opacity to double in range [0,1]
  ctx.fillStyle = "rgba(" + rgba.join(',') + ")";

  // Only fill top left corner to test that output is not flipped or rotated.
  ctx.fillRect(0, 0, canvas.width / 2, canvas.height / 2);
};

/* Test that the given 2d canvas is NOT origin-clean. */
CaptureStreamTestHelper2D.prototype.testNotClean = function(canvas) {
  var ctx = canvas.getContext('2d');
  var error = "OK";
  try {
    var data = ctx.getImageData(0, 0, 1, 1);
  } catch(e) {
    error = e.name;
  }
  is(error, "SecurityError",
     "Canvas '" + canvas.id + "' should not be origin-clean");
};

/* Sub class holding WebGL specific helpers. */
function CaptureStreamTestHelperWebGL(width, height) {
  CaptureStreamTestHelper.call(this, width, height);
}

CaptureStreamTestHelperWebGL.prototype = Object.create(CaptureStreamTestHelper.prototype);
CaptureStreamTestHelperWebGL.prototype.constructor = CaptureStreamTestHelperWebGL;

/* Set the (uniform) color location for future draw calls. */
CaptureStreamTestHelperWebGL.prototype.setFragmentColorLocation = function(colorLocation) {
  this.colorLocation = colorLocation;
};

/* Clear the given WebGL context with |color|. */
CaptureStreamTestHelperWebGL.prototype.clearColor = function(canvas, color) {
  info("WebGL: clearColor(" + color.name + ")");
  var gl = canvas.getContext('webgl');
  var conv = color.data.map(i => i / 255.0);
  gl.clearColor(conv[0], conv[1], conv[2], conv[3]);
  gl.clear(gl.COLOR_BUFFER_BIT);
};

/* Set an already setFragmentColorLocation() to |color| and drawArrays() */
CaptureStreamTestHelperWebGL.prototype.drawColor = function(canvas, color) {
  info("WebGL: drawArrays(" + color.name + ")");
  var gl = canvas.getContext('webgl');
  var conv = color.data.map(i => i / 255.0);
  gl.uniform4f(this.colorLocation, conv[0], conv[1], conv[2], conv[3]);
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
};
