/* vim: ts=4 sw=4 expandtab cindent
 *
 * 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 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}');

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;}


// 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 (new flockJSProfiler()).QueryInterface(iid);
}

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



//DBSVC.off ();
