/*

created by Starcode team
licensed under GPL v.3

mailto: info@starcode.ru
www   : starcode.ru
rtfm  : blog.starcode.ru/js-obj-template/

*/

window.$$ = {

    _static : function( value ){
        return $$._initProperty( value, 2 );
    },

    _public : function ( value ) {
        return $$._initProperty( value, 1 );
    },
    
    _private : function ( value ) {
        return $$._initProperty( value, 0 );
    },
    
    _protected : function( value ){    
        return $$._initProperty( value, -1 );      
    },          
    
    _default : function( value ){
        value.$value.$default = 1;
        return value;
    },
    
    _constructor : function( _initFunction ){
        return {
            $value  : _initFunction,
            $public : 2,    
            $type   : 'constructor'
        };
    },
    
    _superConstructor : function( _initFunction ){
        return {
            $value  : _initFunction,
            $public : 2,    
            $type   : 'superconstructor'
        };    
    },     
    
    _initProperty : function( value, _public ){
        return {
            $value  : ( value && value.$value )? value.$value : value,
            $public : _public,
            $type   : ( value && value.$type )? value.$type : ( typeof value === 'function' ) ? 'function' : 'variable'
        };         
    },    
    
    _each : function( object, callback ){
        var name, i = 0, length = object.length;
			if ( length === undefined ) {
				for ( name in object ) {
					if ( callback.call( object[ name ], name, object[ name ] ) === false ) break;
                }
			} else {
				for ( var value = object[0]; i < length && callback.call( value, i, value ) !== false; value = object[++i] ){}
            }
        return object;
    },
    
    _extend : function( target, source){
        $$._each( source, function( propertyName, property ){
            target[ propertyName ] = property;
        });
        return target;
    },
    
    _clone : function( _object ) {
        if( (! _object ) || ( typeof _object !== 'object' ) )  return _object;
        var clone = ( typeof _object.pop  === 'function' ) ? [] : {};
        $$._each( _object, function( pName, pValue){ 
            var value      = pValue;
            clone[ pName ] = ( value && ( typeof value === 'object' ) ) ? $$._clone( value ) : value;       
        });
        return clone;
    },    
    
    _template : function( _class){
    
        var handler = {
            privateProp       : {},
            functions         : {},
            childrenClasses   : {},
            constructors      : {},
            publicProp        : {}
        };  
        
        var Class = $$._rewriteClass( $$._addHandler( _class, {}, handler ) );
        
        Class._extend = function( _extendObj ){
            Class = $$._template( $$._extend( _class, _extendObj ) );
            return $$._template( $$._extend( _class, _extendObj ) );
        }
        Class._sourceTpl = _class;
        Class.$value     = Class;
        Class.$public    = 1;
        Class.$type      = 'class';       
        return Class;
    },  

    _addHandler : function( sourceClass, targetClass, handler ){
        $$._each( sourceClass, function( propertyName, property ){
            var type    = property.$type,
                value   = property.$value,
                _public = property.$public;
                
            if ( ( value == undefined  ) && handler.childrenClasses[ propertyName ]){
                property.$value = undefined;            
                handler.childrenClasses[ propertyName ] = undefined;
                targetClass[ propertyName ] = undefined; 
                return;
            }
            if ( type == 'class' ) {
                handler.childrenClasses[ propertyName ] = value;
                handler.childrenClasses[ propertyName ].templateName = propertyName;
            }
            
            if ( _public <= 0  ){
                    handler.privateProp[ propertyName ] = property;
            } else if ( _public == 2){
                if ( ( type == 'constructor' ) || ( type == 'superconstructor' ) ) {
                    value.$super = ( type == 'superconstructor' )? 1 : 0;
                    handler.constructors[ propertyName ] = value;
                } else {
                    handler.publicProp[ propertyName ] = property;
                }
            } else {
                handler.publicProp[ propertyName ] = property;
            }
            targetClass[ propertyName ] = ( _public == 2 ) ? value : undefined; 
            if ( type == 'function' ) handler.functions[ propertyName ] = property;
        }); 
        return handler;
    },
    
    _decorateFunction : function(_function, decorator) {
        var newFunc = decorator;
        newFunc.old = (! _function.old )? _function : _function.old;
        return newFunc;
    },  
    
    _rewriteProperty : function( property, propertyName, objForParent, objForConstructor, objForReturn, objForStatic){
        var val = property.$value;
        if ( property.$type != 'function' ){
            objForParent[ propertyName ] = function ( arg ){
                if ( val == undefined ) return val;
                if ( arg ){
                    objForConstructor[ propertyName ] = arg;
                } else {
                    return objForConstructor[ propertyName ];
                }
            }
        } else {
            objForParent[ propertyName ] = val;
            objForParent[ propertyName ].set = function( _func ){
                _func = $$._rewriteFunction( _func, _func.$public, objForConstructor,  objForStatic);                       
                _func.set = this.set;
                objForParent[ propertyName ] = objForConstructor[ propertyName ] = objForReturn[ propertyName ] = _func;
            }
        }  
    },    
    
    _rewriteFunction : function( _func, _public, objForConstructor, objForStatic ){
        _func = $$._decorateFunction( _func, function( ){
            var self = ( _public == 2 )? objForStatic : objForConstructor;
            return _func.old.apply( self, arguments );    
        });
        return _func;
    },    

    _rewriteClass  : function( handler ){  
    
        var tplObj                 = {},
            defaultConstructorName = false,
            objForStatic           = {}; 
        
        $$._each( handler.publicProp, function( propertyName, property ){ 
            if ( property.$public == 2 ) {
                var value = property.$value;
                objForStatic[ propertyName ] = value;
                if ( property.$type != 'function' ){
                    tplObj[ propertyName ] = function ( arg ){
                        if ( value == undefined ) return value;
                        if ( arg ){
                            objForStatic[ propertyName ] = arg;
                        } else {
                            return objForStatic[ propertyName ];
                        }
                    }
                } else {
                    tplObj[ propertyName ] = value;
                }                  
            }
        });
                
        $$._each( handler.constructors, function( constructorName, _constructor) {
            var __constructor = _constructor;
            if ( _constructor.$default && ( defaultConstructorName == false) ) defaultConstructorName = constructorName;   
            _constructor = $$._decorateFunction( __constructor, function( ){
            
                var objForConstructor      = {}, // доступны static, public, protected, private
                    objForParent           = {}, // доступны static, public, protected 
                    objForReturn           = {}; // доступны static, public
                    
                $$._each( handler.functions, function( functionName, _function ){
                    _function.$value = $$._rewriteFunction( _function.$value, _function.$public, objForConstructor, objForStatic )
                });
                   
                $$._each( handler.publicProp, function( propertyName, property ){ 
                    objForConstructor[ propertyName ] = property.$value;
                    $$._rewriteProperty( property, propertyName, objForParent, objForConstructor, objForReturn, objForStatic);
                    objForReturn[ propertyName ]  = objForParent[ propertyName ];
                });    
                
                $$._each( handler.privateProp, function( propertyName, property ){ 
                    if ( property.$public == -1 ) $$._rewriteProperty( property, propertyName, objForParent, objForConstructor, objForReturn, objForStatic);   
                    objForConstructor[ propertyName ] =  property.$value;
                });  

                $$._each( handler.childrenClasses, function( childClassName, childClass ){
                    objForConstructor[ childClassName ].__parent = objForParent;  
                });
        
                var self   = this,
                    parent = self.__parent || arguments.callee.__parent || undefined;
                if ( __constructor.$super ){
                    $$._each( handler.childrenClasses, function( childClassName, childClass ){
                        if ( childClass[ constructorName ]) {
                            objForConstructor[ childClassName ] = childClass[ constructorName ]( arguments );
                        } else if ( childClass.$defaultConstructor ){
                            objForConstructor[ childClassName ] = childClass.$defaultConstructor( arguments );
                        }
                    }); 
                }
                objForConstructor.parent = objForParent.parent = parent;
                _constructor.old.apply( objForConstructor, arguments );    
                return objForReturn;            
            });
            
            tplObj[ constructorName ] = _constructor;

        });
            
        if ( defaultConstructorName ) {
            var _Class = tplObj;
            tplObj =_Class[ defaultConstructorName ];
            tplObj.$defaultConstructor = tplObj;
            $$._extend( tplObj, _Class );
        }
        return tplObj;

    }
    
};