//
// BEGIN FLOCK GPL
//
// Copyright Flock Inc. 2005-2008
// http://flock.com
//
// This file may be used under the terms of the
// GNU General Public License Version 2 or later (the "GPL"),
// http://www.gnu.org/licenses/gpl.html
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
// for the specific language governing rights and limitations under the
// License.
//
// END FLOCK GPL
//

const CC = Components.classes;
const CI = Components.interfaces;
const CR = Components.results;

Components.utils.import("resource:///modules/FlockXPCOMUtils.jsm");
FlockXPCOMUtils.debug = false;

const MODULE_NAME = "Flock Logging";

// The Logging Service.
const FLOCK_LOGGING_SERVICE_CLASS_NAME = "Flock Logging Service";
const FLOCK_LOGGING_SERVICE_CLASS_ID =
        Components.ID("{E8C889A6-F832-4B09-A216-66A7E6178896}");
const FLOCK_LOGGING_SERVICE_CONTRACT_ID = "@flock.com/logging-service;1";

// The Logger; uses the Logging Service.
const FLOCK_LOGGER_CLASS_NAME = "Flock Logger";
const FLOCK_LOGGER_CLASS_ID =
        Components.ID("{05428cec-9fd4-4955-afc9-eaaf4c6d9f9d}");
const FLOCK_LOGGER_CONTRACT_ID = "@flock.com/logger;1";

const PREF_LOGGER_ENABLED = "flock.service.logger.enabled";
const PREF_LOGGER_LEVEL = "flock.service.logger.level";

const NO_OVERRIDE_LEVEL = -1;

/**************************************************************************
 * Component: Flock Logging Service
 **************************************************************************/

// Constructor.
function flockLoggingService() {

  this.setLevel(CI.flockILoggingService.LEVEL_ALL);

  this._observers = [];

  this._enabled = true;

  var catmgr = CC["@mozilla.org/categorymanager;1"]
               .getService(CI.nsICategoryManager);
  var enum = catmgr.enumerateCategory("flockILoggingObserver");
  while (enum.hasMoreElements()) {
    try {
      var obj = enum.getNext();
      var supportsString = obj.QueryInterface(CI.nsISupportsCString);
      var shortName = supportsString.toString();
      var cid = catmgr.getCategoryEntry("flockILoggingObserver", shortName);
      var svc = CC[cid].getService(CI.flockILoggingObserver);
      this.addObserver(svc);
    } catch (ex) {
      // Can't log it... just print message and ignore the error.
      dump("flockLogger.js: cannot load registered "
           + "flockILoggingObserver component.\n");
    }
  }
}

/**************************************************************************
 * Flock Logging Service: XPCOM Component Creation
 **************************************************************************/

flockLoggingService.prototype = new FlockXPCOMUtils.genericComponent(
  FLOCK_LOGGING_SERVICE_CLASS_NAME,
  FLOCK_LOGGING_SERVICE_CLASS_ID,
  FLOCK_LOGGING_SERVICE_CONTRACT_ID,
  flockLoggingService,
  CI.nsIClassInfo.SINGLETON,
  [
    CI.flockILoggingService,
    CI.nsIObserver
  ]
);

// FlockXPCOMUtils.genericModule() categories
flockLoggingService.prototype._xpcom_categories = [
  { category: "app-startup", service: true }
];

/**************************************************************************
 * Flock Logging Service: Private Data and Functions
 **************************************************************************/

// Member variables.
flockLoggingService.prototype._observers = null;
flockLoggingService.prototype._level = null;
flockLoggingService.prototype._enabled = null;

/**************************************************************************
 * Flock Logging Service: flockILoggingService Implementation
 **************************************************************************/

flockLoggingService.prototype.log =
function flockLoggingService_log(aLevel, aOverrideLevel, aContext, aMessage) {

  var effectiveLevel = this._level;
  if (aOverrideLevel > NO_OVERRIDE_LEVEL) {
    effectiveLevel = aOverrideLevel;
  }

  if (this._enabled) {
    if (aLevel >= effectiveLevel) {
      var date = new Date();
      for each (var observer in this._observers) {
        observer.emit(date.getTime(), aLevel, aContext, aMessage);
      }
    }
  }
}

flockLoggingService.prototype.setLevel =
function flockLoggingService_setLevel(aLevel) {
  var oldLevel = this._level;
  this._level = aLevel;
  return oldLevel;
}

flockLoggingService.prototype.addObserver =
function flockLoggingService_addObserver(aObserver) {
  if (aObserver == null) {
    throw CR.NS_ERROR_INVALID_ARG;
  }
  this._observers.push(aObserver);
}

flockLoggingService.prototype.removeObserver =
function flockLoggingService_removeObserver(aObserver) {
  for (var i = 0; i < this._observers.length; i++) {
    if (aObserver == this._observers[i]) {
      this._observers.splice(i, 1);
      break;
    }
  }
}

/**************************************************************************
 * Flock Logging Service: nsIObserver Implementation
 **************************************************************************/

flockLoggingService.prototype.observe =
function flockLoggingService_observe(aSubject, aTopic, aState) {

  var prefService = CC["@mozilla.org/preferences-service;1"]
                    .getService(CI.nsIPrefBranch2);

  switch (aTopic) {
    case "app-startup":
      // These observers are required for the life of the application.
      //
      // Not removing them anywhere looks like a leak, but in fact it does
      // not leak until the application exits, at which point it becomes moot.
      //
      // Also, the "quit-application" notification comes *before* the
      // "profile-before-change" notification, which means we cannot use it
      // to remove these observers, as we still need one of them.
      var obs = CC["@mozilla.org/observer-service;1"]
                .getService(CI.nsIObserverService);
      obs.addObserver(this, "profile-after-change", false);
      obs.addObserver(this, "profile-before-change", false);
      break;

    case "profile-after-change":  // Also called on startup.
      // Ask for "nsPref:changed" notifications.
      prefService.addObserver(PREF_LOGGER_ENABLED, this, false);
      prefService.addObserver(PREF_LOGGER_LEVEL, this, false);
      // Preload with current prefs.
      this.observe(null, "nsPref:changed", null);
      break;

    case "profile-before-change": // Also called on shutdown.
      prefService.removeObserver(PREF_LOGGER_ENABLED, this);
      prefService.removeObserver(PREF_LOGGER_LEVEL, this);
      break;

    case "nsPref:changed":  // One of the above observers was triggered.
      if (prefService.getPrefType(PREF_LOGGER_ENABLED)) {
        this._enabled = prefService.getBoolPref(PREF_LOGGER_ENABLED);
      } else {
        this._enabled = true;
      }
      if (prefService.getPrefType(PREF_LOGGER_LEVEL)) {
        this.setLevel(prefService.getIntPref(PREF_LOGGER_LEVEL));
      } else {
        this.setLevel(CI.flockILoggingService.LEVEL_WARN);
      }
      break;
  }
  this.log(CI.flockILoggingService.LEVEL_DEBUG,
           NO_OVERRIDE_LEVEL,
           "logger",
           "observed '" + aTopic + "' notification");
}

/**************************************************************************
 * END Flock Logging Service
 **************************************************************************/


/**************************************************************************
 * Component: Flock Logger
 **************************************************************************/

// Constructor.
function flockLogger() {
  this._service = CC["@flock.com/logging-service;1"]
                  .getService(CI.flockILoggingService);
}

/**************************************************************************
 * Flock Logger: XPCOM Component Creation
 **************************************************************************/

flockLogger.prototype = new FlockXPCOMUtils.genericComponent(
  FLOCK_LOGGER_CLASS_NAME,
  FLOCK_LOGGER_CLASS_ID,
  FLOCK_LOGGER_CONTRACT_ID,
  flockLogger,
  0, // nsIClassInfo flags
  [CI.flockILogger]
);

/**************************************************************************
 * Flock Logger: Private Data and Functions
 **************************************************************************/

// Member variables.
flockLogger.prototype._service = null;
flockLogger.prototype._context = null;
flockLogger.prototype._overrideLevel = NO_OVERRIDE_LEVEL;

/**************************************************************************
 * Flock Logger: flockILogger Implementation
 **************************************************************************/

flockLogger.prototype.init =
function flockLogger_init(aContext) {

  this._context = aContext;

  // See if we want to override the logging level for this module.
  var myPrefLoggerLevel = PREF_LOGGER_LEVEL + "." + this._context;
  var prefService = CC["@mozilla.org/preferences-service;1"]
                    .getService(CI.nsIPrefBranch);
  if (prefService.getPrefType(myPrefLoggerLevel)) {
    this._overrideLevel = prefService.getIntPref(myPrefLoggerLevel);
  }
}

flockLogger.prototype.debug =
function flockLogger_debug(aMessage) {
  this._service.log(this._service.LEVEL_DEBUG,
                    this._overrideLevel, this._context, aMessage);
}

flockLogger.prototype.info =
function flockLogger_info(aMessage) {
  this._service.log(this._service.LEVEL_INFO,
                    this._overrideLevel, this._context, aMessage);
}

flockLogger.prototype.warn =
function flockLogger_warn(aMessage) {
  this._service.log(this._service.LEVEL_WARN,
                    this._overrideLevel, this._context, aMessage);
}

flockLogger.prototype.error =
function flockLogger_error(aMessage) {
  this._service.log(this._service.LEVEL_ERROR,
                    this._overrideLevel, this._context, aMessage);
}

flockLogger.prototype.fatal =
function flockLogger_fatal(aMessage) {
  this._service.log(this._service.LEVEL_FATAL,
                    this._overrideLevel, this._context, aMessage);
}

/**************************************************************************
 * END Flock Logger
 **************************************************************************/


/**************************************************************************
 * XPCOM Support - Module Construction
 **************************************************************************/

// Create array of components.
var gComponentsArray = [flockLoggingService, flockLogger];

// Generate a module for XPCOM to find.
var NSGetModule = FlockXPCOMUtils.generateNSGetModule(MODULE_NAME,
                                                      gComponentsArray);

/**************************************************************************
 * END XPCOM Support
 **************************************************************************/
