/******************************************************************************
    Introspect.js
    Copyright (c) 2006-2008 Motorola, Inc.
    All rights reserved
******************************************************************************/
$package("netopia/introspect.html");

//--------------------------------

netopia.core.add({

compare : function (a, b)
{
    if (!(a instanceof Array))
    {
        if (!(b instanceof Array))
        {
            if (isFinite(a) && isFinite(b))
                return a - b;

            a += "";
            b += "";
            return (a < b) ? -1 : ((a == b) ? 0 : 1);
        }

        a = [a];
    }
    else if (!(b instanceof Array))
        b = [b];

    var alen = a.length, blen = b.length, len = Math.min(alen, blen);

    for (var i = 0; i < len; ++i)
    {
        var r = arguments.callee(a[i], b[i]);
        if (r != 0)
            return r;
    }

    return (alen < blen) ? -1 : ((alen == blen) ? 0 : 1);
},

/*
Returns the call stack as an string array. Item[0] is the caller of getCallStack,
[1] is the caller of that caller, etc..
*/
getCallStack : function ()
{
    var ret = [ ];
    var recursive;

    for (var f = arguments.callee.caller; f != null; f = f.caller)
    {
        recursive = (netopia.core.findMember(ret, f) !== undefined);
        if (! f.kHideStackFrame)
            ret[ret.length] = f;
        if (recursive)
            break;
    }

    var strictJson = netopia.core.strictJson;
    netopia.core.strictJson = false;

    for (var i = 0; i < ret.length; ++i)
        ret[i] = ret[i].getName() + 
                    "(" + netopia.core.stringifyArray(ret[i].arguments) + ")";

    netopia.core.strictJson = strictJson;

    return ret;
},

/*
    Type        | typeof    | getType
    ------------+-----------+-------------------------------
    Boolean     | boolean   | boolean
    Function    | function  | function
    Number      | number    | number
    String      | string    | string
    [no object] | undefined | undefined
    Arguments   | object    | arguments
    Array       | object    | array
    Date        | object    | date
    Error       | object    | error
    Math        | object    | math
    Null        | object    | null
    Object      | object    | object
    RegExp      | object    | regex
    [custom]    | object    | ctor function name
*/
getType : function ()
{
    Boolean.prototype.kClassName = "boolean";
    Date.prototype.kClassName    = "date";
    Number.prototype.kClassName  = "number";
    RegExp.prototype.kClassName  = "regex";
    String.prototype.kClassName  = "string";

    Error.prototype.kClassName   = "error";
    EvalError.prototype.kClassName   = "error:eval";
    RangeError.prototype.kClassName   = "error:range";
    ReferenceError.prototype.kClassName   = "error:ref";
    SyntaxError.prototype.kClassName   = "error:syntax";
    TypeError.prototype.kClassName   = "error:type";
    URIError.prototype.kClassName   = "error:uri";

    // technique came from http://www.webreference.com/dhtml/column68/4.html
    var re = { object : /^\[object.*\]$/i,
               ctor   : /\s*function (.*)\(/,
               type   : /^\[object (.*)\]$/i  };

    return function (v)
    {
        if (v === undefined)
            return "undefined";
        if (v === null)
            return "null";
        if (v.kClassName)
            return v.kClassName;
        if (v.constructor && v.constructor.kClassName)
            return v.constructor.kClassName;

        var s, m, tv = typeof v;

        if (tv == "function")
        {
            s = v.toString();
            if (re.object.test(s))
                tv = "object";
        }
        if (tv != "object")
            return tv;

        switch (v.constructor)
        {
         case null: case undefined: break;
         case Array: tv = "array"; break;
         case Object: tv = "object"; break;
         default:
            s = v.constructor.toString();
            if (null != (m = s.match(re.ctor)))
                return m[1];
        }

        if (v.toString)
        {
            s = v.toString();
            if (null != (m = s.match(re.type)))
            {
                s = m[1].toLowerCase();
                switch (s)
                {
                 case "event": return "event";
                 case "math": return "math";
                }
            }
        }

        if (v.callee)
            return "arguments";

        return tv;
    };
}(),

hasCallStack : function ()
{
    function test () { return arguments.callee.caller != null; }
    return test();
},

identRegEx : /^[$A-Za-z_][$A-Z0-9a-z_]*$/,

/**
Controls strict encoding of JSON format. Primarily whether property names
are always quoted or can be unquoted if they are valid identifiers.
*/
strictJson : true,

stringifyObject : function (obj, quote)
{
    if (obj === undefined)
        return "undefined";
    if (obj == null)
        return "null";
    if (obj.stringify)
        return obj.stringify(quote);
    if (obj instanceof Function)
        return obj.getName();
    if (obj instanceof Array)
        return "[" + netopia.core.stringifyArray(obj) + "]";
    for (var m in obj)  // detect an object (is there a better way???)
        return "{" + netopia.core.stringifyMembers(obj) + "}";

    return obj + "";
},

stringifyArray : function (obj)
{
    var r = [ ];

    function push (s)
    {
        if (r.length > 0)
            r.push(",");
        r.push(s);
    }

    var n = obj.length;
    for (var i = 0; i < n; ++i)
        netopia.core.stringifyValue(obj[i], push);

    return r.join("");
},

stringifyValue : function (v, push)
{
    switch (typeof(v))
    {
     case "undefined": case "function": case "unknown":
        break;
     case "object":
     default:
        push(netopia.core.stringifyObject(v, true));
        break;
    }
},

stringifyMembers : function (obj)
{
    if (obj instanceof Array)
        return netopia.core.stringifyArray(obj);

    var m, r = [ ];

    function push (s)
    {
        if (r.length > 0)
            r.push(",");
        var n = m+"";
        if (netopia.core.strictJson || !netopia.core.identRegEx.test(n))
            n = netopia.core.stringifyObject(m, true);
        r.push(n, ":", s);
    }

    for (m in obj)
        netopia.core.stringifyValue(obj[m], push);

    return r.join("");
}

}); // netopia.core

//---------------------------------------------

Boolean.prototype.stringify = function (quote)
{
    return this+"";
}

Date.prototype.stringify = function (quote)
{
    function f(n) {
        return n < 10 ? '0' + n : n;
    }

    var s = this.getFullYear() + '-' + f(this.getMonth() + 1) + '-' + f(this.getDate()) +
      ' ' + f(this.getHours()) + ':' + f(this.getMinutes()) + ':' + f(this.getSeconds());

    return quote ? ('"' + s + '"') : s;
}

Number.prototype.stringify = function (quote)
{
    return isFinite(this) ? (this+"") : "null";
}

String.prototype.stringify = function (quote)
{
    var obj = this + "";
    var m = { '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r',
              '"' : '\\"', '\\': '\\\\' };

    if (/["\\\x00-\x1f]/.test(obj))
    {
        return '"' + obj.replace(/([\x00-\x1f\\"])/g, function(a, b)
        {
            var c = m[b];
            if (c) {
                return c;
            }
            c = b.charCodeAt();
            return '\\u00' +
                Math.floor(c / 16).toString(16) +
                (c % 16).toString(16);
        }) + '"';
    }

    return quote ? ('"' + obj + '"') : obj;
}

