// QueryString
// ---------------
// This module provides utilities for dealing with query strings.
//
// Thanks to:
//  - http://nodejs.org/docs/v0.4.7/api/querystring.html
//  - http://developer.yahoo.com/yui/3/api/QueryString.html
//  - https://github.com/lifesinger/dew/tree/master/lib/querystring

var QueryString = {};

// The escape/unescape function used by stringify/parse, provided so that it
// could be overridden if necessary. This is important in cases where
// non-standard delimiters are used, if the delimiters would not normally be
// handled properly by the built-in (en|de)codeURIComponent functions.
QueryString.escape = encodeURIComponent;

QueryString.unescape = function (s) {
  // The + character is interpreted as a space on the server side as well as
  // generated by forms with spaces in their fields.
  return decodeURIComponent(s.replace(/\+/g, ' '));
};

/**
 * Serialize an object to a query string. Optionally override the default
 * separator and assignment characters.
 *
 * stringify({foo: 'bar'})
 *   // returns 'foo=bar'
 *
 * stringify({foo: 'bar', baz: 'bob'}, ';', ':')
 *   // returns 'foo:bar;baz:bob'
 */
QueryString.stringify = function (obj, sep, eq, arrayKey) {
  if (!isPlainObject(obj)) return '';

  sep = sep || '&';
  eq = eq || '=';
  arrayKey = arrayKey || false;

  var buf = [],
    key, val;
  var escape = QueryString.escape;

  for (key in obj) {
    if (!hasOwnProperty.call(obj, key)) continue;

    val = obj[key];
    key = QueryString.escape(key);

    // val is primitive value
    if (isPrimitive(val)) {
      buf.push(key, eq, escape(val + ''), sep);
    }
    // val is not empty array
    else if (isArray(val) && val.length) {
      for (var i = 0; i < val.length; i++) {
        if (isPrimitive(val[i])) {
          buf.push(
            key,
            (arrayKey ? escape('[]') : '') + eq,
            escape(val[i] + ''),
            sep);
        }
      }
    }
    // ignore other cases, including empty array, Function, RegExp, Date etc.
    else {
      buf.push(key, eq, sep);
    }
  }

  buf.pop();
  return buf.join('');
};

/**
 * Deserialize a query string to an object. Optionally override the default
 * separator and assignment characters.
 *
 * parse('a=b&c=d')
 *   // returns {a: 'b', c: 'c'}
 */
QueryString.parse = function (str, sep, eq) {
  if (typeof str === 'undefined') {
    str = document.location.search
  }
  var ret = {};

  if (typeof str !== 'string' || trim(str).length === 0) {
    return ret;
  }

  // remove ^?
  str = str.replace(/^\?/, '');

  var pairs = str.split(sep || '&');
  eq = eq || '=';
  var unescape = QueryString.unescape;

  for (var i = 0; i < pairs.length; i++) {

    var pair = pairs[i].split(eq);
    var key = unescape(trim(pair[0]));
    var val = unescape(trim(pair.slice(1).join(eq)));

    var m = key.match(/^(\w+)\[\]$/);
    if (m && m[1]) {
      key = m[1];
    }

    if (hasOwnProperty.call(ret, key)) {
      if (!isArray(ret[key])) {
        ret[key] = [ret[key]];
      }
      ret[key].push(val);
    } else {
      ret[key] = m ? [val] : val;
    }
  }

  return ret;
};

// Helpers

var toString = Object.prototype.toString;
var hasOwnProperty = Object.prototype.hasOwnProperty;
var isArray = Array.isArray || function (val) {
  return toString.call(val) === '[object Array]';
};
var trim = String.prototype.trim ?
  function (str) {
    return (str == null) ?
      '' :
      String.prototype.trim.call(str);
  } :
  function (str) {
    return (str == null) ?
      '' :
      str.toString().replace(/^\s+/, '').replace(/\s+$/, '');
  };

/**
 * Checks to see if an object is a plain object (created using "{}" or
 * "new Object()" or "new FunctionClass()").
 */
function isPlainObject(o) {
  /**
   * NOTES:
   * isPlainObject(node = document.getElementById("xx")) -> false
   * toString.call(node):
   *   ie678 === '[object Object]', other === '[object HTMLElement]'
   * 'isPrototypeOf' in node:
   *   ie678 === false, other === true
   */
  return o &&
    toString.call(o) === '[object Object]' &&
    'isPrototypeOf' in o;
}

/**
 * If the type of o is null, undefined, number, string, boolean,
 * return true.
 */
function isPrimitive(o) {
  return o !== Object(o);
}

export default QueryString