/* 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.EXPORTED_SYMBOLS = ["Notifications", "Notification", "NotificationButton"];

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;

Cu.import("resource://services-common/observers.js");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-sync/util.js");

this.Notifications = {
  // Match the referenced values in toolkit/content/widgets/notification.xml.
  get PRIORITY_INFO()     1, // PRIORITY_INFO_LOW
  get PRIORITY_WARNING()  4, // PRIORITY_WARNING_LOW
  get PRIORITY_ERROR()    7, // PRIORITY_CRITICAL_LOW

  // FIXME: instead of making this public, dress the Notifications object
  // to behave like an iterator (using generators?) and have callers access
  // this array through the Notifications object.
  notifications: [],

  _observers: [],

  // XXX Should we have a helper method for adding a simple notification?
  // I.e. something like |function notify(title, description, priority)|.

  add: function Notifications_add(notification) {
    this.notifications.push(notification);
    Observers.notify("weave:notification:added", notification, null);
  },

  remove: function Notifications_remove(notification) {
    let index = this.notifications.indexOf(notification);

    if (index != -1) {
      this.notifications.splice(index, 1);
      Observers.notify("weave:notification:removed", notification, null);
    }
  },

  /**
   * Replace an existing notification.
   */
  replace: function Notifications_replace(oldNotification, newNotification) {
    let index = this.notifications.indexOf(oldNotification);

    if (index != -1)
      this.notifications.splice(index, 1, newNotification);
    else {
      this.notifications.push(newNotification);
      // XXX Should we throw because we didn't find the existing notification?
      // XXX Should we notify observers about weave:notification:added?
    }

    // XXX Should we notify observers about weave:notification:replaced?
  },

  /**
   * Remove all notifications that match a title. If no title is provided, all
   * notifications are removed.
   *
   * @param title [optional]
   *        Title of notifications to remove; falsy value means remove all
   */
  removeAll: function Notifications_removeAll(title) {
    this.notifications.filter(function(old) old.title == title || !title).
      forEach(function(old) this.remove(old), this);
  },

  // replaces all existing notifications with the same title as the new one
  replaceTitle: function Notifications_replaceTitle(notification) {
    this.removeAll(notification.title);
    this.add(notification);
  }
};


/**
 * A basic notification.  Subclass this to create more complex notifications.
 */
this.Notification =
 function Notification(title, description, iconURL, priority, buttons) {
  this.title = title;
  this.description = description;

  if (iconURL)
    this.iconURL = iconURL;

  if (priority)
    this.priority = priority;

  if (buttons)
    this.buttons = buttons;
}

// We set each prototype property individually instead of redefining
// the entire prototype to avoid blowing away existing properties
// of the prototype like the the "constructor" property, which we use
// to bind notification objects to their XBL representations.
Notification.prototype.priority = Notifications.PRIORITY_INFO;
Notification.prototype.iconURL = null;
Notification.prototype.buttons = [];

/**
 * A button to display in a notification.
 */
this.NotificationButton =
 function NotificationButton(label, accessKey, callback) {
  function callbackWrapper() {
    try {
      callback.apply(this, arguments);
    } catch (e) {
      let logger = Log.repository.getLogger("Sync.Notifications");
      logger.error("An exception occurred: " + Utils.exceptionStr(e));
      logger.info(Utils.stackTrace(e));
      throw e;
    }
  }

  this.label = label;
  this.accessKey = accessKey;
  this.callback = callbackWrapper;
}
