text: /* 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";
  
  let DEBUG = 0;
  if (DEBUG)
    debug = function (s) { dump("-*- Fallback ContactService component: " + s + "\n"); }
  else
    debug = function (s) {}
  
  const Cu = Components.utils; 
  const Cc = Components.classes;
  const Ci = Components.interfaces;
  
  let EXPORTED_SYMBOLS = ["DOMContactManager"];
  
  Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  Cu.import("resource://gre/modules/Services.jsm");
  Cu.import("resource://gre/modules/ContactDB.jsm");
  
  XPCOMUtils.defineLazyGetter(this, "ppmm", function() {
    return Cc["@mozilla.org/parentprocessmessagemanager;1"].getService(Ci.nsIFrameMessageManager);
  });
  
  let myGlobal = this;
  
  let DOMContactManager = {
    init: function() {
      debug("Init");
      this._messages = ["Contacts:Find", "Contacts:Clear", "Contact:Save", "Contact:Remove"];
      this._messages.forEach((function(msgName) {
        ppmm.addMessageListener(msgName, this);
      }).bind(this));
  
      var idbManager = Components.classes["@mozilla.org/dom/indexeddb/manager;1"].getService(Ci.nsIIndexedDatabaseManager);
      idbManager.initWindowless(myGlobal);
      this._db = new ContactDB(myGlobal);
      this._db.init(myGlobal);
  
      Services.obs.addObserver(this, "profile-before-change", false);
  
      try {
        let hosts = Services.prefs.getCharPref("dom.mozContacts.whitelist")
        hosts.split(",").forEach(function(aHost) {
          debug("Add host: " + JSON.stringify(aHost));
          if (aHost.length > 0)
            Services.perms.add(Services.io.newURI(aHost, null, null), "webcontacts-manage",
                               Ci.nsIPermissionManager.ALLOW_ACTION);
        });
      } catch(e) { debug(e); }
    },
  
    observe: function(aSubject, aTopic, aData) {
      myGlobal = null;
      this._messages.forEach((function(msgName) {
        ppmm.removeMessageListener(msgName, this);
      }).bind(this));
      Services.obs.removeObserver(this, "profile-before-change");
      ppmm = null;
      this._messages = null;
      if (this._db)
        this._db.close();
      this._db = null;
    },
  
    receiveMessage: function(aMessage) {
      debug("Fallback DOMContactManager::receiveMessage " + aMessage.name);
      let msg = aMessage.json;
  
      /*
       * Sorting the contacts by sortBy field. sortBy can either be familyName or givenName.
       * If 2 entries have the same sortyBy field or no sortBy field is present, we continue 
       * sorting with the other sortyBy field.
       */
      function sortfunction(a, b){
        let x, y;
        let sortByNameSet = true;
        let result = 0;
        let sortBy = msg.findOptions.sortBy;
        let sortOrder = msg.findOptions.sortOrder;
        if (!a.properties[sortBy] || !(x = a.properties[sortBy][0].toLowerCase())) {
          sortByNameSet = false;
        }
  
        if (!b.properties[sortBy] || !(y = b.properties[sortBy][0].toLowerCase())) {
          if (sortByNameSet) {
            return sortOrder == 'ascending' ? 1 : -1;
          }
        }
  
        if (sortByNameSet) {
          result = x.localeCompare(y);
        }
  
        if (result == 0) {
          // If 2 entries have the same sortBy (familyName or givenName) field,
          // we have to continue sorting.
          let otherSortBy = sortBy == "familyName" ? "givenName" : "familyName";
          if (!a.properties[otherSortBy] || !(x = a.properties[otherSortBy][0].toLowerCase())) {
            return sortOrder == 'ascending' ? 1 : -1;
          }
          if (!b.properties[otherSortBy] || !(y = b.properties[otherSortBy][0].toLowerCase())) {
            return sortOrder == 'ascending' ? 1 : -1;
          }
          result = x.localeCompare(y);
        }
        return sortOrder == 'ascending' ? result : -result;
      }
  
      switch (aMessage.name) {
        case "Contacts:Find":
          let result = new Array();
          this._db.find(
            function(contacts) {
              for (let i in contacts)
                result.push(contacts[i]);
              if (msg.findOptions.sortOrder !== 'undefined' && msg.findOptions.sortBy !== 'undefined') {
                debug('sortBy: ' + msg.findOptions.sortBy + ', sortOrder: ' + msg.findOptions.sortOrder );
                result.sort(sortfunction);
                if (msg.findOptions.filterLimit)
                  result = result.slice(0, msg.findOptions.filterLimit);
              }
  
              debug("result:" + JSON.stringify(result));
              ppmm.sendAsyncMessage("Contacts:Find:Return:OK", {requestID: msg.requestID, contacts: result});
            }.bind(this),
            function(aErrorMsg) { ppmm.sendAsyncMessage("Contacts:Find:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }) }.bind(this),
            msg.findOptions);
          break;
        case "Contact:Save":
          this._db.saveContact(
            msg.contact, 
            function() { ppmm.sendAsyncMessage("Contact:Save:Return:OK", { requestID: msg.requestID, contactID: msg.contact.id }); }.bind(this),
            function(aErrorMsg) { ppmm.sendAsyncMessage("Contact:Save:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this)
          );
          break;
        case "Contact:Remove":
          this._db.removeContact(
            msg.id, 
            function() { ppmm.sendAsyncMessage("Contact:Remove:Return:OK", { requestID: msg.requestID, contactID: msg.id }); }.bind(this),
            function(aErrorMsg) { ppmm.sendAsyncMessage("Contact:Remove:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this)
          );
          break;
        case "Contacts:Clear":
          this._db.clear(
            function() { ppmm.sendAsyncMessage("Contacts:Clear:Return:OK", { requestID: msg.requestID }); }.bind(this),
            function(aErrorMsg) { ppmm.sendAsyncMessage("Contacts:Clear:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this)
          );
      }
    }
  }
  
  DOMContactManager.init();
  