/**
 * @author vpavlyuk
 *
 */


//Global namespace
(function() {
    
    var toString = Object.prototype.toString,
        hasOwnProperty = Object.prototype.hasOwnProperty;
    
    SF = {
        
        isFunction: function( obj ) {
            return toString.call(obj) === "[object Function]";
        },
        
        isPlainObject: function( obj ) {
            // Must be an Object.
            // Because of IE, we also have to check the presence of the constructor property.
            // Make sure that DOM nodes and window objects don't pass through, as well
            if ( !obj || toString.call(obj) !== "[object Object]" || obj.nodeType || obj.setInterval ) {
                return false;
            }
            
            // Not own constructor property must be Object
            if ( obj.constructor
                && !hasOwnProperty.call(obj, "constructor")
                && !hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf") ) {
                return false;
            }
            
            // Own properties are enumerated firstly, so to speed up,
            // if last one is own, then all properties are own.
        
            var key;
            for ( key in obj ) {}
            
            return key === undefined || hasOwnProperty.call( obj, key );
        },
        
        isArray: function( obj ) {
            return toString.call(obj) === "[object Array]";
        },
        
        
        /**
         * Extend object with preperties from another one(s)
         * #
         *  SF.extend( target, [ object1 ], [ objectN ] )
         *  target - An object that will receive the new properties if additional objects are passed in or that will extend the jQuery namespace if it is the sole argument.
         *  object1 - An object containing additional properties to merge in.
         *  objectN - Additional objects containing properties to merge in.
         *  
         *  SF.extend( [ deep ], target, object1, [ objectN ] )
         *  deep - If true, the merge becomes recursive (aka. deep copy).
         *  target - The object to extend. It will receive the new properties.
         *  object1 - An object containing additional properties to merge in.
         *  objectN - Additional objects containing properties to merge in.
         */
        extend: function() {
            
            // copy reference to target object
            var target = arguments[0] || {}, 
                i = 1, 
                length = arguments.length, 
                deep = false, 
                options, 
                name, 
                src, 
                copy;
        
            // Handle a deep copy situation
            if ( typeof target === "boolean" ) {
                deep = target;
                target = arguments[1] || {};
                // skip the boolean and the target
                i = 2;
            }
        
            // Handle case when target is a string or something (possible in deep copy)
            if ( typeof target !== "object" && !SF.isFunction(target) ) {
                target = {};
            }
        
            // extend jQuery itself if only one argument is passed
            if ( length === i ) {
                target = this;
                --i;
            }
        
            for ( ; i < length; i++ ) {
                // Only deal with non-null/undefined values
                if ( (options = arguments[ i ]) != null ) {
                    // Extend the base object
                    for ( name in options ) {
                        src = target[ name ];
                        copy = options[ name ];
        
                        // Prevent never-ending loop
                        if ( target === copy ) {
                            continue;
                        }
        
                        // Recurse if we're merging object literal values or arrays
                        if ( deep && copy && ( SF.isPlainObject(copy) || SF.isArray(copy) ) ) {
                            var clone = src && ( SF.isPlainObject(src) || SF.isArray(src) ) ? src
                                : SF.isArray(copy) ? [] : {};
        
                            // Never move original objects, clone them
                            target[ name ] = SF.extend( deep, clone, copy );
        
                        // Don't bring in undefined values
                        } else if ( copy !== undefined ) {
                            target[ name ] = copy;
                        }
                    }
                }
            }
        
            // Return the modified object
            return target;
        }    
    };
})();


SF.extend(function() {

    /**
     * Array of functions which are called on page load
     */
    var initFunctions = [];


    return {
        /**
         * Add function to startup execution list
         * @param {Object} f
         */
        addToDOMReady: function(f, pos) {
            if(typeof f === 'function') {
                if(pos == 'first') {
                    initFunctions.unshift(f);
                } else {
                    initFunctions.push(f);
                }
            }
        },

        /**
         * This function executes each function from startup execution list
         */
        bootstrap: function() {
            for(var i = 0; i < initFunctions.length; i++) {
                //Let sandbox code? so error in one place doesn't brake good code
                try {
                    initFunctions[i].call();
                } catch(exception) {
                    //Let implement this in future
                }
            }
        },
        
        /**
         * @param {String}          ns        Namespace to declare
         * @param {Function/Object} method    If provided namespace is extended with the result of this method
         * @return {Object}                   Namespace
         */
        ns: function(ns, extend) {
            var chain = [],
                parent = SF;
            
            if (typeof ns === 'string') {
                chain = ns.split('.');
            } else if(typeof ns === 'function') {
                extend = ns;
            }
            
            
            for(var i = 0; i < chain.length; i ++) {
                if(!parent.hasOwnProperty(chain[i])) {
                    parent[chain[i]] = {};
                }
                parent = parent[chain[i]];
            }
            
            if(extend) {
                var r = {};
                var t = typeof extend;
                
                if(t == 'function') {
                    r = extend();
                    r = (typeof r == 'object') ? r : {};
                } else if(t == 'object') {
                    r = extend;
                }
                SF.extend(true, parent, r);
            }
            
            return parent;
        }
    }
}());

/**
 * General utils
 */
SF.ns('util', function() {
    
    return {
        /**
         * Merges Nth arrays into one array. If first argument is set to true, 
         * then resulting array doesn't contain duplicates.
         * 
         * SF.util.array_merge(array1, array2[, array3, ..., arrayN]);
         * SF.util.array_merge(unique, array1, array2[, array3, ..., arrayN]);
         * 
         */
        array_merge: function() {
            var result = [],
                a,
                temp;
            if(arguments[0] === true) {
                
                temp = Array.prototype.concat.apply([], Array.prototype.slice.call(arguments, 1));
                
                for(var i = 0, len = temp.length; i < len; i ++) {
                    if($.inArray(temp[i], result) < 0) {
                        result.push(temp[i]);
                    }
                }
            } else {
                result = Array.prototype.concat.apply([], arguments);
            }
            
            return result;
        },
        
        
        /**
         * If some garbage is appended/prepended to JSON responce it is trimmed
         * 
         * @param String str - response text 
         * @param String format -  
         */
        trimToJSON: function (str) {
        	// If it's already an object, there's no junk, just return it.
        	if(typeof(str)==='object'){
        		return str
        	}
        	
            var result = null,
                open,
                close;
            
            if(str.length > 0) {
                open = str.indexOf('{');
                close = str.lastIndexOf('}');
                
                if(open > -1 && close > open) {
                    result = str.substring(open, close + 1);
                    try {
                        result = jQuery.parseJSON(result);
                    } catch(e) {
                        result = null;
                    }
                } 
            }
            return result;
        }
    }
    
});



/**
 * Custom events implementation
 */
SF.extend(function(){
    
    var events = {};
    
    return {
        /**
         * Subscribe to event
         * 
         * @param {String} type
         * @param {Function} fn
         * @param {Object} context
         */
        on: function(type, fn, context) {
            events[type] = events[type] || [];
            events[type].push({
                action: fn,
                context: context || null
            });
        },
        
        /**
         * Fire custom event
         * 
         * @param {String} type
         */
        fire: function(type) {
            var e,
                args = Array.prototype.slice.call(arguments, 1);
            if(events.hasOwnProperty(type)) {
                for(var i = 0, len = events[type].length; i < len; i++) {
                    e = events[type][i];
                    if(SF.isFunction(e.action)) {
                        e.action.apply(e.context, args);
                    }
                }
            }
        },
        
        /**
         * Unsubscribe function from listening of specified event 
         * 
         * @param {String} type
         * @param {Function} fn
         */
        ubsubscribe: function(type, fn) {
            if(events.hasOwnProperty(type)) {
                for (var i = 0, len = events[type].length; i < len; i++) {
                    if(events[type][i].action == fn) {
                        //TODO: remove array element
                        events[type].splice(i, 1);
                    }
                }
            }
        }
    }
    
}());

