//##PREON
/**
 * IO library.
 */
//##setCompressionEnabled(false);   // Compression happens by includer

/**
 * The following comes from the json.org json serializer.  Chopped down
 * and re-worked considerably.
 */
var IO_stringifyJSON;
(function() {
    /* We don't want to pollute to global namespace,
       so don't use the prototype extensions of the original
     
      function f(n) {
          // Format integers to have at least two digits.
          return n < 10 ? '0' + n : n;
      }
  
      if (typeof Date.prototype.toJSON !== 'function') {
  
          Date.prototype.toJSON = function (key) {
  
              return this.getUTCFullYear()   + '-' +
                   f(this.getUTCMonth() + 1) + '-' +
                   f(this.getUTCDate())      + 'T' +
                   f(this.getUTCHours())     + ':' +
                   f(this.getUTCMinutes())   + ':' +
                   f(this.getUTCSeconds())   + 'Z';
          };
  
          String.prototype.toJSON =
          Number.prototype.toJSON =
          Boolean.prototype.toJSON = function (key) {
              return this.valueOf();
          };
      }
      */
      
      var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
          escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
          gap,
          indent,
          meta = {    // table of character substitutions
              '\b': '\\b',
              '\t': '\\t',
              '\n': '\\n',
              '\f': '\\f',
              '\r': '\\r',
              '"' : '\\"',
              '\\': '\\\\'
          };
          //rep;
  
  
      function quote(string) {
  
  // If the string contains no control characters, no quote characters, and no
  // backslash characters, then we can safely slap some quotes around it.
  // Otherwise we must also replace the offending characters with safe escape
  // sequences.
  
          escapable.lastIndex = 0;
          return escapable.test(string) ?
              '"' + string.replace(escapable, function (a) {
                  var c = meta[a];
                  return typeof c === 'string' ? c :
                      '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
              }) + '"' :
              '"' + string + '"';
      }
  
  
      function str(key, holder) {
  
  // Produce a string from holder[key].
  
          var i,          // The loop counter.
              k,          // The member key.
              v,          // The member value.
              length,
              mind = gap,
              partial,
              value = holder[key];
  
  // If the value has a toJSON method, call it to obtain a replacement value.
          /*  
          if (value && typeof value === 'object' &&
                  typeof value.toJSON === 'function') {
              value = value.toJSON(key);
          }
          
  
  // If we were called with a replacer function, then call the replacer to
  // obtain a replacement value.
  
          if (typeof rep === 'function') {
              value = rep.call(holder, key, value);
          }
          */
          
  // What happens next depends on the value's type.
  
          switch (typeof value) {
          case 'string':
              return quote(value);
  
          case 'number':
  
  // JSON numbers must be finite. Encode non-finite numbers as null.
  
              return isFinite(value) ? String(value) : 'null';
  
          case 'boolean':
          case 'null':
  
  // If the value is a boolean or null, convert it to a string. Note:
  // typeof null does not produce 'null'. The case is included here in
  // the remote chance that this gets fixed someday.
  
              return String(value);
  
  // If the type is 'object', we might be dealing with an object or an array or
  // null.
  
          case 'object':
  
  // Due to a specification blunder in ECMAScript, typeof null is 'object',
  // so watch out for that case.
  
              if (!value) {
                  return 'null';
              }
  
  // Make an array to hold the partial results of stringifying this object value.
  
              gap += indent;
              partial = [];
  
  // Is the value an array?
  
              if (Object.prototype.toString.apply(value) === '[object Array]') {
  
  // The value is an array. Stringify every element. Use null as a placeholder
  // for non-JSON values.
  
                  length = value.length;
                  for (i = 0; i < length; i += 1) {
                      partial[i] = str(i, value) || 'null';
                  }
  
  // Join all of the elements together, separated with commas, and wrap them in
  // brackets.
  
                  v = partial.length === 0 ? '[]' :
                      gap ? '[\n' + gap +
                              partial.join(',\n' + gap) + '\n' +
                                  mind + ']' :
                            '[' + partial.join(',') + ']';
                  gap = mind;
                  return v;
              }
  
  // If the replacer is an array, use it to select the members to be stringified.
              /*
              if (rep && typeof rep === 'object') {
                  length = rep.length;
                  for (i = 0; i < length; i += 1) {
                      k = rep[i];
                      if (typeof k === 'string') {
                          v = str(k, value);
                          if (v) {
                              partial.push(quote(k) + (gap ? ': ' : ':') + v);
                          }
                      }
                  }
              } else {
              */
  // Otherwise, iterate through all of the keys in the object.
  
                  for (k in value) {
                      if (Object.hasOwnProperty.call(value, k)) {
                          v = str(k, value);
                          if (v) {
                              partial.push(quote(k) + (gap ? ': ' : ':') + v);
                          }
                      }
                  }
              //}
  
  // Join all of the member texts together, separated with commas,
  // and wrap them in braces.
  
              v = partial.length === 0 ? '{}' :
                  gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
                          mind + '}' : '{' + partial.join(',') + '}';
              gap = mind;
              return v;
          }
      }
  
  // If the JSON object does not yet have a stringify method, give it one.
  
          IO_stringifyJSON = function (value, replacer, space) {
  
  // The stringify method takes a value and an optional replacer, and an optional
  // space parameter, and returns a JSON text. The replacer can be a function
  // that can replace values, or an array of strings that will select the keys.
  // A default replacer method can be provided. Use of the space parameter can
  // produce text that is more easily readable.
  
              var i;
              gap = '';
              indent = '';
  
  // If the space parameter is a number, make an indent string containing that
  // many spaces.
  
              if (typeof space === 'number') {
                  for (i = 0; i < space; i += 1) {
                      indent += ' ';
                  }
  
  // If the space parameter is a string, it will be used as the indent string.
  
              } else if (typeof space === 'string') {
                  indent = space;
              }
  
  // If there is a replacer, it must be a function or an array.
  // Otherwise, throw an error.
              /*
              rep = replacer;
              if (replacer && typeof replacer !== 'function' &&
                      (typeof replacer !== 'object' ||
                       typeof replacer.length !== 'number')) {
                  throw new Error('JSON.stringify');
              }
              */
  // Make a fake root object containing our value under the key of ''.
  // Return the result of stringifying the value.
  
              return str('', {'': value});
          };
})();

/**
 * Convert all non-default fields of the object to a query
 * string format.
 */
function IO_toQueryString(obj) {
  var ary=[],
    cmp={},
    k;
  for (k in obj) {
    if (!cmp[k]) {  // Does not exist in Object
      ary.push(encodeURIComponent(k) + '=' + encodeURIComponent(String(obj[k])));
    }
  }
  
  return ary.join('&');
}

/**
 * Create and return an XMLHttpRequest object
 */
function IO_xhr() {
  function ieXhr(progId) {
    try {
      return new ActiveXObject(progId);
    } catch (e) {
      return undefined;
    }
  }
  if (window.XMLHttpRequest) return new window.XMLHttpRequest();
  if (window.ActiveXObject) {
    var ret=ieXhr('Msxml2.XMLHTTP.6.0') ||
      ieXhr('Msxml2.XMLHTTP.3.0') ||
      ieXhr('Msxml2.XMLHTTP') ||
      ieXhr('Microsoft.XMLHTTP');
    if (ret) return ret;
  }
  
  throw new Error('Current browser configuration does not support XMLHttpRequest');
}

/**
 * Parse JSON and return the resultant value.  Return undefined on failure.
 */
function IO_parseJSON(jsonText) {
  try {
    return MQA._jsEval('(' + jsonText + ')');
  } catch (e) {
    Log_handleError('Failed to parse JSON "' + jsonText + '"', e);  //$D
    return undefined;
  }
}

/**
 * Perform an XHR get with the given URL and settings.  The callback
 * takes the parameters (xhr, errorInfo).  errorInfo is an object if
 * an error happened and is undefined/false if success.  Returns an abort
 * function.
 */
function IO_doXhr(url, ioSettings, xhrCallback) {
  if (!ioSettings) ioSettings={};	
  var xhr=IO_xhr(), cancelled, timeoutKey, setup=ioSettings.setup;
  
  Log_debug('Http: ' + url);  //$D
  
  xhr.open(ioSettings.verb||'GET', url, true);
  if (setup) setup(xhr);
  
  xhr.onreadystatechange = function() {
    if (cancelled) return;
    if (xhr.readyState==4) {
      // Unwire it
      xhr.onreadystatechange = AOP_emptyFunction;
      var status, _xhr;
      try {
        // Firefox throws an exception on network errors when querying status
        status=xhr.status;
      } catch (e) { }

      _xhr=xhr;
      xhr=null;
      if (timeoutKey) clearTimeout(timeoutKey);
      
      if (status>=200 && status<=299) {
        // Success
        Log_debug('Success: Http: ' + url); //$D
        xhrCallback(_xhr, false);
      } else {
        // Failure
        var errorResponse;  //$D
        try { errorResponse=_xhr.responseText; } catch (ex) { }  //$D
        Log_debug('Failure(' + status + ') Http: ' + url + '\n' + errorResponse); //$D
        
        xhrCallback(_xhr, { reason: 'HTTP error', statusCode: status, responseText: errorResponse });
      }
      
    }
  };
  
  if (ioSettings.timeout) {
    timeoutKey = setTimeout(function() {
        if (cancelled) return;
        Log_debug('Timeout Http: ' + url);  //$D
        
        cancelled=true;
        xhr.onreadystatechange = AOP_emptyFunction;
        xhr.abort();
        xhrCallback(xhr, {reason: 'Request timed out' });
        
        xhr=null;
    }, ioSettings.timeout);
  }
  
  if (ioSettings.postData) Log_debug('Http post data: \n' + ioSettings.postData); //$D
  xhr.send(ioSettings.postData||null);
  
  // Return abort function
  return function() {
    if (xhr) {
      Log_debug('Abort Http: ' + url);  //$D
      cancelled=true;
      xhr.onreadystatechange = AOP_emptyFunction;
      xhr.abort();
      xhr=null;
      if (timeoutKey) clearTimeout(timeoutKey);
    }
  };
}

/**
 * Performs an HTTP GET of the given URL, returning the results
 * as a JSON object to the callback.
 *
 * Return value: The return value is a cancel function.  If invoked, the
 * request is cancelled.
 * 
 * The callback function takes one arg (the JSON response) on success.  On
 * failure, it takes arguments (false, errorInfo).  errorInfo may be null
 * and varies by the type of request made with ioSettings.  TODO: document
 * errorInfo.
 */
function IO_doGetJSON(url, ioSettings, callback) {
  return IO_doXhr(url, ioSettings, function(xhr, errorInfo) {
      if (errorInfo) {
        callback(false, errorInfo);
      } else {
        // Success
        var resultObj=IO_parseJSON(xhr.responseText);
        if (!resultObj) {
          callback(false, { reason: 'Parse Error', responseText: xhr.responseText });
        } else {
          callback(resultObj, null, xhr.responseText);
        }
      }
  });
}

/**
 * Similar to IO_doGetJSON but posts the request, serializing and sending the
 * given requestObj as the post data.  The content type is set to application/json
 */
function IO_doPostJSON(url, requestObj, ioSettings, callback) {
  ioSettings=AOP_extend(ioSettings, {
      verb: 'POST',
      setup: function(xhr) {
        xhr.setRequestHeader('Content-Type', 'application/json');
      },
      postData: IO_stringifyJSON(requestObj)
  });
  return IO_doGetJSON(url, ioSettings, callback);
}

var __IOCacheBustValue=0;
function IO_cacheBust() {
  return (new Date().getTime() + ',' + (++__IOCacheBustValue));
}

// Public exports
var IO={
  toQueryString: IO_toQueryString,
  parseJSON: IO_parseJSON,
  doXhr: IO_doXhr,
  doGetJSON: IO_doGetJSON,
  doPostJSON: IO_doPostJSON,
  stringifyJSON: IO_stringifyJSON,
  cacheBust: IO_cacheBust
};
MQA.IO=IO;

