"use strict";

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

Cu.import("resource://gre/modules/ExtensionUtils.jsm");
var {
  EventManager,
  runSafe,
} = ExtensionUtils;

// WeakMap[Extension -> Set[Alarm]]
var alarmsMap = new WeakMap();

// WeakMap[Extension -> Set[callback]]
var alarmCallbacksMap = new WeakMap();

// Manages an alarm created by the extension (alarms API).
function Alarm(extension, name, alarmInfo) {
  this.extension = extension;
  this.name = name;
  this.when = alarmInfo.when;
  this.delayInMinutes = alarmInfo.delayInMinutes;
  this.periodInMinutes = alarmInfo.periodInMinutes;
  this.canceled = false;

  let delay, scheduledTime;
  if (this.when) {
    scheduledTime = this.when;
    delay = this.when - Date.now();
  } else {
    if (!this.delayInMinutes) {
      this.delayInMinutes = this.periodInMinutes;
    }
    delay = this.delayInMinutes * 60 * 1000;
    scheduledTime = Date.now() + delay;
  }

  this.scheduledTime = scheduledTime;

  let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  timer.init(this, delay, Ci.nsITimer.TYPE_ONE_SHOT);
  this.timer = timer;
}

Alarm.prototype = {
  clear() {
    this.timer.cancel();
    alarmsMap.get(this.extension).delete(this);
    this.canceled = true;
  },

  observe(subject, topic, data) {
    for (let callback of alarmCallbacksMap.get(this.extension)) {
      callback(this);
    }
    if (this.canceled) {
      return;
    }

    if (!this.periodInMinutes) {
      this.clear();
      return;
    }

    let delay = this.periodInMinutes * 60 * 1000;
    this.scheduledTime = Date.now() + delay;
    this.timer.init(this, delay, Ci.nsITimer.TYPE_ONE_SHOT);
  },

  get data() {
    return {
      name: this.name,
      scheduledTime: this.scheduledTime,
      periodInMinutes: this.periodInMinutes,
    };
  },
};

/* eslint-disable mozilla/balanced-listeners */
extensions.on("startup", (type, extension) => {
  alarmsMap.set(extension, new Set());
  alarmCallbacksMap.set(extension, new Set());
});

extensions.on("shutdown", (type, extension) => {
  for (let alarm of alarmsMap.get(extension)) {
    alarm.clear();
  }
  alarmsMap.delete(extension);
  alarmCallbacksMap.delete(extension);
});
/* eslint-enable mozilla/balanced-listeners */

extensions.registerAPI((extension, context) => {
  return {
    alarms: {
      create: function(...args) {
        let name = "", alarmInfo;
        if (args.length == 1) {
          alarmInfo = args[0];
        } else {
          [name, alarmInfo] = args;
        }

        let alarm = new Alarm(extension, name, alarmInfo);
        alarmsMap.get(extension).add(alarm);
      },

      get: function(args) {
        let name = "", callback;
        if (args.length == 1) {
          callback = args[0];
        } else {
          [name, callback] = args;
        }

        for (let alarm of alarmsMap.get(extension)) {
          if (alarm.name == name) {
            runSafe(context, callback, alarm.data);
            break;
          }
        }
      },

      getAll: function(callback) {
        let alarms = alarmsMap.get(extension);
        let result = alarms.map(alarm => alarm.data);
        runSafe(context, callback, result);
      },

      clear: function(...args) {
        let name = "", callback;
        if (args.length == 1) {
          callback = args[0];
        } else {
          [name, callback] = args;
        }

        let alarms = alarmsMap.get(extension);
        let cleared = false;
        for (let alarm of alarms) {
          if (alarm.name == name) {
            alarm.clear();
            cleared = true;
            break;
          }
        }

        if (callback) {
          runSafe(context, callback, cleared);
        }
      },

      clearAll: function(callback) {
        let alarms = alarmsMap.get(extension);
        let cleared = false;
        for (let alarm of alarms) {
          alarm.clear();
          cleared = true;
        }
        if (callback) {
          runSafe(context, callback, cleared);
        }
      },

      onAlarm: new EventManager(context, "alarms.onAlarm", fire => {
        let callback = alarm => {
          fire(alarm.data);
        };

        alarmCallbacksMap.get(extension).add(callback);
        return () => {
          alarmCallbacksMap.get(extension).delete(callback);
        };
      }).api(),
    },
  };
});
