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

const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "console", () => {
  return console.createInstance({
    prefix: "NotificationStorage",
    maxLogLevelPref: "dom.webnotifications.loglevel",
  });
});

const kMessageGetAllOk = "GetAll:Return:OK";
const kMessageGetAllKo = "GetAll:Return:KO";
const kMessageSaveKo = "Save:Return:KO";
const kMessageDeleteKo = "Delete:Return:KO";

export class NotificationStorage {
  #requests = {};
  #requestCount = 0;

  constructor() {
    Services.obs.addObserver(this, "xpcom-shutdown");

    // Register for message listeners.
    this.registerListeners();
  }

  storageQualifier() {
    return "Notification";
  }

  prefixStorageQualifier(message) {
    return `${this.storageQualifier()}:${message}`;
  }

  formatMessageType(message) {
    return this.prefixStorageQualifier(message);
  }

  supportedMessages() {
    return [
      this.formatMessageType(kMessageGetAllOk),
      this.formatMessageType(kMessageGetAllKo),
      this.formatMessageType(kMessageSaveKo),
      this.formatMessageType(kMessageDeleteKo),
    ];
  }

  registerListeners() {
    for (let message of this.supportedMessages()) {
      Services.cpmm.addMessageListener(message, this);
    }
  }

  unregisterListeners() {
    for (let message of this.supportedMessages()) {
      Services.cpmm.removeMessageListener(message, this);
    }
  }

  observe(aSubject, aTopic) {
    lazy.console.debug(`Topic: ${aTopic}`);
    if (aTopic === "xpcom-shutdown") {
      Services.obs.removeObserver(this, "xpcom-shutdown");
      this.unregisterListeners();
    }
  }

  put(
    origin,
    id,
    title,
    dir,
    lang,
    body,
    tag,
    icon,
    alertName,
    data,
    serviceWorkerRegistrationScope
  ) {
    lazy.console.debug(`PUT: ${origin} ${id}: ${title}`);
    var notification = {
      id,
      title,
      dir,
      lang,
      body,
      tag,
      icon,
      alertName,
      timestamp: new Date().getTime(),
      origin,
      data,
      serviceWorkerRegistrationScope,
    };

    Services.cpmm.sendAsyncMessage(this.formatMessageType("Save"), {
      origin,
      notification,
    });
  }

  get(origin, tag, callback) {
    lazy.console.debug(`GET: ${origin} ${tag}`);
    this.#fetchFromDB(origin, tag, callback);
  }

  delete(origin, id) {
    lazy.console.debug(`DELETE: ${id}`);
    Services.cpmm.sendAsyncMessage(this.formatMessageType("Delete"), {
      origin,
      id,
    });
  }

  receiveMessage(message) {
    var request = this.#requests[message.data.requestID];

    switch (message.name) {
      case this.formatMessageType(kMessageGetAllOk):
        delete this.#requests[message.data.requestID];
        this.#returnNotifications(message.data.notifications, request.callback);
        break;

      case this.formatMessageType(kMessageGetAllKo):
        delete this.#requests[message.data.requestID];
        try {
          request.callback.done();
        } catch (e) {
          lazy.console.debug(`Error calling callback done: ${e}`);
        }
        break;
      case this.formatMessageType(kMessageSaveKo):
      case this.formatMessageType(kMessageDeleteKo):
        lazy.console.debug(
          `Error received when treating: '${message.name}': ${message.data.errorMsg}`
        );
        break;

      default:
        lazy.console.debug(`Unrecognized message: ${message.name}`);
        break;
    }
  }

  #getUniqueRequestID() {
    // This assumes the count will never go above MAX_SAFE_INTEGER, as
    // notifications are not supposed to happen that frequently.
    this.#requestCount += 1;
    return this.#requestCount;
  }

  #fetchFromDB(origin, tag, callback) {
    var request = {
      origin,
      tag,
      callback,
    };
    var requestID = this.#getUniqueRequestID();
    this.#requests[requestID] = request;
    Services.cpmm.sendAsyncMessage(this.formatMessageType("GetAll"), {
      origin,
      tag,
      requestID,
    });
  }

  #returnNotifications(notifications, callback) {
    // Pass each notification back separately.
    // The callback is called asynchronously to match the behaviour when
    // fetching from the database.
    notifications.forEach(function (notification) {
      try {
        Services.tm.dispatchToMainThread(
          callback.handle.bind(
            callback,
            notification.id,
            notification.title,
            notification.dir,
            notification.lang,
            notification.body,
            notification.tag,
            notification.icon,
            notification.data,
            notification.serviceWorkerRegistrationScope
          )
        );
      } catch (e) {
        lazy.console.debug(`Error calling callback handle: ${e}`);
      }
    });
    try {
      Services.tm.dispatchToMainThread(callback.done);
    } catch (e) {
      lazy.console.debug(`Error calling callback done: ${e}`);
    }
  }

  QueryInterface = ChromeUtils.generateQI(["nsINotificationStorage"]);
}

export class MemoryNotificationStorage extends NotificationStorage {
  storageQualifier() {
    return "MemoryNotification";
  }
}
