/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";

/**
 * Unit test for `createTierGraphDataFromFrameNode` function.
 */

const SAMPLE_COUNT = 1000;
const RESOLUTION = 50;
const TIME_PER_SAMPLE = 5;

// Offset needed since ThreadNode requires the first sample to be strictly
// greater than its start time. This lets us still have pretty numbers
// in this test to keep it (more) simple, which it sorely needs.
const TIME_OFFSET = 5;

add_task(function test() {
  let { ThreadNode } = require("devtools/client/performance/modules/logic/tree-model");
  let { createTierGraphDataFromFrameNode } = require("devtools/client/performance/modules/logic/jit");

  // Select the second half of the set of samples
  let startTime = (SAMPLE_COUNT / 2 * TIME_PER_SAMPLE) - TIME_OFFSET;
  let endTime = (SAMPLE_COUNT * TIME_PER_SAMPLE) - TIME_OFFSET;
  let invertTree = true;

  let root = new ThreadNode(gThread, { invertTree, startTime, endTime });

  equal(root.samples, SAMPLE_COUNT / 2,
    "root has correct amount of samples");
  equal(root.sampleTimes.length, SAMPLE_COUNT / 2,
    "root has correct amount of sample times");
  // Add time offset since the first sample begins TIME_OFFSET after startTime
  equal(root.sampleTimes[0], startTime + TIME_OFFSET,
    "root recorded first sample time in scope");
  equal(root.sampleTimes[root.sampleTimes.length - 1], endTime,
    "root recorded last sample time in scope");

  let frame = getFrameNodePath(root, "X");
  let data = createTierGraphDataFromFrameNode(frame, root.sampleTimes,
    (endTime - startTime) / RESOLUTION);

  let TIME_PER_WINDOW = SAMPLE_COUNT / 2 / RESOLUTION * TIME_PER_SAMPLE;

  // Filter out the dupes created with the same delta so the graph
  // can render correctly.
  let filteredData = [];
  for (let i = 0; i < data.length; i++) {
    if (!i || data[i].delta !== data[i - 1].delta) {
      filteredData.push(data[i]);
    }
  }
  data = filteredData;

  for (let i = 0; i < 11; i++) {
    equal(data[i].delta, startTime + TIME_OFFSET + (TIME_PER_WINDOW * i),
          "first window has correct x");
    equal(data[i].values[0], 0.2, "first window has 2 frames in interpreter");
    equal(data[i].values[1], 0.2, "first window has 2 frames in baseline");
    equal(data[i].values[2], 0.2, "first window has 2 frames in ion");
  }
  // Start on 11, since i===10 is where the values change, and the new value (0,0,0)
  // is removed in `filteredData`
  for (let i = 11; i < 20; i++) {
    equal(data[i].delta, startTime + TIME_OFFSET + (TIME_PER_WINDOW * i),
          "second window has correct x");
    equal(data[i].values[0], 0, "second window observed no optimizations");
    equal(data[i].values[1], 0, "second window observed no optimizations");
    equal(data[i].values[2], 0, "second window observed no optimizations");
  }
  // Start on 21, since i===20 is where the values change, and the new value (0.3,0,0)
  // is removed in `filteredData`
  for (let i = 21; i < 30; i++) {
    equal(data[i].delta, startTime + TIME_OFFSET + (TIME_PER_WINDOW * i),
          "third window has correct x");
    equal(data[i].values[0], 0.3, "third window has 3 frames in interpreter");
    equal(data[i].values[1], 0, "third window has 0 frames in baseline");
    equal(data[i].values[2], 0, "third window has 0 frames in ion");
  }
});

var gUniqueStacks = new RecordingUtils.UniqueStacks();

function uniqStr(s) {
  return gUniqueStacks.getOrAddStringIndex(s);
}

const TIER_PATTERNS = [
  // 0-99
  ["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"],
  // 100-199
  ["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"],
  // 200-299
  ["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"],
  // 300-399
  ["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"],
  // 400-499
  ["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"],

  // 500-599
  // Test current frames in all opts
  ["A", "A", "A", "A", "X_1", "X_2", "X_1", "X_2", "X_0", "X_0"],

  // 600-699
  // Nothing for current frame
  ["A", "B", "A", "B", "A", "B", "A", "B", "A", "B"],

  // 700-799
  // A few frames where the frame is not the leaf node
  ["X_2 -> Y", "X_2 -> Y", "X_2 -> Y", "X_0", "X_0", "X_0", "A", "A", "A", "A"],

  // 800-899
  ["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"],
  // 900-999
  ["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"],
];

function createSample(i, frames) {
  let sample = {};
  sample.time = i * TIME_PER_SAMPLE;
  sample.frames = [{ location: "(root)" }];
  if (i === 0) {
    return sample;
  }
  if (frames) {
    frames.split(" -> ").forEach(frame => sample.frames.push({ location: frame }));
  }
  return sample;
}

var SAMPLES = (function () {
  let samples = [];

  for (let i = 0; i < SAMPLE_COUNT;) {
    let pattern = TIER_PATTERNS[Math.floor(i / 100)];
    for (let j = 0; j < pattern.length; j++) {
      samples.push(createSample(i + j, pattern[j]));
    }
    i += 10;
  }

  return samples;
})();

var gThread = RecordingUtils.deflateThread({ samples: SAMPLES, markers: [] },
                                           gUniqueStacks);

var gRawSite1 = {
  line: 12,
  column: 2,
  types: [{
    mirType: uniqStr("Object"),
    site: uniqStr("B (http://foo/bar:10)"),
    typeset: [{
      keyedBy: uniqStr("constructor"),
      name: uniqStr("Foo"),
      location: uniqStr("B (http://foo/bar:10)")
    }, {
      keyedBy: uniqStr("primitive"),
      location: uniqStr("self-hosted")
    }]
  }],
  attempts: {
    schema: {
      outcome: 0,
      strategy: 1
    },
    data: [
      [uniqStr("Failure1"), uniqStr("SomeGetter1")],
      [uniqStr("Failure2"), uniqStr("SomeGetter2")],
      [uniqStr("Inlined"), uniqStr("SomeGetter3")]
    ]
  }
};

function serialize(x) {
  return JSON.parse(JSON.stringify(x));
}

gThread.frameTable.data.forEach((frame) => {
  const LOCATION_SLOT = gThread.frameTable.schema.location;
  const OPTIMIZATIONS_SLOT = gThread.frameTable.schema.optimizations;
  const IMPLEMENTATION_SLOT = gThread.frameTable.schema.implementation;

  let l = gThread.stringTable[frame[LOCATION_SLOT]];
  switch (l) {
  // Rename some of the location sites so we can register different
  // frames with different opt sites
    case "X_0":
      frame[LOCATION_SLOT] = uniqStr("X");
      frame[OPTIMIZATIONS_SLOT] = serialize(gRawSite1);
      frame[IMPLEMENTATION_SLOT] = null;
      break;
    case "X_1":
      frame[LOCATION_SLOT] = uniqStr("X");
      frame[OPTIMIZATIONS_SLOT] = serialize(gRawSite1);
      frame[IMPLEMENTATION_SLOT] = uniqStr("baseline");
      break;
    case "X_2":
      frame[LOCATION_SLOT] = uniqStr("X");
      frame[OPTIMIZATIONS_SLOT] = serialize(gRawSite1);
      frame[IMPLEMENTATION_SLOT] = uniqStr("ion");
      break;
  }
});
