/* 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/. */

/*
 * This file provides common and shared functionality to facilitate
 * testing of the Crashes component (CrashManager.jsm).
 */

"use strict";

const {classes: Cc, interfaces: Ci, utils: Cu} = Components;

this.EXPORTED_SYMBOLS = [
  "configureLogging",
  "getManager",
  "sleep",
  "TestingCrashManager",
];

Cu.import("resource://gre/modules/CrashManager.jsm", this);
Cu.import("resource://gre/modules/Log.jsm", this);
Cu.import("resource://gre/modules/osfile.jsm", this);
Cu.import("resource://gre/modules/Promise.jsm", this);
Cu.import("resource://gre/modules/Task.jsm", this);
Cu.import("resource://gre/modules/Timer.jsm", this);

var loggingConfigured = false;

this.configureLogging = function () {
  if (loggingConfigured) {
    return;
  }

  let log = Log.repository.getLogger("Crashes.CrashManager");
  log.level = Log.Level.All;
  let appender = new Log.DumpAppender();
  appender.level = Log.Level.All;
  log.addAppender(appender);
  loggingConfigured = true;
};

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

  setTimeout(() => {
    deferred.resolve();
  }, wait);

  return deferred.promise;
};

this.TestingCrashManager = function (options) {
  CrashManager.call(this, options);
}

this.TestingCrashManager.prototype = {
  __proto__: CrashManager.prototype,

  createDummyDump: function (submitted=false, date=new Date(), hr=false) {
    let uuid = Cc["@mozilla.org/uuid-generator;1"]
                .getService(Ci.nsIUUIDGenerator)
                .generateUUID()
                .toString();
    uuid = uuid.substring(1, uuid.length - 1);

    let path;
    let mode;
    if (submitted) {
      if (hr) {
        path = OS.Path.join(this._submittedDumpsDir, "bp-hr-" + uuid + ".txt");
      } else {
        path = OS.Path.join(this._submittedDumpsDir, "bp-" + uuid + ".txt");
      }
      mode = OS.Constants.libc.S_IRUSR | OS.Constants.libc.S_IWUSR |
            OS.Constants.libc.S_IRGRP | OS.Constants.libc.S_IROTH;
    } else {
      path = OS.Path.join(this._pendingDumpsDir, uuid + ".dmp");
      mode = OS.Constants.libc.S_IRUSR | OS.Constants.libc.S_IWUSR;
    }

    return Task.spawn(function* () {
      let f = yield OS.File.open(path, {create: true}, {unixMode: mode});
      yield f.setDates(date, date);
      yield f.close();
      dump("Created fake crash: " + path + "\n");

      return uuid;
    });
  },

  createIgnoredDumpFile: function (filename, submitted=false) {
    let path;
    if (submitted) {
      path = OS.Path.join(this._submittedDumpsDir, filename);
    } else {
      path = OS.Path.join(this._pendingDumpsDir, filename);
    }

    return Task.spawn(function* () {
      let mode = OS.Constants.libc.S_IRUSR | OS.Constants.libc.S_IWUSR;
      yield OS.File.open(path, {create: true}, {unixMode: mode});
      dump ("Create ignored dump file: " + path + "\n");
    });
  },

  createEventsFile: function (filename, type, date, content, index=0) {
    let path = OS.Path.join(this._eventsDirs[index], filename);

    let data = type + "\n" +
               Math.floor(date.getTime() / 1000) + "\n" +
               content;
    let encoder = new TextEncoder();
    let array = encoder.encode(data);

    return Task.spawn(function* () {
      yield OS.File.writeAtomic(path, array);
      yield OS.File.setDates(path, date, date);
    });
  },

  /**
   * Overwrite event file handling to process our test file type.
   *
   * We can probably delete this once we have actual events defined.
   */
  _handleEventFilePayload: function (store, entry, type, date, payload) {
    if (type == "test.1") {
      if (payload == "malformed") {
        return this.EVENT_FILE_ERROR_MALFORMED;
      } else if (payload == "success") {
        return this.EVENT_FILE_SUCCESS;
      }
      return this.EVENT_FILE_ERROR_UNKNOWN_EVENT;
    }

    return CrashManager.prototype._handleEventFilePayload.call(this,
                                                               store,
                                                               entry,
                                                               type,
                                                               date,
                                                               payload);
  },
};

var DUMMY_DIR_COUNT = 0;

this.getManager = function () {
  return Task.spawn(function* () {
    const dirMode = OS.Constants.libc.S_IRWXU;
    let baseFile = OS.Constants.Path.profileDir;

    function makeDir(create=true) {
      return Task.spawn(function* () {
        let path = OS.Path.join(baseFile, "dummy-dir-" + DUMMY_DIR_COUNT++);

        if (!create) {
          return path;
        }

        dump("Creating directory: " + path + "\n");
        yield OS.File.makeDir(path, {unixMode: dirMode});

        return path;
      });
    }

    let pendingD = yield makeDir();
    let submittedD = yield makeDir();
    let eventsD1 = yield makeDir();
    let eventsD2 = yield makeDir();

    // Store directory is created at run-time if needed. Ensure those code
    // paths are triggered.
    let storeD = yield makeDir(false);

    let m = new TestingCrashManager({
      pendingDumpsDir: pendingD,
      submittedDumpsDir: submittedD,
      eventsDirs: [eventsD1, eventsD2],
      storeDir: storeD,
    });

    return m;
  });
};