text: var utils = require('./utils'),
    dateFormatter = require('./dateformatter');
  
  /**
   * Helper method to recursively run a filter across an object/array and apply it to all of the object/array's values.
   * @param  {*} input
   * @return {*}
   * @private
   */
  function iterateFilter(input) {
    var self = this,
      out = {};
  
    if (utils.isArray(input)) {
      return utils.map(input, function (value) {
        return self.apply(null, arguments);
      });
    }
  
    if (typeof input === 'object') {
      utils.each(input, function (value, key) {
        out[key] = self.apply(null, arguments);
      });
      return out;
    }
  
    return;
  }
  
  /**
   * Backslash-escape characters that need to be escaped.
   *
   * @example
   * {{ "\"quoted string\""|addslashes }}
   * // => \"quoted string\"
   *
   * @param  {*}  input
   * @return {*}        Backslash-escaped string.
   */
  exports.addslashes = function (input) {
    var out = iterateFilter.apply(exports.addslashes, arguments);
    if (out !== undefined) {
      return out;
    }
  
    return input.replace(/\\/g, '\\\\').replace(/\'/g, "\\'").replace(/\"/g, '\\"');
  };
  
  /**
   * Upper-case the first letter of the input and lower-case the rest.
   *
   * @example
   * {{ "i like Burritos"|capitalize }}
   * // => I like burritos
   *
   * @param  {*} input  If given an array or object, each string member will be run through the filter individually.
   * @return {*}        Returns the same type as the input.
   */
  exports.capitalize = function (input) {
    var out = iterateFilter.apply(exports.capitalize, arguments);
    if (out !== undefined) {
      return out;
    }
  
    return input.toString().charAt(0).toUpperCase() + input.toString().substr(1).toLowerCase();
  };
  
  /**
   * Format a date or Date-compatible string.
   *
   * @example
   * // now = new Date();
   * {{ now|date('Y-m-d') }}
   * // => 2013-08-14
   *
   * @param  {?(string|date)} input
   * @param  {string} format  PHP-style date format compatible string.
   * @param  {number=} offset Timezone offset from GMT in minutes.
   * @param  {string=} abbr   Timezone abbreviation. Used for output only.
   * @return {string}         Formatted date string.
   */
  exports.date = function (input, format, offset, abbr) {
    var l = format.length,
      date = new dateFormatter.DateZ(input),
      cur,
      i = 0,
      out = '';
  
    if (offset) {
      date.setTimezoneOffset(offset, abbr);
    }
  
    for (i; i < l; i += 1) {
      cur = format.charAt(i);
      if (dateFormatter.hasOwnProperty(cur)) {
        out += dateFormatter[cur](date, offset, abbr);
      } else {
        out += cur;
      }
    }
    return out;
  };
  
  /**
   * If the input is `undefined`, `null`, or `false`, a default return value can be specified.
   *
   * @example
   * {{ null_value|default('Tacos') }}
   * // => Tacos
   *
   * @example
   * {{ "Burritos"|default("Tacos") }}
   * // => Burritos
   *
   * @param  {*}  input
   * @param  {*}  def     Value to return if `input` is `undefined`, `null`, or `false`.
   * @return {*}          `input` or `def` value.
   */
  exports.default = function (input, def) {
    return (typeof input !== 'undefined' && (input || typeof input === 'number')) ? input : def;
  };
  
  /**
   * Force escape the output of the variable. Optionally use `e` as a shortcut filter name. This filter will be applied by default if autoescape is turned on.
   *
   * @example
   * {{ "<blah>"|escape }}
   * // => &lt;blah&gt;
   *
   * @example
   * {{ "<blah>"|e("js") }}
   * // => \u003Cblah\u003E
   *
   * @param  {*} input
   * @param  {string} [type='html']   If you pass the string js in as the type, output will be escaped so that it is safe for JavaScript execution.
   * @return {string}         Escaped string.
   */
  exports.escape = function (input, type) {
    var out = iterateFilter.apply(exports.escape, arguments),
      inp = input,
      i = 0,
      code;
  
    if (out !== undefined) {
      return out;
    }
  
    if (typeof input !== 'string') {
      return input;
    }
  
    out = '';
  
    switch (type) {
    case 'js':
      inp = inp.replace(/\\/g, '\\u005C');
      for (i; i < inp.length; i += 1) {
        code = inp.charCodeAt(i);
        if (code < 32) {
          code = code.toString(16).toUpperCase();
          code = (code.length < 2) ? '0' + code : code;
          out += '\\u00' + code;
        } else {
          out += inp[i];
        }
      }
      return out.replace(/&/g, '\\u0026')
        .replace(/</g, '\\u003C')
        .replace(/>/g, '\\u003E')
        .replace(/\'/g, '\\u0027')
        .replace(/"/g, '\\u0022')
        .replace(/\=/g, '\\u003D')
        .replace(/-/g, '\\u002D')
        .replace(/;/g, '\\u003B');
  
    default:
      return inp.replace(/&(?!amp;|lt;|gt;|quot;|#39;)/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;');
    }
  };
  exports.e = exports.escape;
  
  /**
   * Get the first item in an array or character in a string. All other objects will attempt to return the first value available.
   *
   * @example
   * // my_arr = ['a', 'b', 'c']
   * {{ my_arr|first }}
   * // => a
   *
   * @example
   * // my_val = 'Tacos'
   * {{ my_val|first }}
   * // T
   *
   * @param  {*} input
   * @return {*}        The first item of the array or first character of the string input.
   */
  exports.first = function (input) {
    if (typeof input === 'object' && !utils.isArray(input)) {
      var keys = utils.keys(input);
      return input[keys[0]];
    }
  
    if (typeof input === 'string') {
      return input.substr(0, 1);
    }
  
    return input[0];
  };
  
  /**
   * Group an array of objects by a common key. If an array is not provided, the input value will be returned untouched.
   *
   * @example
   * // people = [{ age: 23, name: 'Paul' }, { age: 26, name: 'Jane' }, { age: 23, name: 'Jim' }];
   * {% for agegroup in people|groupBy('age') %}
   *   <h2>{{ loop.key }}</h2>
   *   <ul>
   *     {% for person in agegroup %}
   *     <li>{{ person.name }}</li>
   *     {% endfor %}
   *   </ul>
   * {% endfor %}
   *
   * @param  {*}      input Input object.
   * @param  {string} key   Key to group by.
   * @return {object}       Grouped arrays by given key.
   */
  exports.groupBy = function (input, key) {
    if (!utils.isArray(input)) {
      return input;
    }
  
    var out = {};
  
    utils.each(input, function (value) {
      if (!value.hasOwnProperty(key)) {
        return;
      }
  
      var keyname = value[key],
        newVal = utils.extend({}, value);
      delete value[key];
  
      if (!out[keyname]) {
        out[keyname] = [];
      }
  
      out[keyname].push(value);
    });
  
    return out;
  };
  
  /**
   * Join the input with a string.
   *
   * @example
   * // my_array = ['foo', 'bar', 'baz']
   * {{ my_array|join(', ') }}
   * // => foo, bar, baz
   *
   * @example
   * // my_key_object = { a: 'foo', b: 'bar', c: 'baz' }
   * {{ my_key_object|join(' and ') }}
   * // => foo and bar and baz
   *
   * @param  {*}  input
   * @param  {string} glue    String value to join items together.
   * @return {string}
   */
  exports.join = function (input, glue) {
    if (utils.isArray(input)) {
      return input.join(glue);
    }
  
    if (typeof input === 'object') {
      var out = [];
      utils.each(input, function (value) {
        out.push(value);
      });
      return out.join(glue);
    }
    return input;
  };
  
  /**
   * Return a string representation of an JavaScript object.
   *
   * Backwards compatible with swig@0.x.x using `json_encode`.
   *
   * @example
   * // val = { a: 'b' }
   * {{ val|json }}
   * // => {"a":"b"}
   *
   * @example
   * // val = { a: 'b' }
   * {{ val|json(4) }}
   * // => {
   * //        "a": "b"
   * //    }
   *
   * @param  {*}    input
   * @param  {number}  [indent]  Number of spaces to indent for pretty-formatting.
   * @return {string}           A valid JSON string.
   */
  exports.json = function (input, indent) {
    return JSON.stringify(input, null, indent || 0);
  };
  exports.json_encode = exports.json;
  
  /**
   * Get the last item in an array or character in a string. All other objects will attempt to return the last value available.
   *
   * @example
   * // my_arr = ['a', 'b', 'c']
   * {{ my_arr|last }}
   * // => c
   *
   * @example
   * // my_val = 'Tacos'
   * {{ my_val|last }}
   * // s
   *
   * @param  {*} input
   * @return {*}          The last item of the array or last character of the string.input.
   */
  exports.last = function (input) {
    if (typeof input === 'object' && !utils.isArray(input)) {
      var keys = utils.keys(input);
      return input[keys[keys.length - 1]];
    }
  
    if (typeof input === 'string') {
      return input.charAt(input.length - 1);
    }
  
    return input[input.length - 1];
  };
  
  /**
   * Return the input in all lowercase letters.
   *
   * @example
   * {{ "FOOBAR"|lower }}
   * // => foobar
   *
   * @example
   * // myObj = { a: 'FOO', b: 'BAR' }
   * {{ myObj|lower|join('') }}
   * // => foobar
   *
   * @param  {*}  input
   * @return {*}          Returns the same type as the input.
   */
  exports.lower = function (input) {
    var out = iterateFilter.apply(exports.lower, arguments);
    if (out !== undefined) {
      return out;
    }
  
    return input.toString().toLowerCase();
  };
  
  /**
   * Deprecated in favor of <a href="#safe">safe</a>.
   */
  exports.raw = function (input) {
    return exports.safe(input);
  };
  exports.raw.safe = true;
  
  /**
   * Returns a new string with the matched search pattern replaced by the given replacement string. Uses JavaScript's built-in String.replace() method.
   *
   * @example
   * // my_var = 'foobar';
   * {{ my_var|replace('o', 'e', 'g') }}
   * // => feebar
   *
   * @example
   * // my_var = "farfegnugen";
   * {{ my_var|replace('^f', 'p') }}
   * // => parfegnugen
   *
   * @example
   * // my_var = 'a1b2c3';
   * {{ my_var|replace('\w', '0', 'g') }}
   * // => 010203
   *
   * @param  {string} input
   * @param  {string} search      String or pattern to replace from the input.
   * @param  {string} replacement String to replace matched pattern.
   * @param  {string} [flags]      Regular Expression flags. 'g': global match, 'i': ignore case, 'm': match over multiple lines
   * @return {string}             Replaced string.
   */
  exports.replace = function (input, search, replacement, flags) {
    var r = new RegExp(search, flags);
    return input.replace(r, replacement);
  };
  
  /**
   * Reverse sort the input. This is an alias for <code data-language="swig">{{ input|sort(true) }}</code>.
   *
   * @example
   * // val = [1, 2, 3];
   * {{ val|reverse }}
   * // => 3,2,1
   *
   * @param  {array}  input
   * @return {array}        Reversed array. The original input object is returned if it was not an array.
   */
  exports.reverse = function (input) {
    return exports.sort(input, true);
  };
  
  /**
   * Forces the input to not be auto-escaped. Use this only on content that you know is safe to be rendered on your page.
   *
   * @example
   * // my_var = "<p>Stuff</p>";
   * {{ my_var|safe }}
   * // => <p>Stuff</p>
   *
   * @param  {*}  input
   * @return {*}          The input exactly how it was given, regardless of autoescaping status.
   */
  exports.safe = function (input) {
    // This is a magic filter. Its logic is hard-coded into Swig's parser.
    return input;
  };
  exports.safe.safe = true;
  
  /**
   * Sort the input in an ascending direction.
   * If given an object, will return the keys as a sorted array.
   * If given a string, each character will be sorted individually.
   *
   * @example
   * // val = [2, 6, 4];
   * {{ val|sort }}
   * // => 2,4,6
   *
   * @example
   * // val = 'zaq';
   * {{ val|sort }}
   * // => aqz
   *
   * @example
   * // val = { bar: 1, foo: 2 }
   * {{ val|sort(true) }}
   * // => foo,bar
   *
   * @param  {*} input
   * @param {boolean} [reverse=false] Output is given reverse-sorted if true.
   * @return {*}        Sorted array;
   */
  exports.sort = function (input, reverse) {
    var out;
    if (utils.isArray(input)) {
      out = input.sort();
    } else {
      switch (typeof input) {
      case 'object':
        out = utils.keys(input).sort();
        break;
      case 'string':
        out = input.split('');
        if (reverse) {
          return out.reverse().join('');
        }
        return out.sort().join('');
      }
    }
  
    if (out && reverse) {
      return out.reverse();
    }
  
    return out || input;
  };
  
  /**
   * Strip HTML tags.
   *
   * @example
   * // stuff = '<p>foobar</p>';
   * {{ stuff|striptags }}
   * // => foobar
   *
   * @param  {*}  input
   * @return {*}        Returns the same object as the input, but with all string values stripped of tags.
   */
  exports.striptags = function (input) {
    var out = iterateFilter.apply(exports.striptags, arguments);
    if (out !== undefined) {
      return out;
    }
  
    return input.toString().replace(/(<([^>]+)>)/ig, '');
  };
  
  /**
   * Capitalizes every word given and lower-cases all other letters.
   *
   * @example
   * // my_str = 'this is soMe text';
   * {{ my_str|title }}
   * // => This Is Some Text
   *
   * @example
   * // my_arr = ['hi', 'this', 'is', 'an', 'array'];
   * {{ my_arr|title|join(' ') }}
   * // => Hi This Is An Array
   *
   * @param  {*}  input
   * @return {*}        Returns the same object as the input, but with all words in strings title-cased.
   */
  exports.title = function (input) {
    var out = iterateFilter.apply(exports.title, arguments);
    if (out !== undefined) {
      return out;
    }
  
    return input.toString().replace(/\w\S*/g, function (str) {
      return str.charAt(0).toUpperCase() + str.substr(1).toLowerCase();
    });
  };
  
  /**
   * Remove all duplicate items from an array.
   *
   * @example
   * // my_arr = [1, 2, 3, 4, 4, 3, 2, 1];
   * {{ my_arr|uniq|join(',') }}
   * // => 1,2,3,4
   *
   * @param  {array}  input
   * @return {array}        Array with unique items. If input was not an array, the original item is returned untouched.
   */
  exports.uniq = function (input) {
    var result;
  
    if (!input || !utils.isArray(input)) {
      return '';
    }
  
    result = [];
    utils.each(input, function (v) {
      if (result.indexOf(v) === -1) {
        result.push(v);
      }
    });
    return result;
  };
  
  /**
   * Convert the input to all uppercase letters. If an object or array is provided, all values will be uppercased.
   *
   * @example
   * // my_str = 'tacos';
   * {{ my_str|upper }}
   * // => TACOS
   *
   * @example
   * // my_arr = ['tacos', 'burritos'];
   * {{ my_arr|upper|join(' & ') }}
   * // => TACOS & BURRITOS
   *
   * @param  {*}  input
   * @return {*}        Returns the same type as the input, with all strings upper-cased.
   */
  exports.upper = function (input) {
    var out = iterateFilter.apply(exports.upper, arguments);
    if (out !== undefined) {
      return out;
    }
  
    return input.toString().toUpperCase();
  };
  
  /**
   * URL-encode a string. If an object or array is passed, all values will be URL-encoded.
   *
   * @example
   * // my_str = 'param=1&anotherParam=2';
   * {{ my_str|url_encode }}
   * // => param%3D1%26anotherParam%3D2
   *
   * @param  {*} input
   * @return {*}       URL-encoded string.
   */
  exports.url_encode = function (input) {
    var out = iterateFilter.apply(exports.url_encode, arguments);
    if (out !== undefined) {
      return out;
    }
    return encodeURIComponent(input);
  };
  
  /**
   * URL-decode a string. If an object or array is passed, all values will be URL-decoded.
   *
   * @example
   * // my_str = 'param%3D1%26anotherParam%3D2';
   * {{ my_str|url_decode }}
   * // => param=1&anotherParam=2
   *
   * @param  {*} input
   * @return {*}       URL-decoded string.
   */
  exports.url_decode = function (input) {
    var out = iterateFilter.apply(exports.url_decode, arguments);
    if (out !== undefined) {
      return out;
    }
    return decodeURIComponent(input);
  };
  