/* 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);
  },
};

let 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));
  },

  getByName: function(name, context, callback) {
    return this._callFunction("getByName",
                              [ name, contextArg(context) ],
                              callback);
  },

  getByDomainAndName: function(domain, name, context, callback) {
    return this._callFunction("getByDomainAndName",
                              [ domain, name, contextArg(context) ],
                              callback);
  },

  getBySubdomainAndName: function(domain, name, context, callback) {
    return this._callFunction("getBySubdomainAndName",
                              [ domain, name, contextArg(context) ],
                              callback);
  },

  getGlobal: function(name, context, callback) {
    return this._callFunction("getGlobal",
                              [ name, contextArg(context) ],
                              callback);
  },

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

  set: function(domain, name, value, context, callback) {
    this._callFunction("set",
                       [ domain, name, value, contextArg(context) ],
                       callback);
  },

  setGlobal: function(name, value, context, callback) {
    this._callFunction("setGlobal",
                       [ name, value, contextArg(context) ],
                       callback);
  },

  removeByDomainAndName: function(domain, name, context, callback) {
    this._callFunction("removeByDomainAndName",
                       [ domain, name, contextArg(context) ],
                       callback);
  },

  removeBySubdomainAndName: function(domain, name, context, callback) {
    this._callFunction("removeBySubdomainAndName",
                       [ domain, name, contextArg(context) ],
                       callback);
  },

  removeGlobal: function(name, context, callback) {
    this._callFunction("removeGlobal", [ name, contextArg(context) ], callback);
  },

  removeByDomain: function(domain, context, callback) {
    this._callFunction("removeByDomain", [ domain, contextArg(context) ],
                       callback);
  },

  removeBySubdomain: function(domain, context, callback) {
    this._callFunction("removeBySubdomain", [ domain, contextArg(context) ],
                       callback);
  },

  removeByName: function(name, context, callback) {
    this._callFunction("removeByName", [ name, value, contextArg(context) ],
                       callback);
  },

  removeAllDomains: function(context, callback) {
    this._callFunction("removeAllDomains", [ contextArg(context) ], callback);
  },

  removeAllGlobals: function(context, callback) {
    this._callFunction("removeAllGlobals", [ contextArg(context) ],
                       callback);
  },

  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
};

ContentPrefServiceChild.init();
