// vim: tabstop=2 softtabstop=2 shiftwidth=2 expandtab
//
// 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 CL_CONTRACTID = '@flock.com/remote-console-logger;1';
const CL_CLASSID    = Components.ID('{70dc2acf-7e36-4a43-8f28-3e5f70613428}');
const CL_CLASSNAME  = 'Flock Remote Console Logger';


const ENABLED_BY_DEFAULT = false;

const LOGGING_URL = "http://exceptions.flock.com/log";

const PREF_FLOCK_LOG_ERRORS = "flock.remote_logging";
const PREF_FLOCK_FIRSTRUN_UUID = "flock.firstrun.uuid";


const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;


gApp     = null;
gConsole = null;


function getObserverService() {
  return Cc['@mozilla.org/observer-service;1']
    .getService(Ci.nsIObserverService);
}


function RemoteConsoleLogger() {
  gApp = Cc['@mozilla.org/xre/app-info;1']
    .getService(Ci.nsIXULAppInfo)
    .QueryInterface(Ci.nsIXULRuntime);
  gConsole = Cc['@mozilla.org/consoleservice;1']
    .getService(Ci.nsIConsoleService);

  this._registered = false;

  this._listener = {
    logger: this,
    observe: function CLL_observe(obj) { this.logger._log(obj) }
  };

  var obs = getObserverService();

  obs.addObserver(this, 'profile-after-change', false);
  obs.addObserver(this, 'xpcom-shutdown', false);

  if (ENABLED_BY_DEFAULT)
    this.observe(null, 'nsPref:changed', null);
}

RemoteConsoleLogger.prototype = {
  _start: function CL__start() {
    var prefs = Cc['@mozilla.org/preferences-service;1']
      .getService(Ci.nsIPrefBranch2);

    prefs.addObserver(PREF_FLOCK_FIRSTRUN_UUID, this, false);
    prefs.addObserver(PREF_FLOCK_LOG_ERRORS, this, false);

    this.observe(null, 'nsPref:changed', null);
  },
  _configure: function CL__configure() {
    var prefs = Cc['@mozilla.org/preferences-service;1']
      .getService(Ci.nsIPrefBranch);

    var enabled;
    try {
      enabled = prefs.getBoolPref(PREF_FLOCK_LOG_ERRORS);
    }
    catch (e) {
      /* disable by default always for dev builds */
      if (gApp.appBuildID == '0000000000')
        enabled = false;
      else
        enabled = ENABLED_BY_DEFAULT;
    }

    var metrics = Cc["@flock.com/metrics-service;1"]
                  .getService(Ci.flockIMetricsService);

    this._preamble = gApp.appBuildID + " " + metrics.getUserUUID() + "\n";

    if (enabled && !this._registered)
      this._registerListener()
    else if (!enabled && this._registered)
      this._unregisterListener()
  },
  _shutdown: function CL__shutdown() {
    gApp     = null;
    gConsole = null;
  },
  _registerListener: function CL__registerListener() {
    try {
      gConsole.registerListener(this._listener);
      this._registered = true;
    }
    catch (e) {
      debug("Couldn't register with console service: " + e + "\n");
    }
  },
  _unregisterListener: function CL__unregisterListener() {
    try {
      gConsole.unregisterListener(this._listener);
      this._registered = false;
    }
    catch (e) {
      debug("Couldn't unregister with console service: " + e + "\n");
    }
  },
  _log: function CL__log(message) {
    try {
      var scriptError = message.QueryInterface(Ci.nsIScriptError);
      if (scriptError.sourceName.indexOf('http:') != -1 ||
          scriptError.sourceName.indexOf('https:') != -1)
        return;

      var hr = Cc['@mozilla.org/xmlextras/xmlhttprequest;1']
        .createInstance(Ci.nsIXMLHttpRequest);
      hr.backgroundRequest = true;
      hr.open('POST', LOGGING_URL);
      hr.send(this._preamble + scriptError.toString() + '\n');
    }
    catch (e) {
      // Something bad happened, just ignore it
    }
  },

  observe: function CL_observe(subject, topic, state) {
    var obs = getObserverService();

    switch (topic) {
      case 'profile-after-change':
        obs.removeObserver(this, 'profile-after-change');
        this._start();
        break;

      case 'xpcom-shutdown':
        obs.removeObserver(this, 'xpcom-shutdown');
        this._shutdown();
        break;

      case 'nsPref:changed':
        this._configure();
        break;
    }
  },

  getInterfaces: function CL_getInterfaces(countRef) {
    var interfaces = [Ci.nsIObserver, Ci.nsIClassInfo, Ci.nsISupports];
    countRef.value = interfaces.length;
    return interfaces;
  },
  getHelperForLanguage: function CL_getHelperForLanguage(language) {
    return null;
  },
  contractID: CL_CONTRACTID,
  classDescription: CL_CLASSNAME,
  classID: CL_CLASSID,
  implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
  flags: Ci.nsIClassInfo.SINGLETON,

  QueryInterface: function CL_QueryInterface(iid) {
    if (iid.equals(Ci.nsIObserver) ||
        iid.equals(Ci.nsIClassInfo) ||
        iid.equals(Ci.nsISupports))
      return this;
    throw Cr.NS_ERROR_NO_INTERFACE;
  }
}


function GenericComponentFactory(ctor) {
  this._ctor = ctor;
}

GenericComponentFactory.prototype = {

  _ctor: null,

  // nsIFactory
  createInstance: function(outer, iid) {
    if (outer != null)
      throw Cr.NS_ERROR_NO_AGGREGATION;
    return (new this._ctor()).QueryInterface(iid);
  },

  // nsISupports
  QueryInterface: function(iid) {
    if (iid.equals(Ci.nsIFactory) ||
        iid.equals(Ci.nsISupports))
      return this;
    throw Cr.NS_ERROR_NO_INTERFACE;
  },
};

var Module = {
  QueryInterface: function(iid) {
    if (iid.equals(Ci.nsIModule) ||
        iid.equals(Ci.nsISupports))
      return this;

    throw Cr.NS_ERROR_NO_INTERFACE;
  },

  getClassObject: function(cm, cid, iid) {
    if (!iid.equals(Ci.nsIFactory))
      throw Cr.NS_ERROR_NOT_IMPLEMENTED;

    if (cid.equals(CL_CLASSID))
      return new GenericComponentFactory(RemoteConsoleLogger)

    throw Cr.NS_ERROR_NO_INTERFACE;
  },

  registerSelf: function(cm, file, location, type) {
    var cr = cm.QueryInterface(Ci.nsIComponentRegistrar);
    cr.registerFactoryLocation(CL_CLASSID, CL_CLASSNAME, CL_CONTRACTID,
                               file, location, type);

    var catman = Cc['@mozilla.org/categorymanager;1']
      .getService(Ci.nsICategoryManager);
    catman.addCategoryEntry('app-startup', CL_CLASSNAME,
                            'service,' + CL_CONTRACTID,
                            true, true);
  },

  unregisterSelf: function(cm, location, type) {
    var cr = cm.QueryInterface(Ci.nsIComponentRegistrar);
    cr.unregisterFactoryLocation(CL_CLASSID, location);
  },

  canUnload: function(cm) {
    return true;
  },
};

function NSGetModule(compMgr, fileSpec)
{
  return Module;
}
