/* 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";

const { Front, FrontClassWithSpec } = require("devtools/shared/protocol");
const { performanceRecordingSpec } = require("devtools/shared/specs/performance-recording");

loader.lazyRequireGetter(this, "PerformanceIO",
  "devtools/client/performance/modules/io");
loader.lazyRequireGetter(this, "PerformanceRecordingCommon",
  "devtools/shared/performance/recording-common", true);
loader.lazyRequireGetter(this, "RecordingUtils",
  "devtools/shared/performance/recording-utils");
loader.lazyRequireGetter(this, "merge", "sdk/util/object", true);

/**
 * This can be used on older Profiler implementations, but the methods cannot
 * be changed -- you must introduce a new method, and detect the server.
 */
const PerformanceRecordingFront = FrontClassWithSpec(performanceRecordingSpec, merge({
  form: function (form, detail) {
    if (detail === "actorid") {
      this.actorID = form;
      return;
    }
    this.actorID = form.actor;
    this._form = form;
    this._configuration = form.configuration;
    this._startingBufferStatus = form.startingBufferStatus;
    this._console = form.console;
    this._label = form.label;
    this._startTime = form.startTime;
    this._localStartTime = form.localStartTime;
    this._recording = form.recording;
    this._completed = form.completed;
    this._duration = form.duration;

    if (form.finalizedData) {
      this._profile = form.profile;
      this._systemHost = form.systemHost;
      this._systemClient = form.systemClient;
    }

    // Sort again on the client side if we're using realtime markers and the recording
    // just finished. This is because GC/Compositing markers can come into the array out
    // of order with the other markers, leading to strange collapsing in waterfall view.
    if (this._completed && !this._markersSorted) {
      this._markers = this._markers.sort((a, b) => (a.start > b.start));
      this._markersSorted = true;
    }
  },

  initialize: function (client, form, config) {
    Front.prototype.initialize.call(this, client, form);
    this._markers = [];
    this._frames = [];
    this._memory = [];
    this._ticks = [];
    this._allocations = { sites: [], timestamps: [], frames: [], sizes: [] };
  },

  destroy: function () {
    Front.prototype.destroy.call(this);
  },

  /**
   * Saves the current recording to a file.
   *
   * @param nsILocalFile file
   *        The file to stream the data into.
   */
  exportRecording: function (file) {
    let recordingData = this.getAllData();
    return PerformanceIO.saveRecordingToFile(recordingData, file);
  },

  /**
   * Fired whenever the PerformanceFront emits markers, memory or ticks.
   */
  _addTimelineData: function (eventName, data) {
    let config = this.getConfiguration();

    switch (eventName) {
      // Accumulate timeline markers into an array. Furthermore, the timestamps
      // do not have a zero epoch, so offset all of them by the start time.
      case "markers": {
        if (!config.withMarkers) {
          break;
        }
        let { markers } = data;
        RecordingUtils.offsetMarkerTimes(markers, this._startTime);
        RecordingUtils.pushAll(this._markers, markers);
        break;
      }
      // Accumulate stack frames into an array.
      case "frames": {
        if (!config.withMarkers) {
          break;
        }
        let { frames } = data;
        RecordingUtils.pushAll(this._frames, frames);
        break;
      }
      // Accumulate memory measurements into an array. Furthermore, the timestamp
      // does not have a zero epoch, so offset it by the actor's start time.
      case "memory": {
        if (!config.withMemory) {
          break;
        }
        let { delta, measurement } = data;
        this._memory.push({
          delta: delta - this._startTime,
          value: measurement.total / 1024 / 1024
        });
        break;
      }
      // Save the accumulated refresh driver ticks.
      case "ticks": {
        if (!config.withTicks) {
          break;
        }
        let { timestamps } = data;
        this._ticks = timestamps;
        break;
      }
      // Accumulate allocation sites into an array.
      case "allocations": {
        if (!config.withAllocations) {
          break;
        }
        let {
          allocations: sites,
          allocationsTimestamps: timestamps,
          allocationSizes: sizes,
          frames,
        } = data;

        RecordingUtils.offsetAndScaleTimestamps(timestamps, this._startTime);
        RecordingUtils.pushAll(this._allocations.sites, sites);
        RecordingUtils.pushAll(this._allocations.timestamps, timestamps);
        RecordingUtils.pushAll(this._allocations.frames, frames);
        RecordingUtils.pushAll(this._allocations.sizes, sizes);
        break;
      }
    }
  },

  toString: () => "[object PerformanceRecordingFront]"
}, PerformanceRecordingCommon));

exports.PerformanceRecordingFront = PerformanceRecordingFront;
