if (typeof(whispercast) == 'undefined') {
  whispercast  = {};
}
whispercast.uri = whispercast.uri ? whispercast.uri : {};

whispercast.uri.urlencode = function(plain)
{
  var SAFECHARS = '0123456789' +          // Numeric
          'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +  // Alphabetic
          'abcdefghijklmnopqrstuvwxyz' +
          '-_.!~*\'()';          // RFC2396 Mark characters
  var HEX = '0123456789ABCDEF';

  if (typeof(plain) == 'boolean')
    plain = plain ? 1 : 0;
  plain = '' + plain;

  var encoded = '';
  for (var i = 0; i < plain.length; i++ ) {
    var ch = plain.charAt(i);
      if (ch == ' ') {
        encoded += '+';       // x-www-urlencoded, rather than %20
    } else if (SAFECHARS.indexOf(ch) != -1) {
        encoded += ch;
    } else {
        var charCode = ch.charCodeAt(0);
      if (charCode > 255) {
        encoded += '+';
      } else {
        encoded += '%';
        encoded += HEX.charAt((charCode >> 4) & 0xF);
        encoded += HEX.charAt(charCode & 0xF);
      }
    }
  } // for
  return encoded;
};
whispercast.uri.urldecode = function(encoded)
{
   var HEXCHARS = '0123456789ABCDEFabcdef'; 

   var plain = '';
   var i = 0;
   while (i < encoded.length) {
       var ch = encoded.charAt(i);
     if (ch == '+') {
         plain += ' ';
       i++;
     } else if (ch == '%') {
      if (i < (encoded.length-2) 
          && HEXCHARS.indexOf(encoded.charAt(i+1)) != -1 
          && HEXCHARS.indexOf(encoded.charAt(i+2)) != -1 ) {
        plain += unescape( encoded.substr(i,3) );
        i += 3;
      } else {
        plain += '%[ERROR]';
        i++;
      }
    } else {
       plain += ch;
       i++;
    }
  } // while
  return plain;
};

/**
 * The regular expressions used to parse the URI. 
 *
 * @access public
 **/
whispercast.uri.parser = {
  strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
  loose:  /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/,

  query: /(?:^|&)([^&=]*)=?([^&]*)/g
};
/**
 * The URI keys. 
 *
 * @access public
 **/
whispercast.uri.keys = [
  'source','protocol','authority','userInfo','user','password','host','port','relative','path','directory','file','query','anchor'
];

/**
 * Parses an URI into components.
 *
 * @access public
 *
 * @parameter input:string
 *            The URI to be parsed.
 * @parameter parse_query:boolean
 *            If true then the query part of the URI will
 *            be parsed into key->value pairs, too.
 * @parameter strict:boolean
 *            If true the use the strict parsing rules.
 *
 * @return object The URI components as an associative array.
 **/
whispercast.uri.parse = function(input, parse_query/*= true*/, strict/*= true*/) {
  parse_query = (parse_query == undefined) ? true : parse_query;
  strict = (strict == undefined) ? true : strict;

  var raw  = whispercast.uri.parser[strict ? 'strict' : 'loose'].exec(input);
  var result = {};

  var i = 14;
  while (i--) result[whispercast.uri.keys[i]] = raw[i] || '';

  if (parse_query) {
    var query = {};
    result[whispercast.uri.keys[12]].replace(whispercast.uri.parser['query'], function ($0, $1, $2) {
      if ($1) query[$1] = whispercast.uri.urldecode($2);
    });
    result[whispercast.uri.keys[12]] = query;
  }
  return result;
}
