/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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";

// How many times, maximum, can we loop before we find the optimal time
// interval in the timeline graph.
const OPTIMAL_TIME_INTERVAL_MAX_ITERS = 100;
// Background time graduations should be multiple of this number of millis.
const TIME_INTERVAL_MULTIPLE = 10;
const TIME_INTERVAL_SCALES = 3;
// The default minimum spacing between time graduations in px.
const TIME_GRADUATION_MIN_SPACING = 10;
// RGB color for the time interval background.
const TIME_INTERVAL_COLOR = [128, 136, 144];
const TIME_INTERVAL_OPACITY_MIN = 32; // byte
const TIME_INTERVAL_OPACITY_ADD = 32; // byte

/**
 * DOM node creation helper function.
 * @param {Object} Options to customize the node to be created.
 * - nodeType {String} Optional, defaults to "div",
 * - attributes {Object} Optional attributes object like
 *   {attrName1:value1, attrName2: value2, ...}
 * - parent {DOMNode} Mandatory node to append the newly created node to.
 * - textContent {String} Optional text for the node.
 * @return {DOMNode} The newly created node.
 */
function createNode(options) {
  if (!options.parent) {
    throw new Error("Missing parent DOMNode to create new node");
  }

  let type = options.nodeType || "div";
  let node = options.parent.ownerDocument.createElement(type);

  for (let name in options.attributes || {}) {
    let value = options.attributes[name];
    node.setAttribute(name, value);
  }

  if (options.textContent) {
    node.textContent = options.textContent;
  }

  options.parent.appendChild(node);
  return node;
}

exports.createNode = createNode;

/**
 * Given a data-scale, draw the background for a graph (vertical lines) into a
 * canvas and set that canvas as an image-element with an ID that can be used
 * from CSS.
 * @param {Document} document The document where the image-element should be set.
 * @param {String} id The ID for the image-element.
 * @param {Number} graphWidth The width of the graph.
 * @param {Number} timeScale How many px is 1ms in the graph.
 */
function drawGraphElementBackground(document, id, graphWidth, timeScale) {
  let canvas = document.createElement("canvas");
  let ctx = canvas.getContext("2d");

  // Set the canvas width (as requested) and height (1px, repeated along the Y
  // axis).
  canvas.width = graphWidth;
  canvas.height = 1;

  // Create the image data array which will receive the pixels.
  let imageData = ctx.createImageData(canvas.width, canvas.height);
  let pixelArray = imageData.data;

  let buf = new ArrayBuffer(pixelArray.length);
  let view8bit = new Uint8ClampedArray(buf);
  let view32bit = new Uint32Array(buf);

  // Build new millisecond tick lines...
  let [r, g, b] = TIME_INTERVAL_COLOR;
  let alphaComponent = TIME_INTERVAL_OPACITY_MIN;
  let interval = findOptimalTimeInterval(timeScale);

  // Insert one pixel for each division on each scale.
  for (let i = 1; i <= TIME_INTERVAL_SCALES; i++) {
    let increment = interval * Math.pow(2, i);
    for (let x = 0; x < canvas.width; x += increment) {
      let position = x | 0;
      view32bit[position] = (alphaComponent << 24) | (b << 16) | (g << 8) | r;
    }
    alphaComponent += TIME_INTERVAL_OPACITY_ADD;
  }

  // Flush the image data and cache the waterfall background.
  pixelArray.set(view8bit);
  ctx.putImageData(imageData, 0, 0);
  document.mozSetImageElement(id, canvas);
}

exports.drawGraphElementBackground = drawGraphElementBackground;

/**
 * Find the optimal interval between time graduations in the animation timeline
 * graph based on a time scale and a minimum spacing.
 * @param {Number} timeScale How many px is 1ms in the graph.
 * @param {Number} minSpacing The minimum spacing between 2 graduations,
 * defaults to TIME_GRADUATION_MIN_SPACING.
 * @return {Number} The optimal interval, in pixels.
 */
function findOptimalTimeInterval(timeScale,
                                 minSpacing=TIME_GRADUATION_MIN_SPACING) {
  let timingStep = TIME_INTERVAL_MULTIPLE;
  let numIters = 0;

  if (timeScale > minSpacing) {
    return timeScale;
  }

  while (true) {
    let scaledStep = timeScale * timingStep;
    if (++numIters > OPTIMAL_TIME_INTERVAL_MAX_ITERS) {
      return scaledStep;
    }
    if (scaledStep < minSpacing) {
      timingStep *= 2;
      continue;
    }
    return scaledStep;
  }
}

exports.findOptimalTimeInterval = findOptimalTimeInterval;
