/**
 * Library for AJAX.
 *
 * @package     AJAX
 * @author      Svec Jiri <jiri.svec@livesport.cz>
 * @copyright   (c) 2008 Livesport, s.r.o.; {@link http://www.livesport.cz/}
 * @since       2006
 */

/**
 * Constrructor of XHR object.
 * 
 * @constructor
 */
function XHR()
{
    // init the object
    this._init();
    
    // create AJAX
    this.createAJAX()
};

/**
 * Create the XHR object and set up it.
 * 
 * @return {XHR}   Returns a fluent interface.
 */
XHR.prototype.createAJAX = function()
{
    try {
        this.xhr = new ActiveXObject('Msxml2.XMLHTTP');
    } catch (e) {
        try {
            this.xhr = new ActiveXObject('Microsoft.XMLHTTP');
        } catch (e) {
            this.xhr = null;
        }
    }
    
    if (! this.xhr) {
        if (typeof XMLHttpRequest != 'undefined') {
            this.xhr = new XMLHttpRequest();
        } else {
            this.failed = true;
        }
    }
    
    return this;
};

/**
 * Set a variable to append URL.
 * 
 * @param  {String} name    Name of variable.
 * @param  {Mixed}  value   Variable value.
 * @return {XHR}   Returns a fluent interface.
 */
XHR.prototype.setVar = function(name, value) {
    this.vars[name] = new Array(value, false);
    
    return this;
};

/**
 * Run the AJAX request.
 * 
 * @param  {String}  ajaxContent   Content for AJAX request.
 * @param  {Boolean} xml           Send as XML (optional, default is false)?
 * @return {XHR}   Returns a fluent interface.
 */
XHR.prototype.runAjax = function(ajaxContent, xml)
{
    if (this.failed) {
        this.onFail();
    } else {
        xml = (cJsLib.empty(xml) || xml == false) ? false : true;
        
        // check if content type is not a XML string
        (!xml) ? this._createUrl(ajaxContent) : this.urlString = ajaxContent;

        if (this.element) {
            this.elementObj = document.getElementById(this.element);
        }
        
        if (this.xhr) {
            var self = this;
            
            if (this.method == 'GET') {
                completeUrlString = this.requestFile + this.queryStringSeparator + this.urlString;
                this.xhr.open(this.method, completeUrlString, true);
            } else {
                this.xhr.open(this.method, this.requestFile, true);
                try {
                    this.xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
                } catch (e) {}
            }
            
            this.xhr.onreadystatechange = function() {
                switch (self.xhr.readyState) {
                    
                    // not catchable in Safari, Konqueror, Opera
                    case 1:
                        self.onLoading();
                        break;
    
                    case 2:
                        self.onLoaded();
                        break;
    
                    case 3:
                        self.onInteractive();
                        break;
    
                    case 4:
                        self.response          = self.xhr.responseText;
                        self.responseXML       = self.xhr.responseXML;
                        self.responseStatus[0] = self.xhr.status;
                        self.responseStatus[1] = self.xhr.statusText;

                        // execute user defined function?
                        if (self.execute) {
                        	self.runAfterResponse();
                        }
                        
                        // append AJAX response to defined element
                        if (self.elementObj) {
                            elemNodeName = self.elementObj.nodeName;
                            nodeName     = elemNodeName.toLowerCase();
                            
                            if (nodeName == 'input' || nodeName == 'select' || nodeName == 'option' || nodeName == 'textarea') {
                                self.elementObj.value = self.response;
                            } else {
                                self.elementObj.innerHTML = self.response;
                            }
                        }
                        
                        if (self.responseStatus[0] == '200') {
                            self.onCompletion(self.responseXML, self.response);
                        } else {
                            self.onError();
                        }
                        
                        self.userRedirect();
                        
                        self.urlString = '';
                        break;
                }
            };
            
            this.xhr.send(this.urlString);
        }
    }
    
    return this;
};

/**
 * Adjust overlay layer to a new layer.
 * 
 * @return {XHR}   Returns a fluent interface.
 */
XHR.prototype.placeOverlay = function()
{
    if (cJsLib.$('overlay') == null) {
        pDimension = cJsLib.getDocSize();
        args       = new Object();
        args.id    = 'overlay';
        args.style = 'visibility:hidden;';
        mOverlay   = cJsLib.createElement('div', args); 
        document.body.appendChild(mOverlay);
        cJsLib.setStyle(mOverlay, 'width:' + pDimension.width.toString() + 'px;height:' + pDimension.height.toString() + 'px;');
        
        delete mOverlay, pDimension;        
    }
    
    return this;
};

/**
 * Adjust overlay message container with AJAX pre-loader
 * 
 * @param  {Integer} ajaxIndex        Index of AJAX object which is parent for closing action.
 * @param  {String}  preloadMessage   Message for pre-loader.
 * @return {XHR}   Returns a fluent interface.
 */
XHR.prototype.placePreLoader = function(ajaxIndex, preloadMessage)
{
    if (!cJsLib.isNull(ajaxIndex) && cJsLib.$('overlay-container') == null) {
        // main container for message
        args      = new Object();
        args.id   = 'overlay-container';
        container = cJsLib.createElement('div', args);
        
        // message container for AJAX results
        args           = new Object();
        args.id        = 'overlay-message';
        overlayMessage = cJsLib.createElement('div', args);
        
        // AJAX preloader
        args      = new Object();
        args.id   = 'preloader';
        preLoader = cJsLib.createElement('span', args);
        preLoader.appendChild(document.createTextNode(preloadMessage));
        overlayMessage.appendChild(preLoader);
        
        // overlay message closer
        args         = new Object();
        args.id      = 'overlay-closer';
        args.onclick = 'ajaxObject[' + ajaxIndex + '].destroyOverlay();';
        args.title   = this.TXT_CLOSE_POPUP_WINDOW;
        closer = cJsLib.createElement('a', args);
        
        container.appendChild(overlayMessage);
        container.appendChild(closer);
        
        container.style.visibility = 'hidden';
        document.body.appendChild(container);
        cJsLib.center(container);
        container.style.visibility = 'visible';
        
        delete container, overlayMessage, overlayMessage, preLoader;
    }
    
    return this;
};

/**
 * Clear the AJAX pre-loader image and replace with empty DIV.
 * 
 * @return {XHR}   Returns a fluent interface.
 */
XHR.prototype.clearAjaxPreLoader = function()
{
    var toReplace  = cJsLib.$('overlay-message');
    var pElement   = toReplace.parentNode;
    var newReplace = document.createElement('div');
    newReplace.setAttribute('id', 'overlay-message');
    newReplace.appendChild(document.createTextNode(''));
           
    pElement.replaceChild(newReplace, toReplace);
    
    return this;
};

/**
 * Destroy the overlay layer.
 * 
 * @return {XHR}   Returns a fluent interface.
 */
XHR.prototype.destroyOverlay = function()
{
    cJsLib.destroyObj('overlay-container');
    cJsLib.destroyObj('overlay');
    
    return this;
};

/**
 * Set a new AJAX request type method.
 * 
 * @param  {GET|POST} methodType   A new value of request method
 * @return {XHR}   Returns a fluent interface.
 */
XHR.prototype.setMethod = function(methodType)
{
    this.method = ! cJsLib.empty(methodType) && methodType.toUpperCase() == 'POST' ? 'POST' : 'GET';
    
    return this;
};

/**
 * Get a method type of AJAX request.
 * 
 * @return {POST|GET}
 */
XHR.prototype.getMethod = function()
{
    return this.method;
};

/**
 * Set a ID of element for showing AJAX result.
 * 
 * @param  {String} elem   ID of element.
 * @return {XHR}   Returns a fluent interface.
 */
XHR.prototype.setElement = function(elem)
{
	if (! cJsLib.empty(elem)) {
		this.element = elem;
	}
    
    return this;
};

/**
 * Set a request filename for processing AJAX script on server.
 * 
 * @param  {String} fileName   Filename.
 * @return {XHR}   Returns a fluent interface.
 */
XHR.prototype.setRequestFile = function(fileName)
{
	if (! cJsLib.empty(fileName)) {
		this.requestFile = fileName;
	}
    
    return this;
};

/**
 * Set a DOM ID of element for showing basic pre-loader.
 * 
 * @param  {String}  id   DOM ID of element for showing basic pre-loader.
 * @param  {Boolean} useAbsPosition   OPTIONAL - Use absolute position <i>(default: true)</i>.
 * @return {XHR}   Returns a fluent interface.
 */
XHR.prototype.setBasicPreLoaderId = function(id, useAbsPosition)
{
    if (! cJsLib.empty(id) && cJsLib.isObject(cJsLib.$(id))) {
        // store source element 
        this.basicPreLoaderId     = cJsLib.$(id);
        this.basicPreLoaderParent = this.basicPreLoaderId.parentNode;
        useAbsPosition = ! cJsLib.isNull(useAbsPosition) && ! useAbsPosition ? false : true;
       
        var pPos  = cJsLib.getElementPosition(this.basicPreLoaderId, useAbsPosition);
        
        // create the pre loader icon
        var pLoad = cJsLib.createElement('span', {'id': 'ajaxPreLoader'});
        cJsLib.setCls(pLoad, 'basic');
        cJsLib.setStyle(pLoad, 'left:' + pPos.left + 'px;top:' + pPos.top + 'px;');
        pLoad.appendChild(document.createTextNode(' '));
        
        this.basicPreLoaderParent.replaceChild(pLoad, this.basicPreLoaderId);
    }
    
    return this;
};

/**
 * Restore DOM element on which was replaced by AJAX preloader.
 *  
 * @return {XHR}   Returns a fluent interface.
 */
XHR.prototype.restoreBasicPreLoader = function()
{
    if (this.basicPreLoaderId != null) {
        var pElem = this.basicPreLoaderId.parentNode;
        this.basicPreLoaderParent.replaceChild(this.basicPreLoaderId, cJsLib.$('ajaxPreLoader'));
    }
    
    return this;
};

/**
 * Enable / disable execute function `runAfterResponse()` after successfuly XHR request.
 * 
 * <u>Note:</u> Default setting is after successfully response execute function `runAfterResponse()`
 *              which call function `clearAjaxPreLoader()`.
 * 
 * @param  {Boolean} stat   A new flag value.
 * @return {XHR}   Returns a fluent interface.
 */
XHR.prototype.setExecuteAfterRun = function(stat)
{
    this.execute = ! cJsLib.isNull(stat) || stat === true ? true : false;
    
    return this;
};

/**
 * Reset object (return all properties to default value.
 * 
 * @return {XHR}   Returns a fluent interface.
 */
XHR.prototype.reset = function()
{
    this._resetFunctions;
    
    return this;
};

// Private methods {{{

/**
 * Initize the AJAX object.
 * 
 * @return {XHR}   Returns a fluent interface.
 */
XHR.prototype._init = function()
{
    // set a translated messages
    if (! cJsLib.empty(jsMsgXHR)) {
        var tmpStr = null;
        
        for (var i = 0; i < jsMsgXHR.length; ++i) {
            tmpStr = jsMsgXHR[i].split('|');
            
            if (tmpStr.length == 2) {
                eval('this.' + tmpStr[0] + " = '" + tmpStr[1] + "';");
            }
        }
    }
    
    // sending method of request
    this.method = 'POST';
    
    // URI separator
    this.queryStringSeparator = '?';
    
    // separator for arguments in URL
    this.argumentSeparator = '&';
    
    // URL
    this.urlString = '';
    
    // enable / disable encoding of URI strigns 
    this.encodeUri = true;
    
    // requested AJAX script on server
    this.requestFile = '';
    
    // after AJAX finished execute function `runResponse`?
    this.execute = true;
    
    // ID of element for showing AJAX result
    // class may display result to defined element via method `innerHTML`
    // or setting value (for input, select, option, textarea)
    this.element = null;
    
    // DOM object of element for showing AJAX result
    this.elementObj = null;
    
    // array with URL parameters
    this.vars = new Object();
    
    // array with AJAX response code and response message 
    this.responseStatus = new Array(2);
    
    // use overlayed DIV above all content
    this.useOverlay = false;
    
    // URL for user redirection
    this.userRedirectUrl = null;
    
    // element ID for showing preloader without overlay
    this.basicPreLoaderId     = null;
    this.basicPreLoaderParent = null;
    
    // reset all functions
    this._resetFunctions();
    
    return this;
};

/**
 * Reset all function - set to default.
 */
XHR.prototype._resetFunctions = function()
{
    // AJAX readyState with value 1, is not catchable in Opera (9.25, 9.5b), Konqueror, Safari  
    this.onLoading = function() {};
    
    // on slow internet connestion is better place (show) overlay layer with a AJAX pre-loader
    // run manually after execute the action which initize the AJAX, i.e. after click
    // to button for example
    if (this.useOverlay == true) {
        // if enabled using overlay, show overlayed layer with the AJAX result message container
        this.onLoaded = function() { this.placeOverlay(); };
       
        // after AJAX finished, remove a pre-loader and clean the AJAX result message container
        this.runAfterResponse = function() { this.clearAjaxPreLoader(); };
    } else {
        this.onLoaded         = function() {};
        this.runAfterResponse = function() {};
    }
    
    // funtion for redirecting after AJAX finishing
    this.userRedirect  = function() {};
    
    this.onInteractive = function() {};
    this.onCompletion  = function() {};
    this.onError       = function() {};
    this.onFail        = function() {};
};

/**
 * Encode a variable.
 */
XHR.prototype._encodeVar = function(name, value, returnVars) {
	if (returnVars == true) {
        return Array(encodeURIComponent(name), encodeURIComponent(value));
	} else {
        this.vars[encodeURIComponent(name)] = Array(encodeURIComponent(value), true);
	}
}

/**
 * Process the URL.
 * 
 * @param {String}
 * @param {Boolean} encode   Encode a URL?
 */
XHR.prototype._processUrl = function(string, encode)
{
    encoded  = encodeURIComponent(this.argumentSeparator);
    regexp   = new RegExp(this.argumentSeparator + "|" + encoded);
    varArray = string.split(regexp);
    
    for (var i = 0; i < varArray.length; i++) {
        urlVars = varArray[i].split('=');
        if (encode == true){
            this._encodeVar(urlVars[0], urlVars[1]);
        } else {
            this.setVar(urlVars[0], urlVars[1]);
        }
    }
};

/**
 * Create the URL address from string.
 * 
 * @param {String} urlString   URL string.
 */
XHR.prototype._createUrl = function(urlString)
{
    if (this.encodeUri && this.urlString.length) {
        this._processUrl(this.urlString, true);
    }
    
    if (urlString) {
        if (this.urlString.length) {
            this.urlString += this.argumentSeparator + urlString;
        } else {
            this.urlString = urlString;
        }
    }
    
    // prevents caching of URL
    this.setVar("random_var", new Date().getTime());
    
    tmpUrl = new Array();
    
    for (key in this.vars) {
        if (this.vars[key][1] == false && this.encodeUri == true) {
            encoded = this._encodeVar(key, this.vars[key][0], true);
            delete this.vars[key];
            this.vars[encoded[0]] = Array(encoded[1], true);
            key = encoded[0];
        }
        
        tmpUrl[tmpUrl.length] = key + "=" + this.vars[key][0];
    }
    
    if (urlString) {
        this.urlString += this.argumentSeparator + tmpUrl.join(this.argumentSeparator);
    } else {
        this.urlString += tmpUrl.join(this.argumentSeparator);
    }
};

// END: Private methods }}}