/**
 * jsDump
 * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
 * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php)
 * Date: 5/15/2008
 * @projectDescription Advanced and extensible data dumping for Javascript.
 * @version 1.0.0
 * @author Ariel Flesler
 * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
 */
var jsDump;

(function(){
    function quote( str ){
        return '"' + str.toString().replace(/"/g, '\\"') + '"';
    };
    function literal( o ){
        return o + '';
    };
    function join( pre, arr, post ){
        var s = jsDump.separator(),
        base = jsDump.indent();
        inner = jsDump.indent(1);
        if( arr.join )
            arr = arr.join( ',' + s + inner );
        if( !arr )
            return pre + post;
        return [ pre, inner + arr, base + post ].join(s);
    };
    function array( arr ){
        var i = arr.length,	ret = Array(i);
        this.up();
        while( i-- )
            ret[i] = this.parse( arr[i] );
        this.down();
        return join( '[', ret, ']' );
    };

    var reName = /^function (\w+)/;

    jsDump = {
        parse:function( obj, type ){//type is used mostly internally, you can fix a (custom)type in advance
            var	parser = this.parsers[ type || this.typeOf(obj) ];
            type = typeof parser;

            return type == 'function' ? parser.call( this, obj ) :
            type == 'string' ? parser :
            this.parsers.error;
        },
        typeOf:function( obj ){
            var type = typeof obj,
            f = 'function';//we'll use it 3 times, save it
            return type != 'object' && type != f ? type :
            !obj ? 'null' :
            obj.exec ? 'regexp' :// some browsers (FF) consider regexps functions
            obj.getHours ? 'date' :
            obj.scrollBy ?  'window' :
            obj.nodeName == '#document' ? 'document' :
            obj.nodeName ? 'node' :
            obj.item ? 'nodelist' : // Safari reports nodelists as functions
            obj.callee ? 'arguments' :
            obj.call || obj.constructor != Array && //an array would also fall on this hack
            (obj+'').indexOf(f) != -1 ? f : //IE reports functions like alert, as objects
            'length' in obj ? 'array' :
            type;
        },
        separator:function(){
            return this.multiline ?	this.HTML ? '<br />' : '\n' : this.HTML ? '&nbsp;' : ' ';
        },
        indent:function( extra ){// extra can be a number, shortcut for increasing-calling-decreasing
            if( !this.multiline )
                return '';
            var chr = this.indentChar;
            if( this.HTML )
                chr = chr.replace(/\t/g,'   ').replace(/ /g,'&nbsp;');
            return Array( this._depth_ + (extra||0) ).join(chr);
        },
        up:function( a ){
            this._depth_ += a || 1;
        },
        down:function( a ){
            this._depth_ -= a || 1;
        },
        setParser:function( name, parser ){
            this.parsers[name] = parser;
        },
        // The next 3 are exposed so you can use them
        quote:quote,
        literal:literal,
        join:join,
        //
        _depth_: 1,
        // This is the list of parsers, to modify them, use jsDump.setParser
        parsers:{
            window: '[Window]',
            document: '[Document]',
            error:'[ERROR]', //when no parser is found, shouldn't happen
            unknown: '[Unknown]',
            'null':'null',
            undefined:'undefined',
            'function':function( fn ){
                var ret = 'function',
                name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
                if( name )
                    ret += ' ' + name;
                ret += '(';

                ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join('');
                return join( ret, this.parse(fn,'functionCode'), '}' );
            },
            array: array,
            nodelist: array,
            arguments: array,
            object:function( map ){
                var ret = [ ];
                this.up();
                for( var key in map )
                    ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) );
                this.down();
                return join( '{', ret, '}' );
            },
            node:function( node ){
                var open = this.HTML ? '&lt;' : '<',
                close = this.HTML ? '&gt;' : '>';

                var tag = node.nodeName.toLowerCase(),
                ret = open + tag;

                for( var a in this.DOMAttrs ){
                    var val = node[this.DOMAttrs[a]];
                    if( val )
                        ret += ' ' + a + '=' + this.parse( val, 'attribute' );
                }
                return ret + close + open + '/' + tag + close;
            },
            functionArgs:function( fn ){//function calls it internally, it's the arguments part of the function
                var l = fn.length;
                if( !l ) return '';

                var args = Array(l);
                while( l-- )
                    args[l] = String.fromCharCode(97+l);//97 is 'a'
                return ' ' + args.join(', ') + ' ';
            },
            key:quote, //object calls it internally, the key part of an item in a map
            functionCode:'[code]', //function calls it internally, it's the content of the function
            attribute:quote, //node calls it internally, it's an html attribute value
            string:quote,
            date:quote,
            regexp:literal, //regex
            number:literal,
            'boolean':literal
        },
        DOMAttrs:{//attributes to dump from nodes, name=>realName
            id:'id',
            name:'name',
            'class':'className'
        },
        HTML:false,//if true, entities are escaped ( <, >, \t, space and \n )
        indentChar:'   ',//indentation unit
        multiline:true //if true, items in a collection, are separated by a \n, else just a space.
    };

})();