/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * 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.
 *
 * The Original Code is The JavaScript Debugger.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Robert Ginda, <rginda@netscape.com>, original author
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#filter substitution

const __vnk_version        = "@VENKMAN_VERSION@";
const __vnk_requiredLocale = "0.9.87.3";
var   __vnk_versionSuffix  = "";

const __vnk_counter_url = 
"http://www.hacksrus.com/~ginda/venkman/launch-counter/next-sequence.cgi"

/* dd is declared first in venkman-utils.js */
var warn;
var ASSERT;

if (DEBUG)
{
    _dd_pfx = "vnk: ";
    warn = function (msg) { dumpln ("** WARNING " + msg + " **"); }
    ASSERT = function (expr, msg) {
                 if (!expr) {
                     dump ("** ASSERTION FAILED: " + msg + " **\n" + 
                           getStackTrace() + "\n"); 
                     return false;
                 } else {
                     return true;
                 }
             }
}    
else
    dd = warn = ASSERT = function (){};


const MAX_WORD_LEN = 20; /* number of characters to display before forcing a 
                          * potential word break point (in the console.) */

const LINE_BREAKABLE = 0x01;
const LINE_BREAK     = 0x02;
const LINE_FBREAK    = 0x04;

var console = new Object();

console.initialized = false;

/* |this|less functions */

function setStopState(state)
{
    var tb = console.ui["stop-button"];
    if (state)
    {
        console.jsds.interruptHook = console.executionHook;
        tb.setAttribute("willStop", "true");
    }
    else
    {
        console.jsds.interruptHook = null;
        tb.removeAttribute("willStop");
    }
}

function setProfileState(state)
{
    var tb = console.ui["profile-button"];
    if (state)
    {
        console.profiler.enabled = true;
        tb.setAttribute("profile", "true");
    }
    else
    {
        console.profiler.enabled = false;
        tb.removeAttribute("profile");
    }
}

function enableDebugCommands()
{
    var cmds = console.commandManager.list ("", CMD_NEED_STACK);
    for (var i in cmds)
        cmds[i].enabled = true;
    
    cmds = console.commandManager.list ("", CMD_NO_STACK);
    for (i in cmds)
        cmds[i].enabled = false;
}

function disableDebugCommands()
{
    var cmds = console.commandManager.list ("", CMD_NEED_STACK);
    for (var i in cmds)
        cmds[i].enabled = false;
    
    cmds = console.commandManager.list ("", CMD_NO_STACK);
    for (i in cmds)
        cmds[i].enabled = true;
}


function isDOMIInstalled()
{
    const IOSERVICE_CTRID = "@mozilla.org/network/io-service;1";
    const nsIIOService    = Components.interfaces.nsIIOService;
    const CHROMEREG_CTRID = "@mozilla.org/chrome/chrome-registry;1";
    const nsIChromeReg    = Components.interfaces.nsIChromeRegistry;

    var iosvc = Components.classes[IOSERVICE_CTRID].getService(nsIIOService);
    var uri = iosvc.newURI("chrome://inspector/content/inspector.xul",
                           null, null);
    // Evil hack: check if the chrome registry knows about Inspector.
    // If it does, it's installed, otherwise it isn't.
    try
    {
        var cr = Components.classes[CHROMEREG_CTRID].getService(nsIChromeReg);
        var realURI = cr.convertChromeURL(uri);
    }
    catch (ex)
    {
        return false;
    }
    return true;
}

function isDOMThing(o)
{
    const nsIDOMNode     = Components.interfaces.nsIDOMNode;
    const nsIDOMDocument = Components.interfaces.nsIDOMDocument;
    return isinstance(o, nsIDOMNode);
}

function isURLVenkman (url)
{
    return (url.search (/^chrome:\/\/venkman/) == 0 &&
            url.search (/test/) == -1);
}

function isURLFiltered (url)
{
    return (!url ||
            (console.prefs["enableChromeFilter"] &&
             (url.search(/^chrome:/) == 0 ||
              ("componentPath" in console &&
               url.indexOf(console.componentPath) == 0))) ||
            (url.search (/^chrome:\/\/venkman/) == 0 &&
             url.search (/test/) == -1));
}

function toUnicode (msg, charset)
{
    try
    {
        console.ucConverter.charset = charset;
        return console.ucConverter.ConvertToUnicode(msg);
    }
    catch (ex)
    {
        dd ("caught " + ex + " converting to unicode");
        return msg;
    }
}

function fromUnicode (msg, charset)
{
    try
    {
        console.ucConverter.charset = charset;
        return console.ucConverter.ConvertFromUnicode(msg);
    }
    catch (ex)
    {
        dd ("caught " + ex + " converting from unicode");
        return msg;
    }
}

function displayUsageError (e, details)
{
    if (!("isInteractive" in e) || !e.isInteractive)
    {
        var caller = Components.stack.caller.caller;
        if (caller.name == "dispatch")
            caller = caller.caller;
        var error = new Error (details);
        error.fileName = caller.filename;
        error.lineNumber = caller.lineNumber;
        error.name = caller.name;
        display (formatException(error), MT_ERROR);
    }
    else
    {
        display (details, MT_ERROR);
    }
    
    display (getMsg(MSN_FMT_USAGE, [e.command.name, e.command.usage]), MT_USAGE);
}

function dispatch (text, e, flags)
{
    if (!e)
        e = new Object();

    if (!("inputData" in e))
        e.inputData = "";
    
    /* split command from arguments */
    var ary = text.match (/(\S+) ?(.*)/);
    e.commandText = ary[1];
    e.inputData =  ary[2] ? stringTrim(ary[2]) : "";
    
    /* list matching commands */
    ary = console.commandManager.list (e.commandText, flags);
    var rv = null;
    var i;
    
    switch (ary.length)
    {            
        case 0:
            /* no match, try again */
            display (getMsg(MSN_NO_CMDMATCH, e.commandText), MT_ERROR);
            break;
            
        case 1:
            /* one match, good for you */
            var ex;
            try
            {
                rv = dispatchCommand(ary[0], e, flags);
            }
            catch (ex)
            {
                display (getMsg(MSN_ERR_INTERNAL_DISPATCH, ary[0].name),
                         MT_ERROR);
                display (formatException(ex), MT_ERROR);
                dd (formatException(ex), MT_ERROR);
                if (typeof ex == "object" && "stack" in ex)
                    dd (ex.stack);
            }
            break;
            
        default:
            /* more than one match, show the list */
            var str = "";
            for (i in ary)
                str += (str) ? MSG_COMMASP + ary[i].name : ary[i].name;
            display (getMsg (MSN_ERR_AMBIGCOMMAND,
                             [e.commandText, ary.length, str]), MT_ERROR);
    }

    return rv;
}

function dispatchCommand (command, e, flags)
{
    function callHooks (command, isBefore)
    {
        var names, hooks;
        
        if (isBefore)
            hooks = command.beforeHooks;
        else
            hooks = command.afterHooks;

        for (var h in hooks)
        {
            if ("dbgDispatch" in console && console.dbgDispatch)
            {
                dd ("calling " + (isBefore ? "before" : "after") + 
                    " hook " + h);
            }
            try
            {
                hooks[h](e);
            }
            catch (ex)
            {
                if (e.command.name != "hook-session-display")
                {
                    display (getMsg(MSN_ERR_INTERNAL_HOOK, h), MT_ERROR);
                    display (formatException(ex), MT_ERROR);
                }
                else
                {
                    dd (getMsg(MSN_ERR_INTERNAL_HOOK, h));
                }

                dd ("Caught exception calling " +
                    (isBefore ? "before" : "after") + " hook " + h);
                dd (formatException(ex));
                if (typeof ex == "object" && "stack" in ex)
                    dd (ex.stack);
                else
                    dd (getStackTrace());
            }
        }
    };
    
    e.command = command;

    if ((e.command.flags & CMD_NEED_STACK) &&
        (!("frames" in console)))
    {
        /* doc, it hurts when I do /this/... */
        display (MSG_ERR_NO_STACK, MT_ERROR);
        return null;
    }
    
    if (!e.command.enabled)
    {
        /* disabled command */
        display (getMsg(MSN_ERR_DISABLED, e.command.name),
                 MT_ERROR);
        return null;
    }
    
    var h, i;
    
    if ("beforeHooks" in e.command)
        callHooks (e.command, true);
    
    if (typeof e.command.func == "function")
    {
        /* dispatch a real function */
        if (e.command.usage)
            console.commandManager.parseArguments (e);
        if ("parseError" in e)
        {
            displayUsageError (e, e.parseError);
        }
        else
        {
            if ("dbgDispatch" in console && console.dbgDispatch)
            {
                var str = "";
                for (i = 0; i < e.command.argNames.length; ++i)
                {
                    var name = e.command.argNames[i];
                    if (name in e)
                        str += " " + name + ": " + e[name];
                    else if (name != ":")
                        str += " ?" + name;
                }
                dd (">>> " + e.command.name + str + " <<<");
                e.returnValue = e.command.func(e);
                /* set console.lastEvent *after* dispatching, so the dispatched
                 * function actually get's a chance to see the last event. */
                console.lastEvent = e;
            }
            else
            {
                e.returnValue = e.command.func(e);
            }

        }
    }
    else if (typeof e.command.func == "string")
    {
        /* dispatch an alias (semicolon delimited list of subcommands) */
        var commandList = e.command.func.split(";");
        for (i = 0; i < commandList.length; ++i)
        {
            var newEvent = Clone (e);
            delete newEvent.command;            
            commandList[i] = stringTrim(commandList[i]);
            dispatch (commandList[i], newEvent, flags);
        }
    }
    else
    {
        display (getMsg(MSN_ERR_NOTIMPLEMENTED, e.command.name),
                 MT_ERROR);
        return null;
    }

    if ("afterHooks" in e.command)
        callHooks (e.command, false);

    return ("returnValue" in e) ? e.returnValue : null;
}

function feedback(e, message, msgtype)
{
    if ("isInteractive" in e && e.isInteractive)
        display (message, msgtype);
}

function display(message, msgtype)
{
    if (typeof message == "undefined")
        throw new RequiredParam ("message");

    if (typeof message != "string" &&
        !(message instanceof Components.interfaces.nsIDOMHTMLElement))
        throw new InvalidParam ("message", message);

    if ("capturedMsgs" in console)
        console.capturedMsgs.push ([message, msgtype]);
        
    dispatchCommand(console.coDisplayHook, 
                    { message: message, msgtype: msgtype });
}

function evalInDebuggerScope (script, rethrow)
{
    try
    {
        return console.doEval (script);
    }
    catch (ex)
    {
        if (rethrow)
            throw ex;

        display (formatEvalException(ex), MT_ERROR);
        return null;
    }
}

function evalInTargetScope (script, rethrow)
{
    if (!console.frames)
    {
        display (MSG_ERR_NO_STACK, MT_ERROR);
        return null;
    }
    
    var rval = new Object();
    
    if (!getCurrentFrame().eval (script, MSG_VAL_CONSOLE, 1, rval))
    {
        if (rethrow)
            throw rval.value;
        
        //dd ("exception: " + dumpObjectTree(rval.value));
        display (formatEvalException (rval.value), MT_ERROR);
        return null;
    }

    return ("value" in rval) ? rval.value : null;
}

function formatException (ex)
{
    if (ex instanceof BadMojo)
        return getMsg (MSN_FMT_BADMOJO,
                       [ex.errno, ex.message, ex.fileName, ex.lineNumber, 
                        ex.functionName]);

    if (ex instanceof Error)
        return getMsg (MSN_FMT_JSEXCEPTION, [ex.name, ex.message, ex.fileName, 
                                             ex.lineNumber]);

    return String(ex);
}

function formatEvalException (ex)
{
    if (ex instanceof BadMojo || ex instanceof Error)
        return formatException (ex);
    else if (ex instanceof jsdIValue)
        ex = ex.stringValue;
    
    return getMsg (MSN_EVAL_THREW,  String(ex));
}

function init()
{
    dd ("init {");
    
    var i;
    const WW_CTRID = "@mozilla.org/embedcomp/window-watcher;1";
    const nsIWindowWatcher = Components.interfaces.nsIWindowWatcher;

    console.windowWatcher =
        Components.classes[WW_CTRID].getService(nsIWindowWatcher);

    const UC_CTRID = "@mozilla.org/intl/scriptableunicodeconverter";
    const nsIUnicodeConverter = 
        Components.interfaces.nsIScriptableUnicodeConverter;
    console.ucConverter =
        Components.classes[UC_CTRID].getService(nsIUnicodeConverter);

    console.baseWindow = getBaseWindowFromWindow(window);
    console.mainWindow = window;
    console.dnd = nsDragAndDrop;
    console.currentEvalObject = console;
    
    console.files = new Object();
    console.pluginState = new Object();

    console._statusStack = new Array();
    console._lastStackDepth = -1;

    initMsgs();
    initPrefs();
    initCommands();
    initJSDURL();
    
    /* Some commonly used commands, cached now, for use with dispatchCommand. */
    var cm = console.commandManager;
    console.coManagerCreated    = cm.commands["hook-script-manager-created"];
    console.coManagerDestroyed  = cm.commands["hook-script-manager-destroyed"];
    console.coInstanceCreated   = cm.commands["hook-script-instance-created"];
    console.coInstanceSealed    = cm.commands["hook-script-instance-sealed"];
    console.coInstanceDestroyed = cm.commands["hook-script-instance-destroyed"];
    console.coDisplayHook       = cm.commands["hook-session-display"];
    console.coFindScript        = cm.commands["find-script"];

    console.commandManager.addHooks (console.hooks);

    initMenus();

    console.capturedMsgs = new Array();
    var ary = console.prefs["initialScripts"].split(/\s*;\s*/);
    for (i = 0; i < ary.length; ++i)
    {
        var url = stringTrim(ary[i]);
        if (url)
            dispatch ("loadd", { url: ary[i] });
    }

    dispatch ("hook-venkman-started");

    initViews();
    initRecords();
    initHandlers(); // handlers may notice windows, which need views and records

    createMainMenu(document);
    createMainToolbar(document);

    // Must be done after menus are "here", since it corrects them for the host.
    initApplicationCompatibility();

    console.ui = {
        "status-text": document.getElementById ("status-text"),
        "profile-button": document.getElementById ("maintoolbar:profile-tb"),
        "stop-button": document.getElementById ("maintoolbar:stop")
    };    

    disableDebugCommands();

    initDebugger();
    initProfiler();

    // read prefs that have not been explicitly created, such as the layout prefs
    console.prefManager.readPrefs();
    
    fetchLaunchCount();
    
    console.sourceText = new HelpText();

    console.pushStatus(MSG_STATUS_DEFAULT);

    dispatch ("restore-layout default");

    var startupMsgs = console.capturedMsgs;
    delete console.capturedMsgs;
    
    dispatch ("version");

    for (i = 0; i < startupMsgs.length; ++i)
        display (startupMsgs[i][0], startupMsgs[i][1]);

    display (getMsg(MSN_TIP1_HELP, 
                    console.prefs["sessionView.requireSlash"] ? "/" : ""));
    display (MSG_TIP2_HELP);
    if (console.prefs["sessionView.requireSlash"])
        display (MSG_TIP3_HELP);

    if (console.prefs["rememberPrettyprint"])
        dispatch ("pprint", { toggle: console.prefs["prettyprint"] });
    else
        dispatch ("pprint", { toggle: false });

    if (MSG_LOCALE_VERSION != __vnk_requiredLocale)
    {
        display (getMsg(MSN_BAD_LOCALE,
                        [__vnk_requiredLocale, MSG_LOCALE_VERSION]),
                 MT_WARN);
    }

    // Delay being "done" until the window is up.
    setTimeout(initPost, 100);

    if (console.prefs["saveSettingsOnExit"])
    {
        dispatch ("restore-settings",
                  {settingsFile: console.prefs["settingsFile"]});
    }

    dispatch ("hook-venkman-started");

    dd ("}");
}

function initApplicationCompatibility()
{
    // This function does nothing more than tweak the UI based on the host
    // application.

    // Set up simple host and platform information.
    console.host = "Unknown";
    console.haveBrowser = false;

    var app = getService("@mozilla.org/xre/app-info;1", "nsIXULAppInfo");
    // nsIXULAppInfo wasn't implemented before 1.8...
    if (app)
    {
        // Use the XULAppInfo.ID to find out what host we run on.
        switch (app.ID)
        {
            case "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}":
                console.host = "Firefox";
                console.haveBrowser = true;
                break;
            case "{3550f703-e582-4d05-9a08-453d09bdfdc6}": // Thunderbird
                console.host = "Thunderbird";
                break;
            case "{a463f10c-3994-11da-9945-000d60ca027b}": // Flock
                console.host = "Flock";
                console.haveBrowser = true;
                break;
            case "{718e30fb-e89b-41dd-9da7-e25a45638b28}": // Sunbird
                console.host = "Sunbird";
                break;
            case "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}": // SeaMonkey
                console.host = "Mozilla";
                console.haveBrowser = true;
                break;
            default:
                console.unknownAppUID = app.ID;
                console.host = "ToolkitApp"; // Unknown host, platform install
        }
    }
    else if ("getBrowserURL" in window)
    {
        var url = getBrowserURL();
        if (url == "chrome://navigator/content/navigator.xul")
        {
            console.host = "Mozilla";
            console.haveBrowser = true;
        }
        else if (url == "chrome://browser/content/browser.xul")
        {
            console.host = "Firefox";
            console.haveBrowser = true;
        }
        else
        {
            console.unknownAppUID = url;
        }
    }

    console.platform = "Unknown";
    if (navigator.platform.search(/mac/i) > -1)
        console.platform = "Mac";
    if (navigator.platform.search(/win/i) > -1)
        console.platform = "Windows";
    if (navigator.platform.search(/linux/i) > -1)
        console.platform = "Linux";
    if (navigator.platform.search(/os\/2/i) > -1)
        console.platform = "OS/2";

    console.hostPlatform = console.host + console.platform;

    // The menus and the component bar need to be hidden on some hosts.
    var winMenu   = document.getElementById("windowMenu");
    var tasksMenu = document.getElementById("tasksMenu");
    //var toolsMenu = document.getElementById("mainmenu:tools");
    var comBar    = document.getElementById("component-bar");

    if (console.host != "Mozilla") {
        tasksMenu.parentNode.removeChild(tasksMenu);
        winMenu.parentNode.removeChild(winMenu);
        comBar.collapsed = true;
    }
}

function initPost()
{
    console.initialized = true;
}

function destroy ()
{
    if (console.prefs["saveLayoutOnExit"])
        dispatch ("save-layout default");

    if (console.prefs["saveSettingsOnExit"])
    {
        dispatch ("save-settings",
                  {settingsFile: console.prefs["settingsFile"]});
    }

    delete console.currentEvalObject;
    delete console.jsdConsole;

    destroyViews();
    destroyHandlers();
    destroyPrefs();
    detachDebugger();
}

function paintHack (i)
{
    /* when stopping at a timeout, we don't repaint correctly.
     * by jamming a character into this hidden text box, we can force
     * a repaint.
     */
    for (var w in console.viewManager.windows)
    {
        var window = console.viewManager.windows[w];
        var textbox = window.document.getElementById("paint-hack");
        if (textbox)
        {
            textbox.value = " ";
            textbox.value = "";
        }
    }
    
    if (!i)
        i = 0;
    
    if (i < 4)
        setTimeout (paintHack, 250, i + 1);
}   

function fetchLaunchCount()
{
    ++console.prefs["startupCount"];

    if (!toBool(console.prefs["permitStartupHit"]))
        return;

    function onLoad ()
    {
        var ary = String(r.responseText).match(/(\d+)/);
        if (ary)
        {
            display (getMsg(MSN_LAUNCH_COUNT,
                            [console.prefs["startupCount"], ary[1]]));
        }
    };
    
    try
    {
        var r = new XMLHttpRequest();
        r.onload = onLoad;
        r.open ("GET",
                __vnk_counter_url + "?local=" + console.prefs["startupCount"] +
                "&version=" + __vnk_version);
        r.send (null);
    }
    catch (ex)
    {
        // Oops. Probably missing the xmlextras extension, can't really do 
        // much about that.
        display(getMsg(MSN_LAUNCH_COUNT,
                       [console.prefs["startupCount"], MSG_VAL_UNKNOWN]));
    }
}

function getCommandEnabled(command)
{
    try {
        var dispatcher = top.document.commandDispatcher;
        var controller = dispatcher.getControllerForCommand(command);
        
        return controller.isCommandEnabled(command);
    }
    catch (e)
    {
        return false;
    }
}

function doCommand(command)
{
    try {
        var dispatcher = top.document.commandDispatcher;
        var controller = dispatcher.getControllerForCommand(command);
        if (controller && controller.isCommandEnabled(command))
            controller.doCommand(command);
    }
    catch (e)
    {
    }
}

console.__defineGetter__ ("userAgent", con_ua);
function con_ua ()
{
    var ary = navigator.userAgent.match (/;\s*([^;\s]+\s*)\).*\/(\d+)/);
    if (ary)
    {
        return ("Venkman " + __vnk_version + __vnk_versionSuffix + 
                " [Mozilla " + ary[1] + "/" + ary[2] + "]");
    }

    return ("Venkman " + __vnk_version + __vnk_versionSuffix + " [" + 
            navigator.userAgent + "]");
}

console.hooks = new Object();

console.hooks["hook-script-instance-sealed"] =
function hookScriptSealed (e)
{
    var url = e.scriptInstance.url;
    if (!("componentPath" in console))
    {
        const VNK_SVC = "venkman-service.js";
        if (url.substr(-VNK_SVC.length) == VNK_SVC)
        {
            if (!console.prefs["enableChromeFilter"])
            {
                dispatch ("debug-instance-on",
                          { scriptInstance: e.scriptInstance });
            }
            console.componentPath = url.substr(0, url.length - VNK_SVC.length);
        }
    }

    for (var fbp in console.fbreaks)
    {
        if (console.fbreaks[fbp].enabled &&
            url.indexOf(console.fbreaks[fbp].url) != -1)
        {
            e.scriptInstance.setBreakpoint(console.fbreaks[fbp].lineNumber,
                                           console.fbreaks[fbp]);
        }
    }
}

console.hooks["hook-debug-stop"] =
function hookDebugStop (e)
{
    var jsdFrame = setCurrentFrameByIndex(0);
    var type = console.trapType;

    //var frameRec = console.stackView.stack.childData[0];
    //console.pushStatus (getMsg(MSN_STATUS_STOPPED, [frameRec.functionName,
    //                                                frameRec.location]));
    var showHeader = (type != jsdIExecutionHook.TYPE_INTERRUPTED ||
                      console._lastStackDepth != console.frames.length);
    var sourceContext = (type == jsdIExecutionHook.TYPE_INTERRUPTED) ? 0 : 2
        
    displayFrame (jsdFrame, 0, showHeader, sourceContext);    

    var scriptWrapper = getScriptWrapper(jsdFrame.script);
    if (scriptWrapper)
    {
        dispatchCommand (console.coFindScript,
                         { scriptWrapper: scriptWrapper, targetPc: jsdFrame.pc })
    }

    console._lastStackDepth = console.frames.length;

    enableDebugCommands();

    //XXX
    //paintHack();
}

console.hooks["hook-venkman-query-exit"] =
function hookQueryExit (e)
{
    if ("frames" in console)
    {
        if (confirm(MSG_QUERY_CLOSE))
        {
            dispatch ("cont");
            console.__exitAfterContinue__ = true;
        }
        e.returnValue = false;
    }
}

console.hooks["eval"] =
function hookEval()
{
    for (var i = 0; i < $.length; ++i)
        $[i].refresh();
}
    
/* exceptions */

/* keep this list in sync with exceptionMsgNames in venkman-msg.js */
const ERR_NOT_IMPLEMENTED = 0;
const ERR_REQUIRED_PARAM  = 1;
const ERR_INVALID_PARAM   = 2;
const ERR_SUBSCRIPT_LOAD  = 3;
const ERR_NO_DEBUGGER     = 4;
const ERR_FAILURE         = 5;
const ERR_NO_STACK        = 6;

/* venkman exception class */
function BadMojo (errno, params)
{
    var msg = getMsg(exceptionMsgNames[errno], params);
    
    dd ("new BadMojo (" + errno + ": " + msg + ") from\n" + getStackTrace());
    this.message = msg;
    this.errno = errno;
    this.fileName = Components.stack.caller.filename;
    this.lineNumber = Components.stack.caller.lineNumber;
    this.functionName = Components.stack.caller.name;
}

BadMojo.prototype.toString =
function bm_tostring ()
{
    return formatException (this);
}

function Failure (reason)
{
    if (typeof reason == "undefined")
        reason = MSG_ERR_DEFAULT_REASON;

    var obj = new BadMojo(ERR_FAILURE, [reason]);
    obj.fileName = Components.stack.caller.filename;
    obj.lineNumber = Components.stack.caller.lineNumber;
    obj.functionName = Components.stack.caller.name;
    
    return obj;
}

function InvalidParam (name, value)
{
    var obj = new BadMojo(ERR_INVALID_PARAM, [name, String(value)]);
    obj.fileName = Components.stack.caller.filename;
    obj.lineNumber = Components.stack.caller.lineNumber;
    obj.functionName = Components.stack.caller.name;
    return obj;
}

function RequiredParam (name)
{
    var obj = new BadMojo(ERR_REQUIRED_PARAM, name);
    obj.fileName = Components.stack.caller.filename;
    obj.lineNumber = Components.stack.caller.lineNumber;
    obj.functionName = Components.stack.caller.name;
    return obj;
}

/* console object */

console.display = display;
console.dispatch = dispatch;
console.dispatchCommand = dispatchCommand;

console.evalCount = 1;

console.metaDirectives = new Object();

console.metaDirectives["JSD_LOG"] =
console.metaDirectives["JSD_BREAK"] =
console.metaDirectives["JSD_EVAL"] =
function metaBreak (scriptInstance, line, matchResult)
{
    var scriptWrapper = scriptInstance.getScriptWrapperAtLine(line);
    if (!scriptWrapper)
    {
        display (getMsg(MSN_ERR_NO_FUNCTION, [line, scriptInstance.url]),
                 MT_ERROR);
        return;
    }
                        
    var pc = scriptWrapper.jsdScript.lineToPc(line, PCMAP_SOURCETEXT);
    if (scriptWrapper.getBreakpoint(pc))
        return;

    var sourceLine = scriptWrapper.jsdScript.pcToLine(pc, PCMAP_SOURCETEXT);
    var breakpoint = setFutureBreakpoint (scriptWrapper.jsdScript.fileName,
                                          sourceLine);
    scriptWrapper.setBreakpoint (pc, breakpoint);
    matchResult[2] = stringTrim(matchResult[2]);
    
    switch (matchResult[1])
    {
        case "JSD_LOG":
            breakpoint.conditionEnabled = true;
            breakpoint.condition = "return eval(" + matchResult[2].quote() + ")";
            breakpoint.resultAction = BREAKPOINT_STOPNEVER;
            breakpoint.logResult = true;
            break;

        case "JSD_BREAK":
            if (matchResult[2])
            {
                breakpoint.conditionEnabled = true;
                breakpoint.condition = "return eval(" + matchResult[2].quote() +
                    ")";
                breakpoint.resultAction = BREAKPOINT_STOPTRUE;
            }
            else
            {
                breakpoint.resultAction = BREAKPOINT_STOPALWAYS;
            }
            break;
            
        case "JSD_EVAL":
            breakpoint.conditionEnabled = true;
            breakpoint.condition = "return eval(" + matchResult[2].quote() + ")";
            breakpoint.resultAction = BREAKPOINT_STOPNEVER;
            break;
    }
}

console.__defineGetter__ ("status", con_getstatus);
function con_getstatus ()
{
    return console.ui["status-text"].getAttribute ("label");
}

console.__defineSetter__ ("status", con_setstatus);
function con_setstatus (msg)
{
    var topMsg = console._statusStack[console._statusStack.length - 1];

    if (msg)
    {        
        if ("_statusTimeout" in console)
        {
            clearTimeout (console._statusTimeout);
            delete console._statusTimeout;
        }
        if (msg != topMsg)
        {
            console._statusTimeout = setTimeout (con_setstatus,
                                                 console.prefs["statusDuration"],
                                                 null);
        }
    }
    else
    {
        msg = topMsg;
    }

    console.ui["status-text"].setAttribute ("label", msg);
}

console.__defineGetter__ ("pp_stopLine", con_getppline);
function con_getppline ()
{
    if (!("frames" in console))
        return (void 0);
    
    if (!("_pp_stopLine" in this))
    {
        var frame = getCurrentFrame();
        this._pp_stopLine = frame.script.pcToLine(frame.pc, PCMAP_PRETTYPRINT);
    }
    return this._pp_stopLine;
}

console.pushStatus =
function con_pushstatus (msg)
{
    console._statusStack.push (msg);
    console.status = msg;
}

console.popStatus =
function con_popstatus ()
{
    console._statusStack.pop();
    console.status = console._statusStack[console._statusStack.length - 1];
}

function SourceText (scriptInstance)
{
    this.lines = new Array();
    this.tabWidth = console.prefs["tabWidth"];
    if (scriptInstance instanceof ScriptInstance)
    {
        this.scriptInstance = scriptInstance;
        this.url = scriptInstance.url;
        this.jsdURL = JSD_URL_SCHEME + "source?location=" + 
            encodeURIComponent(this.url) +
            "&instance=" + scriptInstance.sequence;
    }
    else
    {
        /* assume scriptInstance is a string containing the filename */
        this.url = scriptInstance;
        this.jsdURL = JSD_URL_SCHEME + "source?location=" + 
                      encodeURIComponent(this.url);
    }

    this.shortName = abbreviateWord(getFileFromPath (this.url), 30);
}

SourceText.prototype.noteFutureBreakpoint =
function st_notefbreak(line, state)
{
    if (!ASSERT(!("scriptInstance" in this),
                "Don't call noteFutureBreakpoint on a SourceText with a " +
                "scriptInstance, use the scriptManager instead."))
    {
        return;
    }
    
    if (state)
        arrayOrFlag (this.lineMap, line - 1, LINE_FBREAK);
    else
        arrayAndFlag (this.lineMap, line - 1, ~LINE_FBREAK);
}

SourceText.prototype.onMarginClick =
function st_marginclick (e, line)
{
    //dd ("onMarginClick");
    
    if (!("scriptInstance" in this))
    {
        if (getFutureBreakpoint(this.url, line))
        {
            clearFutureBreakpoint(this.url, line);
        }
        else
        {
            setFutureBreakpoint(this.url, line);
            //dispatch ("fbreak", { urlPattern: this.url, lineNumber: line });
        }
    }
    else
    {
        var parentBP = getFutureBreakpoint(this.url, line);
        if (parentBP)
        {
            if (this.scriptInstance.hasBreakpoint(line))
                this.scriptInstance.clearBreakpoint(line);
            else
                clearFutureBreakpoint(this.url, line);
        }
        else
        {
            if (this.scriptInstance.hasBreakpoint(line))
            {
                this.scriptInstance.clearBreakpoint(line);
            }
            else
            {
                parentBP = setFutureBreakpoint(this.url, line);
                this.scriptInstance.setBreakpoint (line, parentBP);
            }
        }
    }
}

SourceText.prototype.isLoaded = false;

SourceText.prototype.reloadSource =
function st_reloadsrc (cb)
{
    function reloadCB (status)
    {
        if (typeof cb == "function")
            cb(status);
    }

    this.isLoaded = false;
    this.lines = new Array();
    delete this.markup;
    delete this.charset;
    this.loadSource(reloadCB);
}

SourceText.prototype.onSourceLoaded =
function st_oncomplete (data, url, status)
{
    //dd ("source loaded " + url + ", " + status);
    
    var sourceText = this;
    
    function callall (status)
    {
        while (sourceText.loadCallbacks.length)
        {
            var cb = sourceText.loadCallbacks.pop();
            cb (status);
        }
        delete sourceText.loadCallbacks;
    };
            
    delete this.isLoading;
    
    if (status != Components.results.NS_OK)
    {
        dd ("loadSource failed with status " + status + ", " + url);
        callall (status);
        return;
    }

    if (!ASSERT(data, "loadSource succeeded but got no data"))
        data = "";

    var matchResult;
    
    // search for xml charset
    if (data.substring(0, 5) == "<?xml" && !("charset" in this))
    {
        var s = data.substring(6, data.indexOf("?>"));
        matchResult = s.match(/encoding\s*=\s*([\"\'])([^\"\'\s]+)\1/i);
        if (matchResult)
            this.charset = matchResult[2];
    }

    // kill control characters, except \t, \r, and \n
    data = data.replace(/[\x00-\x08]|[\x0B\x0C]|[\x0E-\x1F]/g, "?");

    // check for a html style charset declaration
    if (!("charset" in this))
    {
        matchResult =
            data.match(/meta\s+http-equiv.*content-type.*charset\s*=\s*([^\;\"\'\s]+)/i);
        if (matchResult)
            this.charset = matchResult[1];
    }

    // look for an emacs mode line
    matchResult = data.match (/-\*-.*tab-width\:\s*(\d+).*-\*-/);
    if (matchResult)
        this.tabWidth = matchResult[1];

    // replace tabs
    data = data.replace(/\x09/g, leftPadString ("", this.tabWidth, " "));
    
    var ary = data.split(/\r\n|\n|\r/m);        

    if (0)
    {
        for (var i = 0; i < ary.length; ++i)
        {
            /*
             * The replace() strips control characters, we leave the tabs in
             * so we can expand them to a per-file width before actually
             * displaying them.
             */
            ary[i] = ary[i].replace(/[\x00-\x08]|[\x0A-\x1F]/g, "?");
            if (!("charset" in this))
            {
                matchResult = ary[i].match(charsetRE);
                if (matchResult)
                    this.charset = matchResult[1];
            }
        }
    }
    
    this.lines = ary;

    if ("scriptInstance" in this)
    {
        this.scriptInstance.guessFunctionNames(sourceText);
        this.lineMap = this.scriptInstance.lineMap;
    }
    else
    {
        this.lineMap = new Array();
        for (var fbp in console.fbreaks)
        {
            var fbreak = console.fbreaks[fbp];
            if (fbreak.url == this.url)
                arrayOrFlag (this.lineMap, fbreak.lineNumber - 1, LINE_FBREAK);
        }
    }

    this.isLoaded = true;
    dispatch ("hook-source-load-complete",
              { sourceText: sourceText, status: status });
    callall(status);
}


SourceText.prototype.loadSource =
function st_loadsrc (cb)
{
    var sourceText = this;
    
    function onComplete (data, url, status)
    {
        //dd ("loaded " + url + " with status " + status + "\n" + data);
        sourceText.onSourceLoaded (data, url, status);
    };

    if (this.isLoaded)
    {
        /* if we're loaded, callback right now, and return. */
        cb (Components.results.NS_OK);
        return;
    }

    if ("isLoading" in this)
    {
        /* if we're in the process of loading, make a note of the callback, and
         * return. */
        if (typeof cb == "function")
            this.loadCallbacks.push (cb);
        return;
    }
    else
        this.loadCallbacks = new Array();

    if (typeof cb == "function")
        this.loadCallbacks.push (cb);
    this.isLoading = true;    
    
    var ex;
    var src;
    var url = this.url;
    if (url.search (/^javascript:/i) == 0)
    {
        src = url;
        delete this.isLoading;
    }
    else
    {
        try
        {
            loadURLAsync (url, { onComplete: onComplete });
            return;
        }
        catch (ex)
        {
            display (getMsg(MSN_ERR_SOURCE_LOAD_FAILED, [url, ex]), MT_ERROR);
            onComplete (src, url, Components.results.NS_ERROR_FAILURE);
            return;
        }
    }

    onComplete (src, url, Components.results.NS_OK);
}

function PPSourceText (scriptWrapper)
{
    this.scriptWrapper = scriptWrapper;
    this.reloadSource();
    this.shortName = scriptWrapper.functionName;
    this.jsdURL = JSD_URL_SCHEME + "pprint?scriptWrapper=" + scriptWrapper.tag;
}

PPSourceText.prototype.isLoaded = true;

PPSourceText.prototype.reloadSource =
function ppst_reload (cb)
{
    try
    {
        var jsdScript = this.scriptWrapper.jsdScript;
        this.tabWidth = console.prefs["tabWidth"];
        this.url = jsdScript.fileName;
        var lines = String(jsdScript.functionSource).split("\n");
        var lineMap = new Array();
        var len = lines.length;
        for (var i = 0; i < len; ++i)
        {
            if (jsdScript.isLineExecutable(i + 1, PCMAP_PRETTYPRINT))
                lineMap[i] = LINE_BREAKABLE;
        }
        
        for (var b in this.scriptWrapper.breaks)
        {
            var line = jsdScript.pcToLine(this.scriptWrapper.breaks[b].pc,
                                          PCMAP_PRETTYPRINT);
            arrayOrFlag (lineMap, line - 1, LINE_BREAK);
        }
        
        this.lines = lines;
        this.lineMap = lineMap;
        delete this.markup;
        
        if (typeof cb == "function")
            cb (Components.results.NS_OK);
    }
    catch (ex)
    {
        ASSERT(0, "caught exception reloading pretty printed source " + ex);
        if (typeof cb == "function")
            cb (Components.results.NS_ERROR_FAILURE);
        this.lines = [String(MSG_CANT_PPRINT)];
        this.lineMap = [0];
    }           
}

PPSourceText.prototype.onMarginClick =
function ppst_marginclick (e, line)
{
    try
    {
        var jsdScript = this.scriptWrapper.jsdScript;
        var pc = jsdScript.lineToPc(line, PCMAP_PRETTYPRINT);
        if (this.scriptWrapper.hasBreakpoint(pc))
            this.scriptWrapper.clearBreakpoint(pc);
        else
            this.scriptWrapper.setBreakpoint(pc);
    }
    catch (ex)
    {
        ASSERT(0, "onMarginClick caught exception " + ex);
    }
}

function HelpText ()
{
    this.tabWidth = console.prefs["tabWidth"];
    this.jsdURL = this.url = JSD_URL_SCHEME + "help";
    this.shortName = this.url;
    this.reloadSource();
}

HelpText.prototype.isLoaded = true;

HelpText.prototype.reloadSource =
function ht_reload (cb)
{
    var ary = console.commandManager.list();
    var str = "";
    for (var c in ary)
    {
        str += "\n                                        ===\n"
            + ary[c].getDocumentation(formatCommandFlags);
    }
    
    this.lines = str.split("\n");
    if (typeof cb == "function")
        cb (Components.results.NS_OK);
}
