/**
 * @author Will Lin
 * @date   2010-06-04
 */
var Ajax = 
{
	filename: "transport.js",
	
	debugging:
	{
		isDebugging       : 0,    // 0-close;  1-open
		debuggingMode     : 0,    // 0-alert;  1-innerHTML
		linefeed          : "",
		containerID       : 0	
	},
	
	// debug
	debug: function(isDebugging, debuggingMode)
	{
		this.debugging = {
			"isDebugging"        : isDebugging,
			"debuggingMode"      : debuggingMode,
			"linefeed"           : debuggingMode ? "<br />" : "\n",
			"containerID"        : "debugging-id" + new Date().getTime()
		};
	},
	
	/**
	 * method calls on the process is running
	 */
	onRunning: function() {},
	
	/**
	 * Method calls on the process is completed
	 */
	onComplete: function() {},
	
	/**
	 * Parse params
	 * 
	 * @param {mixed}    params
	 * @return {mixed}   legal params
	 */
	parseParams: function(params)
	{
		var legalParams = "";
		
		if (typeof(params) == "string") {
			legalParams = params;
		}
		else if (typeof(params) == "object") {
			try {
				legalParams = "JSON=" + params.toJSONString();
			} catch (ex) {
				alert("Can't stringify JSON!");
				return false;
			}
		} else {
			alert("Invalid parameters!");
			return false;
		}
		
		// if debugging
		if (this.debugging.isDebugging) {
			var line = this.debugging.linefeed;
			var info = "[Original Parameters]" + line + params + line + line + "[Parsed Parameters]" + line + legalParams;
			
			this.displayDebuggingInfo(info, "param");
		}
		
		return legalParams;
	},
	
	/**
	 * Parse result
	 */
	parseResult: function(responseType, xhr)
	{
		var result = null;
		
		switch (responseType) {
			case "JSON" :
				result = this.preFilter(xhr.responseText);
				
				try { 
				  // alert(result);
					result = result.parseJSON();
				} catch (ex) {
					throw this.filename + "/parseResult() error: can't parse to JSON.\n\n" + xhr.responseText;
				}
				break;
			case "XML" :
				result = xhr.responseXML;				
				break;
			case "TEXT" :
				result = this.preFilter(xhr.responseText);
				break;
			default :
				throw this.filename + "/parseResult() error: unknown response type:" + responseType;
		}
		
		if (this.debugging.isDebugging) {
			var line = this.debugging.linefeed;
			var info = "[Response Result of " + responseType + " Format]" + line + result;
			
			if (responseType == "JSON") {
				info = "[Response Result of TEXT Format]" + line + xhr.responseText + line + line + info;
			}
			
			this.displayDebuggingInfo(info, "result");
		}
		
		return result;
	},
	
	/**
	 * Filter UTF-8 header
	 */
	preFilter: function(str)
	{
		return str.replace(/\xEF\xBB\xBF/g, "");
	},
	
	
	/**
	 * Display debugging info
	 */
	displayDebuggingInfo: function(info, type)
	{		
		var msg = "Info:" + info + "\n\nDebuggingMode:" + this.debugging.debuggingMode + "\n\nContainerID:" + this.debugging.containerID;	
		alert(msg);
	},
	
	
	/**
	 * Create a XMLHttpRequest object
	 */
	createXMLHttpRequest: function()
	{
		var xhr = null;
		
		// for IE
		if (window.ActiveXObject) {
			var versions = ['Microsoft.XMLHTTP', 'MSXML6.XMLHTTP', 'MSXML5.XMLHTTP', 'MSXML4.XMLHTTP', 'MSXML3.XMLHTTP', 'MSXML2.XMLHTTP', 'MSXML.XMLHTTP'];
			
			for(var i=0; i<versions.length; i++) {
				try {
					xhr = new ActiveXObject(versions[i]);
					break;
				} catch (ex) {
					continue;
				}
			}
		} else {
			xhr = new XMLHttpRequest();
		}
		
		return xhr;
	},
	
	
	/**
	 * XML Http request
	 * 
	 * @param {string}    url
	 * @param {mixed}     params
	 * @param {function}  callback
	 * @param {string}    transMode
	 * @param {string}    responseType
	 * @param {boolean}   asyn
	 * @param {boolean}   quite
	 */
	call: function(url, params, callback, transMode, responseType, asyn, quite)
	{
		params         = this.parseParams(params);
		transMode      = transMode.toUpperCase() === "GET" ? "GET" : "POST";
		responseType   = typeof(responseType) === "string" ? responseType.toUpperCase() : "JSON";
		asyn           = asyn === false ? false : true;
		url            = url + (url.indexOf("?") === -1 ? "?" : "&") + "is_ajax=1";

		if (transMode === "GET") {
			var d = new Date();
			// clear caches
			url = params ? url + "&" + params : "";
			url = encodeURI(url) + (url.indexOf("?") === - 1 ? "?" : "&") + d.getTime() + d.getMilliseconds();
			params = null
		}

		var xhr = this.createXMLHttpRequest();

		try {
			var self = this;
			self.onRunning(); // running
			xhr.open(transMode, url, asyn); // open
			
			if (transMode === "POST") {
				xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
			}

			if (asyn) { // asynchronous
				xhr.onreadystatechange = function () {
					if (xhr.readyState == 4) {
						switch (xhr.status) {
							case 200 : // ok
								self.onComplete();
								
								// callback
								if (typeof(callback) === "function") {
									callback.call(self, self.parseResult(responseType, xhr), xhr.responseText);
								}
								break;	
							default :
								//alert("XmlHttpRequest status: [" + xhr.status + "] Unknow status.");
						}
						
						xhr = null;
					}
				}
				
				if (xhr != null) xhr.send(params);
			} else { // no aysnchronous
				xhr.send(params);
				
				var result = self.parseResult(responseType, xhr);
				self.onComplete();
				
				// callback
				if (typeof(callback) === "function") {
					callback.call(self, result, xhr.reponseText);
				}
				
				xhr = null;
				return result;
			}
		} catch (ex) {
			self.onComplete();
			alert(self.filename + "/run() error:" + ex.description);
		}
	}
}

/*
 * JSON.js  
 * @date 2007-03-06
*/
// Augment the basic prototypes if they have not already been augmented.
if (!Object.prototype.toJSONString) {
    Array.prototype.toJSONString = function () {
        var a = ['['], // The array holding the text fragments.
            b,         // A boolean indicating that a comma is required.
            i,         // Loop counter.
            l = this.length,
            v;         // The value to be stringified.

        function p(s) {
            // p accumulates text fragments in an array. It inserts a comma before all
            // except the first fragment.
            if (b) {
              a.push(',');
            }
            a.push(s);
            b = true;
        }

        // For each value in this array...

        for (i = 0; i < l; i ++) {
            v = this[i];
            switch (typeof v) {
            // Values without a JSON representation are ignored.
            case 'undefined':
            case 'function':
            case 'unknown':
                break;

            // Serialize a JavaScript object value. Ignore objects thats lack the
            // toJSONString method. Due to a specification error in ECMAScript,
            // typeof null is 'object', so watch out for that case.
            case 'object':
                if (v) {
                    if (typeof v.toJSONString === 'function') {
                        p(v.toJSONString());
                    }
                } else {
                    p("null");
                }
                break;

            // Otherwise, serialize the value.

            default:
                p(v.toJSONString());
            }
        }

        // Join all of the fragments together and return.
        a.push(']');
				
        return a.join('');
    };

    Boolean.prototype.toJSONString = function () {
        return String(this);
    };

    Date.prototype.toJSONString = function () {

        // Ultimately, this method will be equivalent to the date.toISOString method.

        function f(n) {
            // Format integers to have at least two digits.
            return n < 10 ? '0' + n : n;
        }

        return '"' + this.getFullYear() + '-' +
                f(this.getMonth() + 1) + '-' +
                f(this.getDate()) + 'T' +
                f(this.getHours()) + ':' +
                f(this.getMinutes()) + ':' +
                f(this.getSeconds()) + '"';
    };

    Number.prototype.toJSONString = function () {
        // JSON numbers must be finite. Encode non-finite numbers as null.
        return isFinite(this) ? String(this) : "null";
    };

    Object.prototype.toJSONString = function () {
        var a = ['{'],  // The array holding the text fragments.
            b,          // A boolean indicating that a comma is required.
            k,          // The current key.
            v;          // The current value.

        function p(s) {

            // p accumulates text fragment pairs in an array. It inserts a comma before all
            // except the first fragment pair.

            if (b) {
                a.push(',');
            }
            a.push(k.toJSONString(), ':', s);
            b = true;
        }

        // Iterate through all of the keys in the object, ignoring the proto chain.

        for (k in this) {
            if (this.hasOwnProperty(k)) {
                v = this[k];
                switch (typeof v) {
                // Values without a JSON representation are ignored.
                case 'undefined':
                case 'function':
                case 'unknown':
                    break;

                // Serialize a JavaScript object value. Ignore objects that lack the
                // toJSONString method. Due to a specification error in ECMAScript,
                // typeof null is 'object', so watch out for that case.
                case 'object':
                    if (this !== window)
                    {
                      if (v) {
                          if (typeof v.toJSONString === 'function') {
                              p(v.toJSONString());
                          }
                      } else {
                          p("null");
                      }
                    }
                    break;
                default:
                    p(v.toJSONString());
                }
            }
        }

          // Join all of the fragments together and return.

        a.push('}');
        return a.join('');
    };

    (function (s) {

        // Augment String.prototype. We do this in an immediate anonymous function to
        // avoid defining global variables.

        // m is a table of character substitutions.

        var m = {
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        };

        s.parseJSON = function (filter) {

            // Parsing happens in three stages. In the first stage, we run the text against
            // a regular expression which looks for non-JSON characters. We are especially
            // concerned with '()' and 'new' because they can cause invocation, and '='
            // because it can cause mutation. But just to be safe, we will reject all
            // unexpected characters.
            try {
                if (/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.
                        test(this)) {

                    // In the second stage we use the eval function to compile the text into a
                    // JavaScript structure. The '{' operator is subject to a syntactic ambiguity
                    // in JavaScript: it can begin a block or an object literal. We wrap the text
                    // in parens to eliminate the ambiguity.
                    var j = eval('(' + this + ')');

                    // In the optional third stage, we recursively walk the new structure, passing
                    // each name/value pair to a filter function for possible transformation.
                    if (typeof filter === 'function') {
                        function walk(k, v) {
                            if (v && typeof v === 'object') {
                                for (var i in v) {
                                    if (v.hasOwnProperty(i)) {
                                        v[i] = walk(i, v[i]);
                                    }
                                }
                            }
                            return filter(k, v);
                        }

                        j = walk('', j);
                    }
                    return j;
                }
            } catch (e) {

            // Fall through if the regexp test fails.
            }
            throw new SyntaxError("parseJSON");
        };
				
        s.toJSONString = function () {

          // If the string contains no control characters, no quote characters, and no
          // backslash characters, then we can simply slap some quotes around it.
          // Otherwise we must also replace the offending characters with safe
          // sequences.
          // add by weberliu @ 2007-4-2			
          var _self = this.replace("&", "%26");

          if (/["\\\x00-\x1f]/.test(this)) {
              return '"' + _self.replace(/([\x00-\x1f\\"])/g, function(a, b) {
                  var c = m[b];
                  if (c) {
                      return c;
                  }
                  c = b.charCodeAt();
                  return '\\u00' +
                      Math.floor(c / 16).toString(16) +
                      (c % 16).toString(16);
              }) + '"';
          }
          return '"' + _self + '"';
        };
    })(String.prototype);
}

