/**
 * This file defines the initialization and core utility methods needed for the
 * Ajam library.
 *
 * @category   Sprink
 * @package    AjaxMadness
 * @author     Michael White <michael@getsprink.com>
 * @copyright  2009-2010 Michael White
 * @license    http://getsprink.com/ajaxmadness/license/0.1  Ajam License 0.1
 * @version    SVN: $Id: ajam.js 21 2010-04-10 04:36:17Z mwhite $
 * @since      File available since Release 0.0.1
 */

//------  Measuring Stick (80 characters)  ------------------------------------=

// Based on John Resig's class inheritance model: http://ejohn.org/blog/simple-javascript-inheritance/
// Modified slightly to, where possible, more closely mimic the naming conventions of PHP.5
// @todo Add support for each level of inheritance knowing it's true class name via some special property or method.
//       or at the very least, implement the trace method as part of this class so that it knows what's going on?
(function(){
    var __constructing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;

    // The base stdClass implementation (does nothing)
    this.stdClass = function(){};
 
    // Create a new stdClass that inherits from this class
    stdClass.extend = function(prop) {
        var _super = this.prototype;
   
        // Instantiate a base class (but only create the instance,
        // don't run the init constructor)
        //console.log("Turning on constructability.");
        __constructing = true;
        var prototype = new this();
        //console.log("Turning off constructability.");
        __constructing = false;
        
        // Copy the properties over onto the new prototype
        for (var name in prop) {
            // Check if we're overwriting an existing function
            prototype[name] = typeof prop[name] == "function" &&
            typeof _super[name] == "function" && fnTest.test(prop[name]) ?
            (function(name, fn){
                return function() {
                    var tmp = this._super;
                    
                    // Add a new ._super() method that is the same method
                    // but on the super-class
                    this._super = _super[name];
               
                    // The method only needs to be bound temporarily, so we
                    // remove it when we're done executing
                    var ret = fn.apply(this, arguments);       
                    this._super = tmp;
    
                    return ret;
                };
            })(name, prop[name]) :
            prop[name];
        }
        
        // The dummy class constructor
        function stdClass() {
            // All construction is actually done in the init method
            if (!__constructing && this.__construct) {
                this.__construct.apply(this, arguments);
            }
        }
        
        // Populate our constructed prototype object
        stdClass.prototype = prototype;
        
        // Enforce the constructor to be what we expect
        stdClass.constructor = stdClass;
        
        // And make this class extendable
        stdClass.extend = arguments.callee;
        
        return stdClass;
    };
})();

window.AjamUtil = {
    _version: '0.0.1'
    ,_className: 'AjamUtil'
    ,_requiredPrototypeVersion: '1.6.1'
    ,_requiredScriptaculousVersion: '1.8.3'
    ,_requiredJqueryVersion: '1.4.1'
    ,_externalLibrary: ''
    ,_defaultExternalLibrary: 'jquery'
    ,_externalLibraryLoaded: false
    ,_baseUrl: ''
    ,config: {
        logErrors: false
        ,debug: false
        ,log: false
        ,trace: false
    }
    /* Behaves like the __FILE__ magic constant in PHP. */
    ,__FILE__: function()
    {
        var tag = document.getElementsByTagName('script');
        var url = tag[tag.length-1];
        if ('src' in url && url.src != '') {
            return url.src;
        } else {
            return window.location.href;
        }
    }
    /* Behaves like the __DIR__ magic constant in PHP. */
    ,__DIR__: function()
    {
        var path = this.__FILE__();
        return path.replace(/\\/g, '/').replace(/\/[^\/]*\/?$/, '');
    }
    ,getUrl: function()
    {
        return this._baseUrl;
    }
    ,getRandomString: function(length) {
        var chars = "ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
        var stringLength = length ? length : 8;
        var randomString = '';
        for (var i=0; i < stringLength; i++) {
            var rNum = Math.floor(Math.random() * chars.length);
            randomString += chars.substring(rNum, rNum+1);
        }
        return randomString;
    }
    /* Straigt out of the MSDN website
     * http://msdn.microsoft.com/en-us/library/ms535874%28VS.85%29.aspx
     */
    ,getXmlHttpRequestObject: function() 
    {
        if (window.XMLHttpRequest) {
            return new window.XMLHttpRequest;
        }
        else {
            try {
                return new ActiveXObject('MSXML2.XMLHTTP.3.0');
            }
            catch(ex) {
                return null;
            }
        }
    }
    ,ajax: function(options)
    {
        this.trace('ajax', arguments, true);
        
        var options = this.extend({
            xhr: null
            ,expectJson: false
            ,method: 'get'
            ,url: ''
            ,async: true
            ,user: false
            ,password: false
            ,headers: null
            ,data: null
            ,rawData: undefined
            ,onResponse: false
            ,onSuccess: false
            ,onError: false
            ,onComplete: false
        }, options);

        if(typeof(options.rawData) == 'undefined') {
            options.rawData = options.data;
        }

        if (!options.xhr) {
            options.xhr = this.getXmlHttpRequestObject();
        }
        
        if (typeof(options.data) != 'string') {
            //options.data = JSON.stringify(options.data);
            options.data = this.buildQueryString(options.data);
        }
        
        //Set the headers for POST request.
        if (options.method == 'post') {
            options.headers['Content-type'] = 'application/x-www-form-urlencoded';
            options.headers['Content-length'] = options.data.length;
            options.headers['Connection'] = 'close';
        } else {
            var parsedUrl = this.parseUrl(options.url);
            if(options.data && parsedUrl.query) {
                options.data = parsedUrl.query + '&' + options.data;
            }
            if(options.data) {
                options.data = '?' + options.data;
            }
            options.url = parsedUrl.path + options.data;
            options.data = null; // Send null data for GET requests.
        }
        
        // Build open() method arguments list from options.
        var requestOpenArgs = [];
        // Make sure that the method is set to a valid string value of either get or post
        if (!options.method || typeof(options.method) != 'string' || (options.method.toLowerCase() != 'post' && options.method.toLowerCase() != 'get')) {
            requestOpenArgs[0] = 'get';
        } else {
            requestOpenArgs[0] = options.method;
        }
        
        if (!options.url || typeof(options.url) != 'string') {
            throw('Invalid URL for Ajax request.');
        } else {
            requestOpenArgs[1] = options.url;
        }
        
        // Let the request default to an asynchronous request unless explicitly set to null, 0, or false.
        if (typeof(options.async) === 'undefined' || options.async) {
            requestOpenArgs[2] = true;
        } else {
            requestOpenArgs[2] = false;
        }
        
        if (options.user && typeof(options.user) == 'string') {
            requestOpenArgs[3] = options.user;
        }
        
        if (options.password && typeof(options.password) == 'string') {
            requestOpenArgs[4] = options.password;
        }
        
        //options.xhr.open.apply(options.xhr, requestOpenArgs);
        options.xhr.open(requestOpenArgs[0], requestOpenArgs[1], requestOpenArgs[2], requestOpenArgs[3], requestOpenArgs[4]);
        
        
        // Response, success, error, and complete callbacks can be specified.
        // Each callback can be either a function or an object with a few specific properties.

        // onResponse
        if (this.isFunction(options.onResponse)) {
            options.onResponse = {
                callback: options.onResponse
                ,scope: this
                ,args: []
            };
        } else if (
            typeof(options.onResponse) === 'object'
            && options.onResponse
            && this.isFunction(options.onResponse.callback)) {
            //&& typeof(options.onResponse.callback) !== 'undefined'
            options.onResponse = this.extend({
                scope: this
                ,args: []
            }, options.onResponse);
        } else {
            options.onResponse = null;
        }
        
        // onSuccess
        if (this.isFunction(options.onSuccess)) {
            options.onSuccess = {
                callback: options.onSuccess
                ,scope: this
                ,args: []
            };
        } else if (
            typeof(options.onSuccess) === 'object'
            && options.onSuccess
            && this.isFunction(options.onSuccess.callback)) {
            //&& typeof(options.onResponse.callback) !== 'undefined'
            options.onSuccess = this.extend({
                scope: this
                ,args: []
            }, options.onSuccess);
        } else {
            options.onSuccess = null;
        }
        
        // onError
        if (this.isFunction(options.onError)) {
            options.onError = {
                callback: options.onError
                ,scope: this
                ,args: []
            };
        } else if (
            typeof(options.onError) === 'object'
            && options.onError
            && this.isFunction(options.onError.callback)) {
            //&& typeof(options.onResponse.callback) !== 'undefined'
            options.onError = this.extend({
                scope: this
                ,args: []
            }, options.onError);
        } else {
            options.onError = null;
        }
        
        // onComplete
        if (this.isFunction(options.onComplete)) {
            options.onComplete = {
                callback: options.onComplete
                ,scope: this
                ,args: []
            };
        } else if (
            typeof(options.onComplete) === 'object'
            && options.onComplete
            && this.isFunction(options.onComplete.callback)) {
            //&& typeof(options.onResponse.callback) !== 'undefined'
            options.onComplete = this.extend({
                scope: this
                ,args: []
            }, options.onComplete);
        } else {
            options.onComplete = null;
        }
        
        // `this` inside the onreadstatechange function will point to the `options` object.
        options.xhr.onreadystatechange = this.bind(function() {
            // check readyState property for completed request.
            if (this.xhr.readyState == 4) {
                AjamUtil.trace('ajax.onreadystatechange', arguments, true);

                try {
                    var response = {
                        text: this.xhr.responseText
                        ,xml: this.xhr.responseXML
                        ,json: null
                        ,status: this.xhr.status
                        ,statusText: this.xhr.statusText
                    };
                } catch(e) {
                    throw 'AjamUtil.ajax() - XMLHTTPRequest to URL: `' + this.url + '` failed. Ensure that this is not a disallowed cross-domain request (current window.location.href='+window.location.href + ')';
                }

                // If we are expecting JSON data back from the server, then make sure we populate the JSON parameter.
                if (this.expectJson) {
                	try {
                		response.json = JSON.parse(response.text);
                	} catch (exception) {
                		response.json = false;
                	}
                }

                // Call the onResponse function.
                if(this.onResponse && typeof(this.onResponse) === 'object') {
                    //AjamUtil.log('onResponse');
                    response = this.onResponse.callback.apply(
                        this.onResponse.scope
                        ,AjamUtil.prepend(this.onResponse.args, response, this, this.xhr)
                    );
                }
                
                // Check for request status.
                if (this.xhr.status == 200 || this.xhr.status == 304) {
                    // Call the onSuccess function.
                    if(this.onSuccess && typeof(this.onSuccess) === 'object') {
                        //AjamUtil.log('onSuccess');
                        response = this.onSuccess.callback.apply(
                            this.onSuccess.scope
                            ,AjamUtil.prepend(this.onSuccess.args, response, this, this.xhr)
                        );
                    }
                } else if (this.onError && typeof(this.onError) === 'object') {
                    //AjamUtil.log('onError');
                    // Call the onError function.
                    response = this.onError.callback.apply(
                        this.onError.scope
                        ,AjamUtil.prepend(this.onError.args, response, this, this.xhr)
                    );
                }
                
                // Call the onComplete function.
                if(this.onComplete && typeof(this.onComplete) === 'object') {
                    //AjamUtil.log('onComplete');
                    this.onComplete.callback.apply(
                        this.onComplete.scope
                        ,AjamUtil.prepend(this.onComplete.args, response, this, this.xhr)
                    );
                }
            }
        }, options);
        
        if (typeof(options.headers) == 'object') {
            try {
                for (var headerName in options.headers) {
                    options.xhr.setRequestHeader(headerName, options.headers[headerName]);
                }
            } catch (ex) {
                throw('Invalid headers object.');
            }
        }

        options.xhr.send(options.data);

        return options;
    }
    ,error: function(message)
    {
        if (this.config.log === true
            && typeof(window.console) !== 'undefined'
            && window.console != null) {
            console.error(message);
            return true; // logged
        } else {
            return false; // not logged
        }
    }
    ,debug: function(message)
    {
        if (this.config.log === true
            && typeof(window.console) !== 'undefined'
            && window.console != null) {
            console.info(message);
            return true; // logged
        } else {
            return false; // not logged
        }
    }
    ,log: function(message)
    {
        if (this.config.log === true
            && typeof(window.console) !== 'undefined'
            && window.console != null) {
            console.log(message);
            return true; // logged
        } else {
            return false; // not logged
        }
    }
    ,trace: function()
    {
        if (this.config.trace === true
            && typeof(window.console) !== 'undefined'
            && window.console != null) {
            var concatOperator = '.';
            switch (arguments.length) {
                case 1:
                    // arguments[0] = functionName (name of function that is being traced)
                    console.log(this._className + concatOperator + arguments[0]);
                    break;
                case 2:
                    /*
                     * @param arguments[0] functionName: name of function that is being traced
                     * @param arguments[1] arguments:    array of arguments originally passed to functionName
                     */
                    console.log(
                        this._className + concatOperator + arguments[0]
                        + '(numArgs:' + arguments[1].length + ')'
                    );
                    break;
                case 3:
                    /* Two different possibilities here. If last argument is boolean then we
                     * can assume that they intend it to be the isStaticCall parameter. Otherwise
                     * we assume it is the arguments array and that the first parameter is the className.
                     *
                     * Scenario #1 - Class name is assumed to be this class.
                     * @param arguments[0] functionName: the name of the function being traced.
                     * @param arguments[1] arguments:    the array of arguments originally passed
                     * @param arguments[2] isStaticCall: whether or not this is a static method call.
                     *
                     * OR
                     *
                     * Scenario #2
                     * @param arguments[0] className:    the name of the class being traced.
                     * @param arguments[1] functionName: the name of the method being traced.
                     * @param arguments[2] arguments:    the array of arguments originally passed
                     */
                    if (typeof(arguments[2]) === 'boolean') {
                       // Scenario #1
                       if (arguments[2] === true) {
                           concatOperator = '#';
                       }
                       console.log(
                           this._className + concatOperator + arguments[0]
                           + '(numArgs:' + arguments[1].length + ')'
                       );
                    } else if (typeof(arguments[2]) === 'object') {
                       // Scenario #2
                       console.log(
                           arguments[0] + concatOperator + arguments[1]
                           + '(numArgs:' + arguments[2].length + ')'
                       );
                    } else {
                       console.log(arguments[0] + concatOperator + arguments[1] + '(argsNotProvided)');
                    }
                    break;
                case 4:
                    /*
                     * @param arguments[0] className
                     * @param arguments[1] functionName
                     * @param arguments[2] arguments
                     * @param arguments[3] isStaticCall
                     */
                    if (arguments[3] === true) {
                        concatOperator = '#';
                    }
                    if(arguments[2] && typeof(arguments[2]) === 'object') {
                        console.log(
                            arguments[0] + concatOperator + arguments[1]
                            + '(numArgs:' + arguments[2].length + ')'
                        );
                    } else {
                        console.log(arguments[0] + concatOperator + arguments[1] + '(argsNotProvided)');
                    }
                    break;
            }
            return true; // logged
        } else {
            return false; // not logged
        }
    }
    ,__preInit: function()
    {
        this._baseUrl = this.__DIR__();

        // If no console is present then automatically shut off logging.
        if (typeof(window.console) === 'undefined' && window.console != null) {
            this.config.log = false;
            this.config.trace = false;
            this.config.logErrors = false;
            this.config.debug = false;
        }
    }
    ,includeCss: function(url)
    {
        try{
            // inserting via DOM fails in Safari 2.0, so brute force approach
            document.write('<link rel="stylesheet" type="text/css" href="' + url + '" />');
        } catch(e) {
            // for xhtml+xml served content, fall back to DOM methods
            var link = document.createElement('link');
            link.rel = 'stylesheet';
            link.type = 'text/css';
            link.href = url;
            document.getElementsByTagName('head')[0].appendChild(link);
        }
    }
    /*
     * Borrowed straight from Scriptaculous.
     */
    ,includeJs: function(url)
    {
        try{
            // inserting via DOM fails in Safari 2.0, so brute force approach
            document.write('<script type="text/javascript" src="' + url + '"><\/script>');
        } catch(e) {
            // for xhtml+xml served content, fall back to DOM methods
            var script = document.createElement('script');
            script.type = 'text/javascript';
            script.src = url;
            document.getElementsByTagName('head')[0].appendChild(script);
        }
    }
    ,loadExt: function(extensionName)
    {
        this.includeJs(
            this._baseUrl + '/ext/' + this._externalLibrary
            + '/' + extensionName.toLowerCase() + '/plugin.js'
        );
    }
    ,loadExtension: function(extensionName)
    {
        return this.loadExt(extensionName);
    }
    ,init: function(externalLibrary)
    {
        /*if (typeof(allowAutoload) === 'undefined') {
            allowAutoload = false;
        }*/

        function times(string, repeat) {
            var output = '';
            for (var i=0; i < repeat; i++) {
                output += string;
            }
            return output;
        }
        
        // Snippet borrowed directly from Scriptaculous
        function convertVersionString(versionString) {
            var v = versionString.replace(/_.*|\./g, '');
            v = parseInt(v + times('0', 4-v.length));
            return versionString.indexOf('_') > -1 ? v-1 : v;
        }
        
        var isLoaded = false;

        // If the user already has a specific library loaded then use it.
        if (!externalLibrary) {
            if (typeof(jQuery) !== 'undefined' && convertVersionString(jQuery.fn.jquery) >= convertVersionString(this._requiredJqueryVersion)) {
                externalLibrary = 'jquery';
                isLoaded = true;
            } else if ((typeof(Prototype) !== 'undefined' && convertVersionString(Prototype.Version) >= convertVersionString(this._requiredPrototypeVersion)) && (typeof(Scriptaculous) !== 'undefined' && convertVersionString(Scriptaculous.Version) >= convertVersionString(this._requiredScriptaculousVersion))) {
                externalLibrary = 'prototype';
                isLoaded = true;
            } else {
                this.error('AjaxMadness requires (Prototype 1.6.1+ && Scriptaculous 1.8.3+) || (jQuery 1.4.1+) to be included prior to loading.');
                return;
            }
        }

        switch (externalLibrary.toLowerCase()) {
            case 'jquery':
                this._externalLibrary = 'jquery';
                break;
            case 'prototype':
                this._externalLibrary = 'prototype';
                break;
        }
        
        /*if (!isLoaded && allowAutoload) {
            this.log('Autloading ' + this._externalLibrary);
            switch (this._externalLibrary) {
                case 'prototype':
                    this.includeJs(this._baseUrl + '/externals/prototype-1.6.1.js');
                    this.includeJs(this._baseUrl + '/externals/scriptaculous-1.8.3/scriptaculous.js');
                    break;
                case 'jquery':
                    this.includeCss(this._baseUrl + '/externals/jquery-ui-1.7.2/css/cupertino/jquery-ui.css');
                    this.includeJs(this._baseUrl + '/externals/jquery-1.4.1.js');
                    this.includeJs(this._baseUrl + '/externals/jquery-ui-1.7.2/jquery-ui.js');
                    break;
            }
        }*/
        
        //this.includeJs(this._baseUrl + '/utils/ClassMaker.js');
        this.includeJs(this._baseUrl + '/core/ajam-' + this._externalLibrary + '.js');
    }
    ,getArgumentNames: function(obj)
    {
        var names = obj.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(',');
        for (var i=0; i < names.length; i++) {
            names[i] = this.strip(names[i]);
        }
        return names.length == 1 && !names[0] ? [] : names;
    }
    ,strip: function(str)
    {
        return str.replace(/^\s+/, '').replace(/\s+$/, '');
    }
    ,prepend: function(obj)
    {
        if(typeof(obj) === 'object' || typeof(obj) === 'array') {
        	if(typeof(obj.reverse) == 'function') {
        		obj.reverse();
        	}
            for (var tl=arguments.length-1, i=tl; 0 < i; i--) {
                obj[obj.length] = arguments[i];
            }
            if(typeof(obj.reverse) == 'function') {
        		obj.reverse();
        	}
        }
        return obj;
    }
    /* Ok, binding is really really simple. It works by accepting a function to
     * bind and returning an encapsulated version of that function that, when
     * run, uses apply() to propoate the correct scope to the bound function.
     *
     * Added the boundArgs argument to contain an array or object with arguments
     * need to be used if the function is called in such as way that arguments
     * cannot normally be passed in.
     */
    ,bind: function(fn, scope, boundArgs)
    {
        /* Create and return an anonymous function that encapsulates the bound
         * function and acts like a proxy to maintain scope and pass through
         * arguments without altering them. */
        return function() {
            /* The "arguments" variable contains an array of arguments passed
             * to this anonymous function. It is then used to pass those
             * directly on to the bound function. */
            var args = arguments;
             /* If boundArgs is an object, use it as a complete override for the arguments */
            if(typeof(boundArgs) === 'object') {
                args = boundArgs;
            }
            return fn.apply(scope, args);
        };
    }
    ,clone: function(obj) {
        var newObj = {}, value = null;
        for (var key in obj) {
            vType = typeof(obj[key]);
            if (vType === 'string' || vType === 'number' || vType === 'boolean') {
                newObj[key] = obj[key]; // Copy scalar value.
            } else if (vType !== 'null' && vType !== 'function' && vType !== 'undefined') {
                newObj[key] = this.clone(obj[key]); // Recurse on object/array
            }
        }
        return newObj;
    }
    ,parseUrl: function (str, component) {
        /* Parse a URL and return its components
         * version: 903.3016
         * discuss at: http://phpjs.org/functions/parse_url
         *  note: Does not replace invaild characters with '_' as in PHP, nor does it return false with
         *      a seriously malformed URL.
         *  note: Besides function name, is the same as parseUri besides the commented out portion
         *      and the additional section following, as well as our allowing an extra slash after
         *      the scheme/protocol (to allow file:/// as in PHP)
         *  example 1: parse_url('http://username:password@hostname/path?arg=value#anchor');
         *  returns 1: {scheme: 'http', host: 'hostname', user: 'username', pass: 'password', path: '/path', query: 'arg=value', fragment: 'anchor'}
         */
        var  o   = {
            strictMode: false,
            key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
            q:   {
                name:   "queryKey",
                parser: /(?:^|&)([^&=]*)=?([^&]*)/g
            },
            parser: {
                strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
                loose:  /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/\/?)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ // Added one optional slash to post-protocol to catch file:/// (should restrict this)
            }
        };

        var m   = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
        uri = {},
        i   = 14;
        while (i--) uri[o.key[i]] = m[i] || "";
        // Uncomment the following to use the original more detailed (non-PHP) script
        /**/
            uri[o.q.name] = {};
            uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
            if ($1) uri[o.q.name][$1] = $2;
            });
            return uri;
        /**/

        switch (component) {
            case "PHP_URL_SCHEME":
                return uri.protocol;
            case "PHP_URL_HOST":
                return uri.host;
            case "PHP_URL_PORT":
                return uri.port;
            case "PHP_URL_USER":
                return uri.user;
            case "PHP_URL_PASS":
                return uri.password;
            case "PHP_URL_PATH":
                return uri.path;
            case "PHP_URL_QUERY":
                return uri.query;
            case "PHP_URL_FRAGMENT":
                return uri.anchor;
            default:
                var retArr = {};
                if (uri.protocol !== '') retArr.scheme=uri.protocol;
                if (uri.host !== '') retArr.host=uri.host;
                if (uri.port !== '') retArr.port=uri.port;
                if (uri.user !== '') retArr.user=uri.user;
                if (uri.password !== '') retArr.pass=uri.password;
                if (uri.path !== '') retArr.path=uri.path;
                if (uri.query !== '') retArr.query=uri.query;
                if (uri.anchor !== '') retArr.fragment=uri.anchor;
                return retArr;
        }
    }
    /*
     * The following methods are borrowed directly from the jQuery library.
     *
     * 
     */
    ,isFunction: function(obj) {
        try {
        return (typeof(obj) === 'function' || toString.call(obj) === "[object Function]");
        } catch (e) {
            return false;
        }
    }
    ,isArray: function(obj) {
        try {
        return ((obj instanceof Array) || (toString.call(obj) === "[object Array]"));
        } catch(e) {
            return false;
        }
    }
    ,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
        try {
        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 );
        } catch(e) {
            return false;
        }
    }
    ,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" && !this.isFunction(target) ) {
            target = {};
        }
    
        // If only one argument is passed in, then return that object unmodified.
        if ( length === i ) {
            return target;
        }
    
        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 && ( this.isPlainObject(copy) || this.isArray(copy) ) ) {
                        var clone = src && ( this.isPlainObject(src) || this.isArray(src) ) ? src
                            : this.isArray(copy) ? [] : {};
    
                        // Never move original objects, clone them
                        target[ name ] = this.extend( deep, clone, copy );
    
                    // Don't bring in undefined values
                    } else if ( copy !== undefined ) {
                        target[ name ] = copy;
                    }
                }
            }
        }
    
        // Return the modified object
        return target;

    }
    ,buildQueryString: function(formdata, numeric_prefix, arg_separator) {
        var key, use_val, use_key, i = 0, tmp_arr = [];

        if(!arg_separator){
            arg_separator = '&';
        }
        for(key in formdata) {
            if(typeof(formdata[key]) != 'undefined' && formdata[key] != null) {
                use_key = encodeURIComponent(key);
                use_val = encodeURIComponent((formdata[key].toString()));
                use_val = use_val.replace(/%20/g, '+');

                if(numeric_prefix && !isNaN(key)) {
                    use_key = numeric_prefix + i;
                }
                tmp_arr[i] = use_key + '=' + use_val;
                i++;
            } else {
                // @mmi-log MMI.log('**--**--**--**--**--** NULL OR UNDEFINED FORMDATA VALUE FOR KEY: ' + key);
            }
        }
        return tmp_arr.join(arg_separator);
    }
    ,toQueryString: function(o) {
        var s = [], value = null, type = null;
        
        for(var key in o) {
            value = o[key];
            type = typeof(value);
            
            if(type === 'string' || type === 'integer') {
            
            } else if (type === 'boolean') {
                
            } else if (type === 'object') {
                // Recurse and apply any special formatting.
            }
        }
        
        
        
        /*var s = [];
        
        if (typeof(o) === 'object') {
            for(var key in o) {
                add(key, o);
            }
        }
        function add(key, value) {
            // If value is a function, invoke it and return its value
            value = AjamUtil.isFunction(value) ? value() : value;
            
            if (typeof(value) === 'object') {
                for(var i in value) {
                    add(i, value[i]);
                }
            } else {
                s[s.length] = encodeURIComponent(key) + '=' + encodeURIComponent(value);
            }
        }
        return s.join('&').replace(/%20/g, '+');*/
    }
};

// Sets the base directory for this javascript file.
// Allows the Ajam library to reference resources relative to the directory
// where Ajam is stored.
AjamUtil.__preInit();



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


/**
 * Implements JSON stringify and parse functions
 * v1.0
 *
 * By Craig Buckler, Optimalworks.net
 *
 * As featured on SitePoint.com
 * Please use as you wish at your own risk.
*
 * Usage:
 *
 * // serialize a JavaScript object to a JSON string
 * var str = JSON.stringify(object);
 *
 * // de-serialize a JSON string to a JavaScript object
 * var obj = JSON.parse(str);
 */

var JSON = JSON || {};

// implement JSON.stringify serialization
JSON.stringify = JSON.stringify || function (obj) {

    var t = typeof (obj);
    if (t != "object" || obj === null) {

        // simple data type
        if (t == "string") obj = '"'+obj+'"';
        return String(obj);

    }
    else {

        // recurse array or object
        var n, v, json = [], arr = (obj && obj.constructor == Array);

        for (n in obj) {
            v = obj[n]; t = typeof(v);

            if (t == "string") v = '"'+v+'"';
            else if (t == "object" && v !== null) v = JSON.stringify(v);

            json.push((arr ? "" : '"' + n + '":') + String(v));
        }

        return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}");
    }
};


// implement JSON.parse de-serialization
JSON.parse = JSON.parse || function (str) {
    if (str === "") str = '""';
    eval("var p=" + str + ";");
    return p;
};
