/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* 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/. */

"use strict";

this.EXPORTED_SYMBOLS = [ "ContentPrefServiceChild" ];

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

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/ContentPrefUtils.jsm");
Cu.import("resource://gre/modules/ContentPrefStore.jsm");

// We only need one bit of information out of the context.
function contextArg(context) {
  return (context && context.usePrivateBrowsing) ?
            { usePrivateBrowsing: true } :
            null;
}

function NYI() {
  throw new Error("Do not add any new users of these functions");
}

function CallbackCaller(callback) {
  this._callback = callback;
}

CallbackCaller.prototype = {
  handleResult: function(contentPref) {
    cbHandleResult(this._callback,
                   new ContentPref(contentPref.domain,
                                   contentPref.name,
                                   contentPref.value));
  },

  handleError: function(result) {
    cbHandleError(this._callback, result);
  },

  handleCompletion: function(reason) {
    cbHandleCompletion(this._callback, reason);
  },
};

var ContentPrefServiceChild = {
  QueryInterface: XPCOMUtils.generateQI([ Ci.nsIContentPrefService2 ]),

  // Map from pref name -> set of observers
  _observers: new Map(),

  _mm: Cc["@mozilla.org/childprocessmessagemanager;1"]
         .getService(Ci.nsIMessageSender),

  _getRandomId: function() {
    return Cc["@mozilla.org/uuid-generator;1"]
             .getService(Ci.nsIUUIDGenerator).generateUUID().toString();
  },

  // Map from random ID string -> CallbackCaller, per request
  _requests: new Map(),

  init: function() {
    this._mm.addMessageListener("ContentPrefs:HandleResult", this);
    this._mm.addMessageListener("ContentPrefs:HandleError", this);
    this._mm.addMessageListener("ContentPrefs:HandleCompletion", this);
  },

  receiveMessage: function(msg) {
    let data = msg.data;
    let callback;
    switch (msg.name) {
      case "ContentPrefs:HandleResult":
        callback = this._requests.get(data.requestId);
        callback.handleResult(data.contentPref);
        break;

      case "ContentPrefs:HandleError":
        callback = this._requests.get(data.requestId);
        callback.handleError(data.error);
        break;

      case "ContentPrefs:HandleCompletion":
        callback = this._requests.get(data.requestId);
        this._requests.delete(data.requestId);
        callback.handleCompletion(data.reason);
        break;

      case "ContentPrefs:NotifyObservers": {
        let observerList = this._observers.get(data.name);
        if (!observerList)
          break;

        for (let observer of observerList) {
          safeCallback(observer, data.callback, data.args);
        }

        break;
      }
    }
  },

  _callFunction: function(call, args, callback) {
    let requestId = this._getRandomId();
    let data = { call: call, args: args, requestId: requestId };

    this._mm.sendAsyncMessage("ContentPrefs:FunctionCall", data);

    this._requests.set(requestId, new CallbackCaller(callback));
  },

  getCachedByDomainAndName: NYI,
  getCachedBySubdomainAndName: NYI,
  getCachedGlobal: NYI,

  addObserverForName: function(name, observer) {
    let set = this._observers.get(name);
    if (!set) {
      set = new Set();
      if (this._observers.size === 0) {
        // This is the first observer of any kind. Start listening for changes.
        this._mm.addMessageListener("ContentPrefs:NotifyObservers", this);
      }

      // This is the first observer for this name. Start listening for changes
      // to it.
      this._mm.sendAsyncMessage("ContentPrefs:AddObserverForName", { name: name });
      this._observers.set(name, set);
    }

    set.add(observer);
  },

  removeObserverForName: function(name, observer) {
    let set = this._observers.get(name);
    if (!set)
      return;

    set.delete(observer);
    if (set.size === 0) {
      // This was the last observer for this name. Stop listening for changes.
      this._mm.sendAsyncMessage("ContentPrefs:RemoveObserverForName", { name: name });

      this._observers.delete(name);
      if (this._observers.size === 0) {
        // This was the last observer for this process. Stop listing for all
        // changes.
        this._mm.removeMessageListener("ContentPrefs:NotifyObservers", this);
      }
    }
  },

  extractDomain: NYI
};

function forwardMethodToParent(method, signature, ...args) {
  // Ignore superfluous arguments
  args = args.slice(0, signature.length);

  // Process context argument for forwarding
  let contextIndex = signature.indexOf("context");
  if (contextIndex > -1) {
    args[contextIndex] = contextArg(args[contextIndex]);
  }
  // Take out the callback argument, if present.
  let callbackIndex = signature.indexOf("callback");
  let callback = null;
  if (callbackIndex > -1 && args.length > callbackIndex) {
    callback = args.splice(callbackIndex, 1)[0];
  }
  this._callFunction(method, args, callback);
}

for (let [method, signature] of _methodsCallableFromChild) {
  ContentPrefServiceChild[method] = forwardMethodToParent.bind(ContentPrefServiceChild, method, signature);
}

ContentPrefServiceChild.init();