/* vim: ts=4 sw=4 expandtab cindent
 *
 * BEGIN FLOCK GPL
 *
 * Copyright Flock Inc. 2005-2009
 * 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 JS_DEBUG = true;
function debug(msg) { if (JS_DEBUG) dump("jsProfiler:: " + msg + "\n"); }

const JSPROFILER_CONTRACTID = '@flock.com/js/profiler;1';
const JSPROFILER_CID = Components.ID('{191c2940-667f-4f0c-bfdb-aee09ab8f566}');

function jsProfilerNamespace() {


  const DBSVC = Components.classes['@mozilla.org/js/jsd/debugger-service;1']
          .getService (Components.interfaces.jsdIDebuggerService);
  const RDFS = Components.classes['@mozilla.org/rdf/rdf-service;1']
          .getService (Components.interfaces.nsIRDFService);
  const RDFCU = Components.classes['@mozilla.org/rdf/container-utils;1']
          .getService (Components.interfaces.nsIRDFContainerUtils);
  const IOS = Components.classes['@mozilla.org/network/io-service;1']
          .getService (Components.interfaces.nsIIOService);

  const RDF_FUNCTIONS = RDFS.GetResource ('urn:flock:profiler:functions');

  const RDF_FILE_NAME = RDFS.GetResource ('http://flock.com/profiler#fileName');
  const RDF_FUNCTION_NAME = RDFS.GetResource ('http://flock.com/profiler#functionName');
  const RDF_LINE_NUMBER = RDFS.GetResource ('http://flock.com/profiler#lineNumber');
  const RDF_LINE_EXTENT = RDFS.GetResource ('http://flock.com/profiler#lineExtent');
  const RDF_CALL_COUNT = RDFS.GetResource ('http://flock.com/profiler#callCount');
  const RDF_MAX_RECURSE_DEPTH = RDFS.GetResource ('http://flock.com/profiler#maxRecurseDepth');
  const RDF_MIN_EXECUTION_TIME = RDFS.GetResource ('http://flock.com/profiler#minExecutionTime');
  const RDF_MAX_EXECUTION_TIME = RDFS.GetResource ('http://flock.com/profiler#maxExecutionTime');
  const RDF_TOTAL_EXECUTION_TIME = RDFS.GetResource ('http://flock.com/profiler#totalExecutionTime');
  const RDF_MIN_OWN_EXECUTION_TIME = RDFS.GetResource ('http://flock.com/profiler#minOwnExecutionTime');
  const RDF_MAX_OWN_EXECUTION_TIME = RDFS.GetResource ('http://flock.com/profiler#maxOwnExecutionTime');
  const RDF_TOTAL_OWN_EXECUTION_TIME = RDFS.GetResource ('http://flock.com/profiler#totalOwnExecutionTime');

  function flockJSProfiler () {
    this.mOutput = null;
  }

  // flockIJSProfiler implementation
  flockJSProfiler.prototype.isRunning = false;

  flockJSProfiler.prototype.start =
  function flockJSProfiler_start () {
      DBSVC.on (); // need to turn it on before setting flags
      DBSVC.flags |= Components.interfaces.jsdIDebuggerService.COLLECT_PROFILE_DATA;
      this.isRunning = true;
  }

  flockJSProfiler.prototype.stop =
  function flockJSProfiler_stop () {
      DBSVC.flags &= ~Components.interfaces.jsdIDebuggerService.COLLECT_PROFILE_DATA;
      this.isRunning = false;
  }

  flockJSProfiler.prototype.getProfilerState =
  function flockJSProfiler_getProfilerState()
  {
      return Boolean(DBSVC.flags & Components.interfaces.jsdIDebuggerService.COLLECT_PROFILE_DATA);
  }

  flockJSProfiler.prototype.getProfileData =
  function flockJSProfiler_getProfileData(aDataSource)
  {
    var functions = RDFCU.MakeSeq (aDataSource, RDF_FUNCTIONS);
  
    DBSVC.enumerateScripts(
      {
        enumerateScript: function (aScript) {
          if (aScript.callCount == 0) {
              debug("No Scripts were called in " + aScript.functionName + ", aScript.callCount = 0");
              return;
          }
          // only log chrome function calls
          if (aScript.fileName.match(/^chrome:\/\/.*/)) {
            debug("Adding function [" + aScript.functionName + "] from file [" + aScript.fileName + "]");
          
            var node = RDFS.GetAnonymousResource ();
            functions.AppendElement (node);
    
            // script info
            aDataSource.Assert (node, RDF_FILE_NAME, RDFS.GetLiteral (aScript.fileName), true);
            aDataSource.Assert (node, RDF_FUNCTION_NAME,RDFS.GetLiteral (aScript.functionName), true);
            aDataSource.Assert (node, RDF_LINE_NUMBER, RDFS.GetIntLiteral (aScript.baseLineNumber), true);
            aDataSource.Assert (node, RDF_LINE_EXTENT, RDFS.GetIntLiteral (aScript.lineExtent), true);
    
            // profile info
            aDataSource.Assert (node, RDF_CALL_COUNT, RDFS.GetIntLiteral (aScript.callCount), true);
            aDataSource.Assert (node, RDF_MAX_RECURSE_DEPTH, RDFS.GetIntLiteral (aScript.maxRecurseDepth), true);
            aDataSource.Assert (node, RDF_MIN_EXECUTION_TIME, RDFS.GetIntLiteral (Math.round (aScript.minExecutionTime*1000)), true);
            aDataSource.Assert (node, RDF_MAX_EXECUTION_TIME, RDFS.GetIntLiteral (Math.round (aScript.maxExecutionTime*1000)), true);
            aDataSource.Assert (node, RDF_TOTAL_EXECUTION_TIME, RDFS.GetIntLiteral (Math.round (aScript.totalExecutionTime*1000)), true);
            aDataSource.Assert (node, RDF_MIN_OWN_EXECUTION_TIME, RDFS.GetIntLiteral (Math.round (aScript.minOwnExecutionTime*1000)), true);
            aDataSource.Assert (node, RDF_MAX_OWN_EXECUTION_TIME, RDFS.GetIntLiteral (Math.round (aScript.maxOwnExecutionTime*1000)), true);
            aDataSource.Assert (node, RDF_TOTAL_OWN_EXECUTION_TIME, RDFS.GetIntLiteral (Math.round (aScript.totalOwnExecutionTime*1000)), true);
          } else {
            debug("Ignoring function " + aScript.functionName + " from file " + aScript.fileName);
          }
        }
      });
  }

  flockJSProfiler.prototype.clearProfileData =
  function flockJSProfile_clearProfileData () {
    DBSVC.clearProfileData();
  }
  // END flockIJSProfiler

  // the nsICommandLineHandler
  flockJSProfiler.prototype.helpInfo = '        -profiler <output>      Enable profiling\n';
  flockJSProfiler.prototype.handle =
  function flockJSProfiler_handle (aCommandLine) {
      var output = aCommandLine.handleFlagWithParam ('profiler', false);
      if (output) {
          this.mOutput = output;
          Components.classes['@mozilla.org/observer-service;1']
                  .getService(Components.interfaces.nsIObserverService)
                  .addObserver(this, 'quit-application-requested', false);
          this.start ();
      }
  }

  // the nsIObserver implementation
  flockJSProfiler.prototype.observe =
  function flockJSProfiler_observe (aSubject, aTopic, aData) {
      if (aTopic != 'quit-application') {
          return;
      }
      var file = Components.classes['@mozilla.org/file/local;1']
              .createInstance (Components.interfaces.nsILocalFile);
      file.initWithPath (this.mOutput);
      var ds = RDFS.GetDataSourceBlocking (IOS.newFileURI(file).asciiSpec);
      this.stop();
      this.getProfileData(ds);
      ds.QueryInterface (Components.interfaces.nsIRDFRemoteDataSource);
      ds.Flush();
      DBSVC.off ();
  }

  // the nsISupports implementation
  flockJSProfiler.prototype.QueryInterface =
  function (iid) {
      if (!iid.equals(Components.interfaces.flockIJSProfiler) &&
              !iid.equals(Components.interfaces.nsIClassInfo) &&
              !iid.equals(Components.interfaces.nsISupports) &&
              !iid.equals(Components.interfaces.nsICommandLineHandler) &&
              !iid.equals(Components.interfaces.nsIObserver)) {
          throw Components.results.NS_ERROR_NO_INTERFACE;
      }
      return this;
  }

  // the nsIClassInfo implementation
  flockJSProfiler.prototype.flags = Components.interfaces.nsIClassInfo.SINGLETON;
  flockJSProfiler.prototype.classDescription = "Flock JS Profiler Service";
  flockJSProfiler.prototype.getInterfaces = function (count) {
      var interfaceList = [Components.interfaces.flockIJSProfiler,
              Components.interfaces.nsIClassInfo,
              Components.interfaces.nsISupports,
              Components.interfaces.nsICommandLineHandler,
              Components.interfaces.nsIObserver,
      ];
      count.value = interfaceList.length;
      return interfaceList;
  }
  flockJSProfiler.prototype.getHelperForLanguage = function (count) {return null;}

  return new flockJSProfiler();
}
// end jsProfilerNamespace

// Module implementation
var jsProfilerModule = {};
jsProfilerModule.registerSelf =
function (compMgr, fileSpec, location, type)
{
    compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
    compMgr.registerFactoryLocation(JSPROFILER_CID, 
                                    'Flock JS Profiler Component',
                                    JSPROFILER_CONTRACTID, 
                                    fileSpec, 
                                    location,
                                    type);

    var catman = Components.classes['@mozilla.org/categorymanager;1']
        .getService(Components.interfaces.nsICategoryManager);
    catman.addCategoryEntry('app-startup', 'Flock JS Profiler Component',
                            'service,' + JSPROFILER_CONTRACTID,
                            true, true);
    catman.addCategoryEntry('command-line-handler', 'mflockjsprofiler',
                            JSPROFILER_CONTRACTID,
                            true, true);
}
jsProfilerModule.getClassObject =
function (compMgr, cid, iid)
{
    if (!cid.equals(JSPROFILER_CID))
        throw Components.results.NS_ERROR_NO_INTERFACE;
    
    if (!iid.equals(Components.interfaces.nsIFactory))
        throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
    
    return jsProfilerServiceFactory;
}

jsProfilerModule.canUnload =
function(compMgr)
{
    return true;
}
    
/* factory object */
var jsProfilerServiceFactory = new Object();

jsProfilerServiceFactory.createInstance =
function (outer, iid)
{
    if (outer != null)
        throw Components.results.NS_ERROR_NO_AGGREGATION;

    return (jsProfilerNamespace()).QueryInterface(iid);
}

/* entrypoint */
function NSGetModule(compMgr, fileSpec)
{
    return jsProfilerModule;
}



//DBSVC.off ();
