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

"use strict";

var {utils: Cu} = Components;

Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/SessionRecorder.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://services-common/utils.js");


function run_test() {
  run_next_test();
}

function monkeypatchStartupInfo(recorder, start = new Date(), offset = 500) {
  Object.defineProperty(recorder, "_getStartupInfo", {
    value: function _getStartupInfo() {
      return {
        process: start,
        main: new Date(start.getTime() + offset),
        firstPaint: new Date(start.getTime() + 2 * offset),
        sessionRestored: new Date(start.getTime() + 3 * offset),
      };
    }
  });
}

function sleep(wait) {
  let deferred = Promise.defer();

  CommonUtils.namedTimer(function onTimer() {
    deferred.resolve();
  }, wait, deferred.promise, "_sleepTimer");

  return deferred.promise;
}

function getRecorder(name, start, offset) {
  let recorder = new SessionRecorder("testing." + name + ".");
  monkeypatchStartupInfo(recorder, start, offset);

  return recorder;
}

add_test(function test_basic() {
  let recorder = getRecorder("basic");
  recorder.onStartup();
  recorder.onShutdown();

  run_next_test();
});

add_task(function* test_current_properties() {
  let now = new Date();
  let recorder = getRecorder("current_properties", now);
  yield sleep(25);
  recorder.onStartup();

  do_check_eq(recorder.startDate.getTime(), now.getTime());
  do_check_eq(recorder.activeTicks, 0);
  do_check_true(recorder.fineTotalTime > 0);
  do_check_eq(recorder.main, 500);
  do_check_eq(recorder.firstPaint, 1000);
  do_check_eq(recorder.sessionRestored, 1500);

  recorder.incrementActiveTicks();
  do_check_eq(recorder.activeTicks, 1);

  recorder._startDate = new Date(Date.now() - 1000);
  recorder.updateTotalTime();
  do_check_eq(recorder.totalTime, 1);

  recorder.onShutdown();
});

// If startup info isn't present yet, we should install a timer and get
// it eventually.
add_task(function* test_current_availability() {
  let recorder = new SessionRecorder("testing.current_availability.");
  let now = new Date();

  Object.defineProperty(recorder, "_getStartupInfo", {
    value: function _getStartupInfo() {
      return {
        process: now,
        main: new Date(now.getTime() + 500),
        firstPaint: new Date(now.getTime() + 1000),
      };
    },
    writable: true,
  });

  Object.defineProperty(recorder, "STARTUP_RETRY_INTERVAL_MS", {
    value: 100,
  });

  let oldRecord = recorder.recordStartupFields;
  let recordCount = 0;

  Object.defineProperty(recorder, "recordStartupFields", {
    value() {
      recordCount++;
      return oldRecord.call(recorder);
    }
  });

  do_check_null(recorder._timer);
  recorder.onStartup();
  do_check_eq(recordCount, 1);
  do_check_eq(recorder.sessionRestored, -1);
  do_check_neq(recorder._timer, null);

  yield sleep(125);
  do_check_eq(recordCount, 2);
  yield sleep(100);
  do_check_eq(recordCount, 3);
  do_check_eq(recorder.sessionRestored, -1);

  monkeypatchStartupInfo(recorder, now);
  yield sleep(100);
  do_check_eq(recordCount, 4);
  do_check_eq(recorder.sessionRestored, 1500);

  // The timer should be removed and we should not fire again.
  do_check_null(recorder._timer);
  yield sleep(100);
  do_check_eq(recordCount, 4);

  recorder.onShutdown();
});

add_test(function test_timer_clear_on_shutdown() {
  let recorder = new SessionRecorder("testing.timer_clear_on_shutdown.");
  let now = new Date();

  Object.defineProperty(recorder, "_getStartupInfo", {
    value: function _getStartupInfo() {
      return {
        process: now,
        main: new Date(now.getTime() + 500),
        firstPaint: new Date(now.getTime() + 1000),
      };
    },
  });

  do_check_null(recorder._timer);
  recorder.onStartup();
  do_check_neq(recorder._timer, null);

  recorder.onShutdown();
  do_check_null(recorder._timer);

  run_next_test();
});

add_task(function* test_previous_clean() {
  let now = new Date();
  let recorder = getRecorder("previous_clean", now);
  yield sleep(25);
  recorder.onStartup();

  recorder.incrementActiveTicks();
  recorder.incrementActiveTicks();

  yield sleep(25);
  recorder.onShutdown();

  let total = recorder.totalTime;

  yield sleep(25);
  let now2 = new Date();
  let recorder2 = getRecorder("previous_clean", now2, 100);
  yield sleep(25);
  recorder2.onStartup();

  do_check_eq(recorder2.startDate.getTime(), now2.getTime());
  do_check_eq(recorder2.main, 100);
  do_check_eq(recorder2.firstPaint, 200);
  do_check_eq(recorder2.sessionRestored, 300);

  let sessions = recorder2.getPreviousSessions();
  do_check_eq(Object.keys(sessions).length, 1);
  do_check_true(0 in sessions);
  let session = sessions[0];
  do_check_true(session.clean);
  do_check_eq(session.startDate.getTime(), now.getTime());
  do_check_eq(session.main, 500);
  do_check_eq(session.firstPaint, 1000);
  do_check_eq(session.sessionRestored, 1500);
  do_check_eq(session.totalTime, total);
  do_check_eq(session.activeTicks, 2);

  recorder2.onShutdown();
});

add_task(function* test_previous_abort() {
  let now = new Date();
  let recorder = getRecorder("previous_abort", now);
  yield sleep(25);
  recorder.onStartup();
  recorder.incrementActiveTicks();
  yield sleep(25);
  let total = recorder.totalTime;
  yield sleep(25);

  let now2 = new Date();
  let recorder2 = getRecorder("previous_abort", now2);
  yield sleep(25);
  recorder2.onStartup();

  let sessions = recorder2.getPreviousSessions();
  do_check_eq(Object.keys(sessions).length, 1);
  do_check_true(0 in sessions);
  let session = sessions[0];
  do_check_false(session.clean);
  do_check_eq(session.totalTime, total);

  recorder.onShutdown();
  recorder2.onShutdown();
});

add_task(function* test_multiple_sessions() {
  for (let i = 0; i < 10; i++) {
    let recorder = getRecorder("multiple_sessions");
    yield sleep(25);
    recorder.onStartup();
    for (let j = 0; j < i; j++) {
      recorder.incrementActiveTicks();
    }
    yield sleep(25);
    recorder.onShutdown();
    yield sleep(25);
  }

  let recorder = getRecorder("multiple_sessions");
  recorder.onStartup();

  let sessions = recorder.getPreviousSessions();
  do_check_eq(Object.keys(sessions).length, 10);

  for (let [i, session] of Object.entries(sessions)) {
    do_check_eq(session.activeTicks, i);

    if (i > 0) {
      do_check_true(session.startDate.getTime() > sessions[i - 1].startDate.getTime());
    }
  }

  // #6 is preserved since >=.
  let threshold = sessions[6].startDate;
  recorder.pruneOldSessions(threshold);

  sessions = recorder.getPreviousSessions();
  do_check_eq(Object.keys(sessions).length, 4);

  recorder.pruneOldSessions(threshold);
  sessions = recorder.getPreviousSessions();
  do_check_eq(Object.keys(sessions).length, 4);
  do_check_eq(recorder._prunedIndex, 5);

  recorder.onShutdown();
});

add_task(function* test_record_activity() {
  let recorder = getRecorder("record_activity");
  yield sleep(25);
  recorder.onStartup();
  let total = recorder.totalTime;
  yield sleep(25);

  for (let i = 0; i < 3; i++) {
    Services.obs.notifyObservers(null, "user-interaction-active", null);
    yield sleep(25);
    do_check_true(recorder.fineTotalTime > total);
    total = recorder.fineTotalTime;
  }

  do_check_eq(recorder.activeTicks, 3);

  // Now send inactive. We should increment total time but not active.
  Services.obs.notifyObservers(null, "user-interaction-inactive", null);
  do_check_eq(recorder.activeTicks, 3);
  do_check_true(recorder.fineTotalTime > total);
  total = recorder.fineTotalTime;
  yield sleep(25);

  // If we send active again, this should be counted as inactive.
  Services.obs.notifyObservers(null, "user-interaction-active", null);
  do_check_eq(recorder.activeTicks, 3);
  do_check_true(recorder.fineTotalTime > total);
  total = recorder.fineTotalTime;
  yield sleep(25);

  // If we send active again, this should be counted as active.
  Services.obs.notifyObservers(null, "user-interaction-active", null);
  do_check_eq(recorder.activeTicks, 4);

  Services.obs.notifyObservers(null, "user-interaction-active", null);
  do_check_eq(recorder.activeTicks, 5);

  recorder.onShutdown();
});
