(function(GLOBAL) {
  function addValue(obj,keyValue) {
    var key = keyValue[0], value=keyValue[1];
    if (keyValue.length == 0) return;
    if (keyValue.length == 1) value = null;
    if (keyValue.length > 2) value = keyValue.slice(1).join('=')
    if (typeof obj[key] == 'undefined') obj[key] = value;
    else if (typeof obj[key] == 'string') obj[key] = [obj[key],value];
    else if (obj[key] == null) obj[key] = [null,value];
    else obj[key].push(value);
  }
  
  function keys(obj) {
    var keys = [];
    for (var key in obj) keys.push(key);
    return keys.sort();    
  }
    
  function queryObj(string) {
    var obj = {}, array = [];
    string = string || '';
    array = string.split('&');
    for (var i=0; i<array.length; i++) {
      addValue(obj,array[i].split('='));
    }
    return obj;
  }
  
  function queryToString(obj,order) {
    var key, array = [],order = order || keys(obj);
    while (key = order.shift()) {
      if (obj[key] == null) array.push(key);
      else if (typeof obj[key].push == 'function') {
        for (var i=0; i<obj[key].length; i++) array.push(key+'='+(obj[key][i] == null ? '' : obj[key][i]));
      } else {array.push(key+'='+(obj[key] !== null ? obj[key] : ''));}
    }
    return array.join('&');
  }

  GLOBAL.Cobalt = GLOBAL.Cobalt || {};
  GLOBAL.Cobalt.Core = GLOBAL.Cobalt.Core || {};
  GLOBAL.Cobalt.Core.Util = GLOBAL.Cobalt.Core.Util || {};
  
  /**
   * An object to manipulate a URL querystring
   * @constructor
   * @param {string} string A query string (optional)
   */
  GLOBAL.Cobalt.Core.Util.queryString = function(string) {
    if (typeof string == 'undefined') string = '';
    if (typeof string != 'string') {      
      throw new Error("Invalid argument to queryString");
    }
    string = string.replace(/^\?|#.*$/g,'');
    this.obj = (string.length > 0) ? queryObj(string) : {};
    this.sortOrder = null;
  };

  /**
    * Returns the string value of the querystring object
    * @returns {string} Query-string
    */
  GLOBAL.Cobalt.Core.Util.queryString.prototype.toString = function() {
    return queryToString(this.obj,this.sortOrder);
  };
  
  /**
    * The query parameters' key values
    * @return {array} The querystring keys, in dictionary order
    */
  GLOBAL.Cobalt.Core.Util.queryString.prototype.keys = function() {
    return keys(this.obj);
  };
  
  /**
    * Re-orders the placement of parameters in the querystring string value
    * @param newOrder Array describing the parameters' order
    * @throws Error         if newOrder is improperly specified; i.e., it must be an array containing exactly the keys in the queryString object.
    */
  GLOBAL.Cobalt.Core.Util.queryString.prototype.order = function(newOrder) {
    var existing = keys(this.obj),testNew;
    if (!newOrder || (typeof newOrder.sort != 'function')) {
      throw new Error('Invalid querystring keyset specified');
    } else {
      testNew = newOrder.slice();
      testNew.sort();
      if (String(testNew) != String(existing)) {
        throw new Error('Mismatched querystring keyset specified');
      }
    }
    this.sortOrder = newOrder.slice();
  };
  /**
    * Sets the value of the given queryString key
    * @param {string} key
    * @param {optional} value
    */
  GLOBAL.Cobalt.Core.Util.queryString.prototype.setParam = function(key,value) {
    this.removeParam(key);
    this.addParam(key,value);
  };
  /**
    * Adds the value to the given queryString key. The key is created if necessary.
    * @param {string} key
    * @param {optional} value
    */
  GLOBAL.Cobalt.Core.Util.queryString.prototype.addParam = function(key,value) {
    addValue(this.obj,[key,(typeof value == 'undefined' ? null : value)]);
  };
  /**
    * Retrieves the value of the given queryString key.
    * @param {string} key
    */
  GLOBAL.Cobalt.Core.Util.queryString.prototype.param = function(key) {
    return this.obj[key];
  };
  /**
    * Removes the given queryString key, or key/value
    * @param {string} key
    * @param {optional} value If given, on keys with matching values are removed.
    */
  GLOBAL.Cobalt.Core.Util.queryString.prototype.removeParam = function(key,value) {
    if (typeof value == 'undefined' || typeof this.obj[key].push != 'function') delete this.obj[key];
    else {
      var paramvalue = this.obj[key];
      for (var i=paramvalue.length-1; i >= 0; i--) 
        if (paramvalue[i] === value) paramvalue.splice(i,1);
      if (paramvalue.length === 0) delete this.obj[key];
    }
  } 
})(window); 

