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

/* static functions */
let DEBUG = 0;
if (DEBUG)
  debug = function (s) { dump("-*- SettingsService: " + s + "\n"); }
else
  debug = function (s) {}

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

Cu.import("resource://gre/modules/SettingsQueue.jsm");
Cu.import("resource://gre/modules/SettingsDB.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

const nsIClassInfo            = Ci.nsIClassInfo;

const SETTINGSSERVICELOCK_CONTRACTID = "@mozilla.org/settingsServiceLock;1";
const SETTINGSSERVICELOCK_CID        = Components.ID("{3ab3cbc0-8513-11e1-b0c4-0800200c9a66}");
const nsISettingsServiceLock         = Ci.nsISettingsServiceLock;

function SettingsServiceLock(aSettingsService)
{
  debug("settingsServiceLock constr!");
  this._open = true;
  this._requests = new Queue();
  this._settingsService = aSettingsService;
  this._transaction = null;
}

SettingsServiceLock.prototype = {

  process: function process() {
    debug("process!");
    let lock = this;
    lock._open = false;
    let store = lock._transaction.objectStore(SETTINGSSTORE_NAME);

    while (!lock._requests.isEmpty()) {
      let info = lock._requests.dequeue();
      debug("info:" + info.intent);
      let callback = info.callback;
      let req;
      let name = info.name;
      switch (info.intent) {
        case "set":
          let value = info.value;
          if(typeof(value) == 'object')
            debug("object name:" + name + ", val: " + JSON.stringify(value));
          req = store.put({settingName: name, settingValue: value});

          req.onsuccess = function() {
            debug("set on success");
            lock._open = true;
            if (callback)
              callback.handle(name, value);
            Services.obs.notifyObservers(lock, "mozsettings-changed", JSON.stringify({
              key: name,
              value: value
            }));
            lock._open = false;
          };

          req.onerror = function(event) { callback ? callback.handleError(event.target.errorMessage) : null; };
          break;
        case "get":
          req = store.mozGetAll(name);
          req.onsuccess = function(event) {
            debug("Request successful. Record count:" + event.target.result.length);
            debug("result: " + JSON.stringify(event.target.result));
            this._open = true;
            if (callback) {
              if (event.target.result[0]) {
                if (event.target.result.length > 1) {
                  debug("Warning: overloaded setting:" + name);
                }
                callback.handle(name, event.target.result[0].settingValue);
              } else
                callback.handle(name, null);
            } else {
              debug("no callback defined!");
            }
            this._open = false;
          }.bind(lock);
          req.onerror = function error(event) { callback ? callback.handleError(event.target.errorMessage) : null; };
          break;
      }
    }
    if (!lock._requests.isEmpty())
      throw Components.results.NS_ERROR_ABORT;
    lock._open = true;
  },

  createTransactionAndProcess: function createTransactionAndProcess() {
    if (this._settingsService._settingsDB._db) {
      var lock;
      while (lock = this._settingsService._locks.dequeue()) {
        if (!lock._transaction) {
          lock._transaction = lock._settingsService._settingsDB._db.transaction(SETTINGSSTORE_NAME, "readwrite");
        }
        lock.process();
      }
      if (!this._requests.isEmpty())
        this.process();
    }
  },

  get: function get(aName, aCallback) {
    debug("get: " + aName + ", " + aCallback);
    this._requests.enqueue({ callback: aCallback, intent:"get", name: aName });
    this.createTransactionAndProcess();
  },

  set: function set(aName, aValue, aCallback) {
    debug("set: " + aName + ": " + JSON.stringify(aValue));
    this._requests.enqueue({ callback: aCallback, intent: "set", name: aName, value: aValue});
    this.createTransactionAndProcess();
  },

  classID : SETTINGSSERVICELOCK_CID,
  QueryInterface : XPCOMUtils.generateQI([nsISettingsServiceLock]),

  classInfo : XPCOMUtils.generateCI({classID: SETTINGSSERVICELOCK_CID,
                                     contractID: SETTINGSSERVICELOCK_CONTRACTID,
                                     classDescription: "SettingsServiceLock",
                                     interfaces: [nsISettingsServiceLock],
                                     flags: nsIClassInfo.DOM_OBJECT})
};

const SETTINGSSERVICE_CID        = Components.ID("{3458e760-8513-11e1-b0c4-0800200c9a66}");

let myGlobal = this;

function SettingsService()
{
  debug("settingsService Constructor");
  this._locks = new Queue();
  var idbManager = Components.classes["@mozilla.org/dom/indexeddb/manager;1"].getService(Ci.nsIIndexedDatabaseManager);
  idbManager.initWindowless(myGlobal);
  this._settingsDB = new SettingsDB();
  this._settingsDB.init(myGlobal);
}

SettingsService.prototype = {

  nextTick: function nextTick(aCallback, thisObj) {
    if (thisObj)
      aCallback = aCallback.bind(thisObj);

    Services.tm.currentThread.dispatch(aCallback, Ci.nsIThread.DISPATCH_NORMAL);
  },

  getLock: function getLock() {
    debug("get lock!");
    var lock = new SettingsServiceLock(this);
    this._locks.enqueue(lock);
    this._settingsDB.ensureDB(
      function() { lock.createTransactionAndProcess(); },
      function() { dump("ensureDB error cb!\n"); },
      myGlobal );
    this.nextTick(function() { this._open = false; }, lock);
    return lock;
  },

  classID : SETTINGSSERVICE_CID,
  QueryInterface : XPCOMUtils.generateQI([Ci.nsISettingsService]),
}

const NSGetFactory = XPCOMUtils.generateNSGetFactory([SettingsService, SettingsServiceLock])
