/*!
 * Serializer class
 * Version: 1.0
 * Copyright (c) 2008, Denbel
 */  

// load namespace
Denbel.load( 'util.Serializer' );
   
( function(){
    /**
     * constructor
     * @return void
     */
    Denbel.util.Serializer = function()
    {
        this.reset();
    };

    /**
     * Serializes a variable
     * @param Variant o
     * @param Denbel.util.Serializer obj
     * @return string
     */
    Denbel.util.Serializer.serialize = function( o, obj )
    {
        var self = null;
        
        if( !obj || ( obj.constructor == Function && obj != 'Denbel.util.Serializer' ) )
        {
            self = new Denbel.util.Serializer();
        }
        else
        {
            self = obj;
        }
        
        var serialize = function()
        {
	        if( typeof( o ) == "undefined" || o == null || o.constructor == Function )
	        {
	           self.hv++;
	           self.serializeNull();
	           return;
	        }
	        
	        var className = self.getClassName( o );
	        
	        switch( o.constructor )
	        {
	            case Boolean:
	                self.hv++;
	                self.serializeBoolean( o );
	                break;
	             case Number:
	                self.hv++;
	                self.isInteger( o ) ? self.serializeInteger( o ) : self.serializeDouble( o );
	                break;
	             case String:
	                self.hv++;
	                self.serializeString( o );
	                break;
	             case Date:
	                self.hv++;
	                self.serializeDate( o );
	                break;
	             default:
	                if( className == "Object" || o.constructor == Array )
	                {
	                    var r = self.inHashTable( o );
	                    
	                    if( r )
	                    {
	                        self.serializePointRef( r );
	                    }
	                    else
	                    {
	                        self.ht[self.hv++] = o;
	                        self.serializeArray( o );
	                    }
	                 }
	                 else
	                 {
	                    var r = self.inHashTable( o );
	                    
	                    if( r )
	                    {   
	                        self.hv++;
	                        self.serializeRef( r );
	                     }
	                     else
	                     {
	                        self.ht[self.hv++] = o;
	                        self.serializeObject( o );
	                     }   
	                 }
	                 break;
	         }
         };
         serialize( o );
         return self.sb.join( '' );
    };
    
    /**
     * Unserializes a string
     * @param string s
     * @param Denbel.util.Serializer obj
     * @return Variant
     */
    Denbel.util.Serializer.unserialize = function( s, obj )
    {
        var self = null;
        
        if( !obj || ( obj.constructor == Function && obj != 'Denbel.util.Serializer' ) )
        {
            self = new Denbel.util.Serializer();
        }
        else
        {
            self = obj;
        }

        self.ss = s;
        
        switch( self.ss.charAt( self.p++ ) )
        {
            case 'N': return self.ht[self.hv++] = self.unserializeNull();
            case 'b': return self.ht[self.hv++] = self.unserializeBoolean();
            case 'i': return self.ht[self.hv++] = self.unserializeInteger();
            case 'd': return self.ht[self.hv++] = self.unserializeDouble();
            case 's': return self.ht[self.hv++] = self.unserializeString();
            case 'S': return self.ht[self.hv++] = self.unserializeEscapedString( 2 );
            case 'U': return self.ht[self.hv++] = self.unserializeEscapedString( 4 );
            case 'r': return self.ht[self.hv++] = self.unserializeRef();
            case 'a': return self.unserializeArray();
            case 'O': return self.unserializeObject();
            case 'C': return self.unserializeCustomObject();
            case 'R': return self.unserializeRef();
        }
        
        return false;
    };

    // prototype
    Denbel.util.Serializer.prototype =
    {
        // string builder
        sb: null,
        // hash table
        ht: null,
        // hash value
        hv: null,
        // position
        p: 0,
        // serialized string
        ss: null,
        
        /**
         * Resets internal buffer
         * @return void
         */
        reset: function()
        {
            this.sb = [];
            this.ht = [];
            this.hv = 1;
            this.ss = '';
            this.p  = 0;
        },
        
        /**
         * Gets class name for given variable
         * @param Variant o
         * @return string
         */
        getClassName: function( o )
        {
            if( typeof( o ) == 'undefined' || typeof( o.constructor ) == 'undefined' )
            {
                return '';
            }
            
            var c = o.constructor.toString();
            c = c.substr( 0, c.indexOf( '(' ) ).replace( /(^\s*function\s*)|(\s*$)/ig, '' ); //.utf8Encode();
            return ( ( c == '' ) ? 'Object' : c );
        },
        
        /**
         * Returns a value indicating given variable is an integer or not
         * @param Variant n
         * @return bool
         */
        isInteger: function( n )
        {
            var i;
            var s = n.toString();
            var l = s.length;
            
            if( l > 11 )
            {
                return false;
            }
            
            for( i = ( s.charAt( 0 ) == '-' ) ? 1 : 0; i < l; i++ )
            {
                switch( s.charAt( i ) )
                {
                    case '0':
                    case '1':
                    case '2':
                    case '3':
                    case '4':
                    case '5':
                    case '6':
                    case '7':
                    case '8':
                    case '9': break;
                    default : return false;
                }
            }
            return !( n < -2147483648 || n > 2147483647 );   
         },
         
        /**
         * Checks if given variable is in hashtable
         * @param Variant o
         * @return bool
         */
        inHashTable: function( o )
        {   
            var k;
            
            for( k in this.ht )
            {
                if( this.ht[k] === o )
                {
                    return k;
                }
            }
            return false;
        },
        
        /**
         * Returns a serialized NULL value
         * @return string
         */
        serializeNull: function()
        {
            this.sb[this.p++] = 'N;';
            return 'N;';
        },
        
        /**
         * Serializes a boolean value
         * @param bool b
         * @return string
         */
        serializeBoolean: function( b )
        {
            this.sb[this.p++] = ( b ? 'b:1;' : 'b:0;' );
            return ( b ? 'b:1;' : 'b:0;' );
        },
        
        /**
         * Serializes an Integer value
         * @param int i
         * @return string
         */
        serializeInteger: function( i )
        {
            this.sb[this.p++] = 'i:' + i + ';';
            return 'i:' + i + ';';
        },
        
        /**
         * Serializes a double value
         * @param double d
         * @return string
         */
        serializeDouble: function( d )
        {
            if( isNaN( d ) )
            {
                d = 'NAN';
            }
            else if( d == Number.POSITIVE_INFINITY )
            {
                d = 'INF';
            }
            else if( d == Number.NEGATIVE_INFINITY )
            {
                d = '-INF';
            }
            this.sb[this.p++] = 'd:' + d + ';';
            return 'd:' + d + ';';
        },
        
        /**
         * Serializes a string value
         * @param string s
         * @return string
         */
        serializeString: function( s )
        {
            var utf8 = s; //s.utf8Encode();
            this.sb[this.p++] = 's:' + utf8.length + ':"';
            this.sb[this.p++] = utf8;
            this.sb[this.p++] = '";';
            return 's:' + utf8.length + ':"' + utf8 + '";';
        },
        
        /**
         * Serializes a Date value
         * @param Date dt
         * @return string
         */
        serializeDate: function( dt )
        {
            var sp = ( this.p + 1 );
            
            this.sb[this.p++] = 'O:11:"Date":7:{';
            this.sb[this.p++] = 's:4:"year";';
            this.serializeInteger( dt.getFullYear() );
            this.sb[this.p++] = 's:5:"month";';
            this.serializeInteger( dt.getMonth() + 1 );
            this.sb[this.p++] = 's:3:"day";';
            this.serializeInteger( dt.getDate() );
            this.sb[this.p++] = 's:4:"hour";';
            this.serializeInteger( dt.getHours() );
            this.sb[this.p++] = 's:6:"minute";';
            this.serializeInteger( dt.getMinutes() );
            this.sb[this.p++] = 's:6:"second";';
            this.serializeInteger( dt.getSeconds() );
            this.sb[this.p++] = 's:11:"millisecond";';
            this.serializeInteger( dt.getMilliseconds() );
            this.sb[this.p++] = '}';
            
            var r = '';
            for( var i = sp; i < ( this.p + 1 ); i++ )
            {
                r += this.sb[i];
            }
            return r;
        },
        
        /**
         * Serializes an Array value
         * @param Array a
         * @return string
         */
        serializeArray: function( a )
        {
            var sp = ( this.p + 1 );
            this.sb[this.p++] = 'a:';
            
            var k;
            var lp = this.p;
            
            this.sb[this.p++] = 0;
            this.sb[this.p++] = ':{';
            
            for( k in a )
            {
                if( typeof( a[k] ) != 'function' )
                {
                    this.isInteger( k ) ? this.serializeInteger( k ) : this.serializeString( k );
                    Denbel.util.Serializer.serialize( a[k], this );
                    this.sb[lp]++;
                }
            }
            this.sb[this.p++] = '}';
            
            var r = '';
            for( var i = sp; i < ( this.p + 1 ); i++ )
            {
                r += this.sb[i];
            }
            
            return r;
        },
        
        /**
         * Serializes an object
         * @param object o
         * @return string
         */
        serializeObject: function( o )
        {
            var cn = this.getClassName( o );
            if( cn == '' )
            {
                this.serializeNull();
            }
            else if( typeof( o.serialize ) != 'function' )
            {
                this.sb[this.p++] = 'O:' + cn.length + ':"' + cn + '":';
                var lp = this.p;
                this.sb[this.p++] = 0;
                this.sb[this.p++] = ':{';
                var k;
                if( typeof( o.__sleep ) == 'function' )
                {
                    var a = o.__sleep();
                    for( k in a )
                    {
                        this.serializeString( a[k] );
                        Denbel.util.Serializer.serialize( o[a[k]], this );
                        this.sb[lp]++;
                    }
                }
                else
                {
                    for( k in o )
                    {
                        if( typeof( o[k] ) != 'function' )
                        {
                            self.serializeString( k );
                            Denbel.util.Serializer.serialize( o[k], this );
                            this.sb[lp]++;
                        }
                    }
                }
                this.sb[this.p++] = '}';
            }
            else
            {
                var cs = o.serialize();
                this.sb[this.p++] = 'C:' + cn.length + ':"' + cn + '":' + cs.length + ':{' + cs + '}';
            }
        },
        
        /**
         * Serializes a pointer
         * @param Variant R
         * @return string
         */
        serializePointRef: function( R )
        {
            this.sb[this.p++] = 'R:' + R + ';';
            return 'R:' + R + ';';
        },
        
        /**
         * Serializes a reference
         * @param Variant r
         * @return string
         */
        serializeRef: function( r )
        {
            this.sb[this.p++] = 'r:' + r + ';';
            return 'r:' + r + ';';
        },
        
        /**
         * Unserializes a null value
         * @return null
         */
        unserializeNull: function()
        {
            this.p++;
            return null;
        },
        
        /**
         * Unserializes a boolean value
         * @param string v
         * @return bool
         */
        unserializeBoolean: function()
        {
            this.p++;
            var b = ( this.ss.charAt( this.p++ ) == '1' );
            this.p++;
            return b;
        },
        
        /**
         * Unserializes integer value
         * @return int
         */
        unserializeInteger: function()
        {
            this.p++;
            var i = parseInt( this.ss.substring( this.p, this.p = this.ss.indexOf( ';', this.p ) ) );
            this.p++;
            return i;
        },
        
        /**
         * Unserializes double value
         * @return double
         */
        unserializeDouble: function()
        {
            this.p++;
            var d = this.ss.substring( this.p, this.p = this.ss.indexOf( ';', this.p ) );
            switch( d )
            {
                case 'NAN': d = NaN; break;
                case 'INF': d = Number.POSITIVE_INFINITY; break;
                case '-INF': d = Number.NEGATIVE_INFINITY; break;
                default: d = this.parseFloat( d );
            }
            this.p++;
            return d;
        },
        
        /**
         * Unserializes string value
         * @return string
         */
        unserializeString: function()
        {
            this.p++;
            var l = parseInt( this.ss.substring( this.p, this.p = this.ss.indexOf( ':', this.p ) ) );
            this.p += 2;
            var s = this.ss.substring( this.p, this.p += l ); //.utf8Decode();
            this.p += 2;
            return s;
        },
        
        /**
         * Unserialies an escaped string
         * @param int length
         * @return string
         */
        unserializeEscapedString: function( len )
        {
            this.p++;
            var l = parseInt( this.ss.substring( this.p, this.p = this.ss.indexOf( ':', this.p ) ) );
            this.p += 2;
            var i;
            this.sb = new Array( l );
            
            for( i = 0; i < l; i++ )
            {
                if( ( this.sb[i] = this.ss.charAt( this.p++ ) ) == '\\' )
                {
                    this.sb[i] = String.fromCharCode( parseInt( this.ss.substring( this.p, this.p += len ), 16 ) );
                }
            }
            this.p += 2;   
            return this.sb.join( '' );
        },
        
        /**
         * Unserializes an Array
         * @return Array
         */
        unserializeArray: function()
        {
            this.p++;
            var n = parseInt( this.ss.substring( this.p, this.p = this.ss.indexOf( ':', this.p ) ) );
            this.p += 2;
            var i, k, a = [];
            this.ht[this.hv++] = a;
            for( i = 0; i < n; i++ )
            {
                switch( this.ss.charAt( this.p++ ) )
                {
                    case 'i': k = this.unserializeInteger(); break;
                    case 's': k = this.unserializeString(); break;
                    case 'S': k = this.unserializeEscapedString( 2 ); break;
                    case 'U': k = this.unserializeEscapedString( 4 ); break;
                    default: return false;
                }
                a[k] = Denbel.util.Serializer.unserialize( this.ss, this );
            }
            this.p++;
            return a;
        },

        /**
         * Unserializes a Date value
         * @param string n
         * @return Date
         */
        unserializeDate: function( n )
        {
            var i, k, a = {};
            for( i = 0; i < n; i++ )
            {
                switch( this.ss.charAt( this.p++ ) )
                {
                    case 's': k = this.unserializeString(); break;
                    case 'S': k = this.unserializeEscapedString( 2 ); break;
                    case 'U': k = this.unserializeEscapedString( 4 ); break;
                    default: return false;
                }
                if( this.ss.charAt( this.p++ ) == 'i' )
                {
                    a[k] = this.unserializeInteger();
                }
                else
                {
                    return false;
                }
            }
            this.p++;
            var dt = new Date(
                a.year,
                a.month - 1,
                a.day,
                a.hour,
                a.minute,
                a.second,
                a.millisecond
            );
            this.ht[this.hv++] = dt;
            return dt;
        },
        
        /**
         * Unserializes an object
         * @return object
         */
        unserializeObject: function()
        {
            this.p++;   
            var l = parseInt( this.ss.substring( this.p, this.p = this.ss.indexOf( ':', this.p ) ) );
            this.p += 2;
            var cn = this.ss.substring( this.p, this.p += l );
            this.p += 2;
            var n = parseInt( this.ss.substring( this.p, this.p = this.ss.indexOf( ':', this.p ) ) );   
            this.p += 2;
            if( cn == "Date" )
            {
                return this.unserializeDate( n );
            }
            var i, k, o = this.createObjectOfClass( cn );
            this.ht[this.hv++] = o;
            for( i = 0; i < n; i++ )
            {
                switch( this.ss.charAt( this.p++ ) )
                {
                    case 's': k = this.unserializeString(); break;
                    case 'S': k = this.unserializeEscapedString( 2 ); break;
                    case 'U': k = this.unserializeEscapedString( 4 ); break;
                    default: return false;   
                }
                if( k.charAt( 0 ) == '\0' )
                {
                    k = k.substring( k.indexOf( '\0', 1) + 1, k.length );
                }
                o[k] = Denbel.util.Serializer.unserialize( this.ss, this );
            }
            this.p++;
            if( typeof( o.__wakeup ) == 'function' )
            {
                o.__wakeup();
            }
            return o;
        },
        
        /**
         * Unserializes a custom object
         * @return object
         */
        unserializeCustomObject: function()
        {
            this.p++;
            var l = parseInt( this.ss.substring( this.p, this.p = this.ss.indexOf( ':', this.p ) ) );
            this.p += 2;
            var cn = this.ss.substring( this.p, this.p += l );
            this.p += 2;
            var n = parseInt( this.ss.substring( this.p, this.p = this.ss.indexOf( ':', this.p ) ) );
            this.p += 2;
            var o = this.createObjectOfClass( cn );
            this.ht[this.hv++] = o;
            if( typeof( o.unserialize ) != 'function' )
            {
                this.p += n;
            }
            else
            {
                o.unserialize( this.ss.substring( this.p, this.p += n ) );
            }
            this.p++;   
            return o;   
        },
        
        /**
         * Unserializes a reference
         * @return Variant
         */
        unserializeRef: function()
        {
            this.p++;
            var r = parseInt( this.ss.substring( this.p, this.p = this.ss.indexOf( ';', this.p ) ) );
            this.p++;
            return this.ht[r];
        },
        
        /**
         * Gets an object of a named class
         * @param string cn
         * @param int poslist
         * @param int i
         * @param c
         * @return object
         */
        getObjectOfClass: function( cn, poslist, i, c )
        {
            if( i < poslist.length )
            {
                var pos = poslist[i];
                cn[pos] = c;
                var obj = this.getObjectOfClass( cn, poslist, i + 1, '.' );
                
                if( i + 1 < poslist.length )
                {
                    if( obj == null )
                    {
                        obj = this.getObjectOfClass( cn, poslist, i + 1, '_' );
                    }
                }
                return obj;
            }
            
            var classname = cn.join( '' );
                
            try
            {   
                return freeEval('new ' + classname + '()');   
            }   
            catch( ex )
            {   
                return null;
            }
        },
        
        /**
         * Creates an object of a named class
         * @param string className
         * @return void
         */
	    createObjectOfClass: function( className )
	    {
	        if( eval( 'typeof(' + classname + ') == "function"' ) )
	        {
	            return eval( 'new ' + classname + '()' );
	        }
	        var poslist = [];
	        var pos = classname.indexOf( "_" );
	        
	        while( pos > -1 )
	        {
	            poslist[poslist.length] = pos;
	            pos = classname.indexOf( "_", pos + 1 );
	        }
	        
	        if( poslist.length > 0 )
	        {
	            var cn = classname.split( '' );
	            var obj = this.getObjectOfClass( cn, poslist, 0, '.' );
	            if( obj == null )
	            {
                    obj = this.getObjectOfClass( cn, poslist, 0, '_' );
	            }
	            if( obj != null )
	            {
	                return obj;
	            }
	        }
	        
	        return eval( 'new function ' + classname + '(){};' );
	    },
        
        /**
         * Converts this object to its string representation
         * @return string
         */
        toString: function()
        {
            return 'Denbel.util.Serializer';
        }
    };
}() );
