'use strict';

var isNumber = require('is-number');
var util = require('handlebars-utils');
var utils = require('./utils');
var helpers = module.exports;

/**
 * Append the specified `suffix` to the given string.
 *
 * ```handlebars
 * <!-- given that "item.stem" is "foo" -->
 * {{append item.stem ".html"}}
 * <!-- results in:  'foo.html' -->
 * ```
 * @param {String} `str`
 * @param {String} `suffix`
 * @return {String}
 * @api public
 */

helpers.append = function(str, suffix) {
  if (typeof str === 'string' && typeof suffix === 'string') {
    return str + suffix;
  }
  return str;
};

/**
 * camelCase the characters in the given `string`.
 *
 * ```handlebars
 * {{camelcase "foo bar baz"}};
 * <!-- results in:  'fooBarBaz' -->
 * ```
 * @param {String} `string` The string to camelcase.
 * @return {String}
 * @api public
 */

helpers.camelcase = function(str) {
  if (!util.isString(str)) return '';
  return utils.changecase(str, function(ch) {
    return ch.toUpperCase();
  });
};

/**
 * Capitalize the first word in a sentence.
 *
 * ```handlebars
 * {{capitalize "foo bar baz"}}
 * <!-- results in:  "Foo bar baz" -->
 * ```
 * @param {String} `str`
 * @return {String}
 * @api public
 */

helpers.capitalize = function(str) {
  if (!util.isString(str)) return '';
  return str.charAt(0).toUpperCase() + str.slice(1);
};

/**
 * Capitalize all words in a string.
 *
 * ```handlebars
 * {{capitalizeAll "foo bar baz"}}
 * <!-- results in:  "Foo Bar Baz" -->
 * ```
 * @param {String} `str`
 * @return {String}
 * @api public
 */

helpers.capitalizeAll = function(str) {
  if (!util.isString(str)) return '';
  if (util.isString(str)) {
    return str.replace(/\w\S*/g, function(word) {
      return helpers.capitalize(word);
    });
  }
};

/**
 * Center a string using non-breaking spaces
 *
 * @param {String} `str`
 * @param {String} `spaces`
 * @return {String}
 * @api public
 */

helpers.center = function(str, spaces) {
  if (!util.isString(str)) return '';
  var space = '';
  var i = 0;
  while (i < spaces) {
    space += '&nbsp;';
    i++;
  }
  return space + str + space;
};

/**
 * Like trim, but removes both extraneous whitespace **and
 * non-word characters** from the beginning and end of a string.
 *
 * ```handlebars
 * {{chop "_ABC_"}}
 * <!-- results in:  'ABC' -->
 *
 * {{chop "-ABC-"}}
 * <!-- results in:  'ABC' -->
 *
 * {{chop " ABC "}}
 * <!-- results in:  'ABC' -->
 * ```
 * @param {String} `string` The string to chop.
 * @return {String}
 * @api public
 */

helpers.chop = function(str) {
  if (!util.isString(str)) return '';
  return utils.chop(str);
};

/**
 * dash-case the characters in `string`. Replaces non-word
 * characters and periods with hyphens.
 *
 * ```handlebars
 * {{dashcase "a-b-c d_e"}}
 * <!-- results in:  'a-b-c-d-e' -->
 * ```
 * @param {String} `string`
 * @return {String}
 * @api public
 */

helpers.dashcase = function(str) {
  if (!util.isString(str)) return '';
  return utils.changecase(str, function(ch) {
    return '-' + ch;
  });
};

/**
 * dot.case the characters in `string`.
 *
 * ```handlebars
 * {{dotcase "a-b-c d_e"}}
 * <!-- results in:  'a.b.c.d.e' -->
 * ```
 * @param {String} `string`
 * @return {String}
 * @api public
 */

helpers.dotcase = function(str) {
  if (!util.isString(str)) return '';
  return utils.changecase(str, function(ch) {
    return '.' + ch;
  });
};

/**
 * Lowercase all of the characters in the given string. Alias for [lowercase](#lowercase).
 *
 * ```handlebars
 * {{downcase "aBcDeF"}}
 * <!-- results in:  'abcdef' -->
 * ```
 * @param {String} `string`
 * @return {String}
 * @alias lowercase
 * @api public
 */

helpers.downcase = function() {
  return helpers.lowercase.apply(this, arguments);
};

/**
 * Truncates a string to the specified `length`, and appends
 * it with an elipsis, `…`.
 *
 * ```handlebars
 * {{ellipsis (sanitize "<span>foo bar baz</span>"), 7}}
 * <!-- results in:  'foo bar…' -->
 * {{ellipsis "foo bar baz", 7}}
 * <!-- results in:  'foo bar…' -->
 * ```
 * @param {String} `str`
 * @param {Number} `length` The desired length of the returned string.
 * @return {String} The truncated string.
 * @api public
 */

helpers.ellipsis = function(str, limit) {
  if (util.isString(str)) {
    if (str.length <= limit) {
      return str;
    }
    return helpers.truncate(str, limit) + '…';
  }
};

/**
 * Replace spaces in a string with hyphens.
 *
 * ```handlebars
 * {{hyphenate "foo bar baz qux"}}
 * <!-- results in:  "foo-bar-baz-qux" -->
 * ```
 * @param {String} `str`
 * @return {String}
 * @api public
 */

helpers.hyphenate = function(str) {
  if (!util.isString(str)) return '';
  return str.split(' ').join('-');
};

/**
 * Return true if `value` is a string.
 *
 * ```handlebars
 * {{isString "foo"}}
 * <!-- results in:  'true' -->
 * ```
 * @param {String} `value`
 * @return {Boolean}
 * @api public
 */

helpers.isString = function(value) {
  return typeof value === 'string';
};

/**
 * Lowercase all characters in the given string.
 *
 * ```handlebars
 * {{lowercase "Foo BAR baZ"}}
 * <!-- results in:  'foo bar baz' -->
 * ```
 * @param {String} `str`
 * @return {String}
 * @api public
 */

helpers.lowercase = function(str) {
  if (util.isObject(str) && str.fn) {
    return str.fn(this).toLowerCase();
  }
  if (!util.isString(str)) return '';
  return str.toLowerCase();
};

/**
 * Return the number of occurrences of `substring` within the
 * given `string`.
 *
 * ```handlebars
 * {{occurrences "foo bar foo bar baz" "foo"}}
 * <!-- results in:  2 -->
 * ```
 * @param {String} `str`
 * @param {String} `substring`
 * @return {Number} Number of occurrences
 * @api public
 */

helpers.occurrences = function(str, substring) {
  if (!util.isString(str)) return '';
  var len = substring.length;
  var pos = 0;
  var n = 0;

  while ((pos = str.indexOf(substring, pos)) > -1) {
    n++;
    pos += len;
  }
  return n;
};

/**
 * PascalCase the characters in `string`.
 *
 * ```handlebars
 * {{pascalcase "foo bar baz"}}
 * <!-- results in:  'FooBarBaz' -->
 * ```
 * @param {String} `string`
 * @return {String}
 * @api public
 */

helpers.pascalcase = function(str) {
  if (!util.isString(str)) return '';
  str = utils.changecase(str, function(ch) {
    return ch.toUpperCase();
  });
  return str.charAt(0).toUpperCase() + str.slice(1);
};

/**
 * path/case the characters in `string`.
 *
 * ```handlebars
 * {{pathcase "a-b-c d_e"}}
 * <!-- results in:  'a/b/c/d/e' -->
 * ```
 * @param {String} `string`
 * @return {String}
 * @api public
 */

helpers.pathcase = function(str) {
  if (!util.isString(str)) return '';
  return utils.changecase(str, function(ch) {
    return '/' + ch;
  });
};

/**
 * Replace spaces in the given string with pluses.
 *
 * ```handlebars
 * {{plusify "foo bar baz"}}
 * <!-- results in:  'foo+bar+baz' -->
 * ```
 * @param {String} `str` The input string
 * @return {String} Input string with spaces replaced by plus signs
 * @source Stephen Way <https://github.com/stephenway>
 * @api public
 */

helpers.plusify = function(str, ch) {
  if (!util.isString(str)) return '';
  if (!util.isString(ch)) ch = ' ';
  return str.split(ch).join('+');
};

/**
 * Prepends the given `string` with the specified `prefix`.
 *
 * ```handlebars
 * <!-- given that "val" is "bar" -->
 * {{prepend val "foo-"}}
 * <!-- results in:  'foo-bar' -->
 * ```
 * @param {String} `str`
 * @param {String} `prefix`
 * @return {String}
 * @api public
 */

helpers.prepend = function(str, prefix) {
  return typeof str === 'string' && typeof prefix === 'string'
    ? (prefix + str)
    : str;
};

/**
 * Render a block without processing mustache templates inside the block.
 *
 * ```handlebars
 * {{{{#raw}}}}
 * {{foo}}
 * {{{{/raw}}}}
 * <!-- results in:  '{{foo}}' -->
 * ```
 *
 * @param {Object} `options`
 * @return {String}
 * @block
 * @api public
 */

helpers.raw = function(options) {
  var str = options.fn();
  var opts = util.options(this, options);
  if (opts.escape !== false) {
    var idx = 0;
    while (((idx = str.indexOf('{{', idx)) !== -1)) {
      if (str[idx - 1] !== '\\') {
        str = str.slice(0, idx) + '\\' + str.slice(idx);
      }
      idx += 3;
    }
  }
  return str;
};

/**
 * Remove all occurrences of `substring` from the given `str`.
 *
 * ```handlebars
 * {{remove "a b a b a b" "a "}}
 * <!-- results in:  'b b b' -->
 * ```
 * @param {String} `str`
 * @param {String} `substring`
 * @return {String}
 * @api public
 */

helpers.remove = function(str, ch) {
  if (!util.isString(str)) return '';
  if (!util.isString(ch)) return str;
  return str.split(ch).join('');
};

/**
 * Remove the first occurrence of `substring` from the given `str`.
 *
 * ```handlebars
 * {{remove "a b a b a b" "a"}}
 * <!-- results in:  ' b a b a b' -->
 * ```
 * @param {String} `str`
 * @param {String} `substring`
 * @return {String}
 * @api public
 */

helpers.removeFirst = function(str, ch) {
  if (!util.isString(str)) return '';
  if (!util.isString(ch)) return str;
  return str.replace(ch, '');
};

/**
 * Replace all occurrences of substring `a` with substring `b`.
 *
 * ```handlebars
 * {{replace "a b a b a b" "a" "z"}}
 * <!-- results in:  'z b z b z b' -->
 * ```
 * @param {String} `str`
 * @param {String} `a`
 * @param {String} `b`
 * @return {String}
 * @api public
 */

helpers.replace = function(str, a, b) {
  if (!util.isString(str)) return '';
  if (!util.isString(a)) return str;
  if (!util.isString(b)) b = '';
  return str.split(a).join(b);
};

/**
 * Replace the first occurrence of substring `a` with substring `b`.
 *
 * ```handlebars
 * {{replace "a b a b a b" "a" "z"}}
 * <!-- results in:  'z b a b a b' -->
 * ```
 * @param {String} `str`
 * @param {String} `a`
 * @param {String} `b`
 * @return {String}
 * @api public
 */

helpers.replaceFirst = function(str, a, b) {
  if (!util.isString(str)) return '';
  if (!util.isString(a)) return str;
  if (!util.isString(b)) b = '';
  return str.replace(a, b);
};

/**
 * Reverse a string.
 *
 * ```handlebars
 * {{reverse "abcde"}}
 * <!-- results in:  'edcba' -->
 * ```
 * @param {String} `str`
 * @return {String}
 * @api public
 */

helpers.reverse = function(str) {
  if (!util.isString(str)) return '';
  return str.split('').reverse().join('');
};

/**
 * Sentence case the given string
 *
 * ```handlebars
 * {{sentence "hello world. goodbye world."}}
 * <!-- results in:  'Hello world. Goodbye world.' -->
 * ```
 * @param {String} `str`
 * @return {String}
 * @api public
 */

helpers.sentence = function(str) {
  if (!util.isString(str)) return '';
  return str.replace(/((?:\S[^\.\?\!]*)[\.\?\!]*)/g, function(txt) {
    return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
  });
};

/**
 * snake_case the characters in the given `string`.
 *
 * ```handlebars
 * {{snakecase "a-b-c d_e"}}
 * <!-- results in:  'a_b_c_d_e' -->
 * ```
 * @param {String} `string`
 * @return {String}
 * @api public
 */

helpers.snakecase = function(str) {
  if (!util.isString(str)) return '';
  return utils.changecase(str, function(ch) {
    return '_' + ch;
  });
};

/**
 * Split `string` by the given `character`.
 *
 * ```handlebars
 * {{split "a,b,c" ","}}
 * <!-- results in:  ['a', 'b', 'c'] -->
 * ```
 * @param {String} `string` The string to split.
 * @return {String} `character` Default is an empty string.
 * @api public
 */

helpers.split = function(str, ch) {
  if (!util.isString(str)) return '';
  if (!util.isString(ch)) ch = ',';
  return str.split(ch);
};

/**
 * Tests whether a string begins with the given prefix.
 *
 * ```handlebars
 * {{#startsWith "Goodbye" "Hello, world!"}}
 *   Whoops
 * {{else}}
 *   Bro, do you even hello world?
 * {{/startsWith}}
 * ```
 * @contributor Dan Fox <http://github.com/iamdanfox>
 * @param {String} `prefix`
 * @param {String} `testString`
 * @param {String} `options`
 * @return {String}
 * @block
 * @api public
 */

helpers.startsWith = function(prefix, str, options) {
  var args = [].slice.call(arguments);
  options = args.pop();
  if (util.isString(str) && str.indexOf(prefix) === 0) {
    return options.fn(this);
  }
  if (typeof options.inverse === 'function') {
    return options.inverse(this);
  }
  return '';
};

/**
 * Title case the given string.
 *
 * ```handlebars
 * {{titleize "this is title case"}}
 * <!-- results in:  'This Is Title Case' -->
 * ```
 * @param {String} `str`
 * @return {String}
 * @api public
 */

helpers.titleize = function(str) {
  if (!util.isString(str)) return '';
  var title = str.replace(/[- _]+/g, ' ');
  var words = title.split(' ');
  var len = words.length;
  var res = [];
  var i = 0;
  while (len--) {
    var word = words[i++];
    res.push(exports.capitalize(word));
  }
  return res.join(' ');
};

/**
 * Removes extraneous whitespace from the beginning and end
 * of a string.
 *
 * ```handlebars
 * {{trim " ABC "}}
 * <!-- results in:  'ABC' -->
 * ```
 * @param {String} `string` The string to trim.
 * @return {String}
 * @api public
 */

helpers.trim = function(str) {
  return typeof str === 'string' ? str.trim() : '';
};

/**
 * Removes extraneous whitespace from the beginning of a string.
 *
 * ```handlebars
 * {{trim " ABC "}}
 * <!-- results in:  'ABC ' -->
 * ```
 * @param {String} `string` The string to trim.
 * @return {String}
 * @api public
 */

helpers.trimLeft = function(str) {
  if (util.isString(str)) {
    return str.replace(/^\s+/, '');
  }
};

/**
 * Removes extraneous whitespace from the end of a string.
 *
 * ```handlebars
 * {{trimRight " ABC "}}
 * <!-- results in:  ' ABC' -->
 * ```
 * @param {String} `string` The string to trim.
 * @return {String}
 * @api public
 */

helpers.trimRight = function(str) {
  if (util.isString(str)) {
    return str.replace(/\s+$/, '');
  }
};

/**
 * Truncate a string to the specified `length`. Also see [ellipsis](#ellipsis).
 *
 * ```handlebars
 * truncate("foo bar baz", 7);
 * <!-- results in:  'foo bar' -->
 * truncate(sanitize("<span>foo bar baz</span>", 7));
 * <!-- results in:  'foo bar' -->
 * ```
 * @param {String} `str`
 * @param {Number} `limit` The desired length of the returned string.
 * @param {String} `suffix` Optionally supply a string to use as a suffix to
 * denote when the string has been truncated. Otherwise an ellipsis (`…`) will be used.
 * @return {String} The truncated string.
 * @api public
 */

helpers.truncate = function(str, limit, suffix) {
  if (util.isString(str)) {
    if (typeof suffix !== 'string') {
      suffix = '';
    }
    if (str.length > limit) {
      return str.slice(0, limit - suffix.length) + suffix;
    }
    return str;
  }
};

/**
 * Truncate a string to have the specified number of words.
 * Also see [truncate](#truncate).
 *
 * ```handlebars
 * truncateWords("foo bar baz", 1);
 * <!-- results in:  'foo…' -->
 * truncateWords("foo bar baz", 2);
 * <!-- results in:  'foo bar…' -->
 * truncateWords("foo bar baz", 3);
 * <!-- results in:  'foo bar baz' -->
 * ```
 * @param {String} `str`
 * @param {Number} `limit` The desired length of the returned string.
 * @param {String} `suffix` Optionally supply a string to use as a suffix to
 * denote when the string has been truncated.
 * @return {String} The truncated string.
 * @api public
 */

helpers.truncateWords = function(str, count, suffix) {
  if (util.isString(str) && isNumber(count)) {
    if (typeof suffix !== 'string') {
      suffix = '…';
    }

    var num = Number(count);
    var arr = str.split(/[ \t]/);
    if (num > arr.length) {
      arr = arr.slice(0, num);
    }

    var val = arr.join(' ').trim();
    return val + suffix;
  }
};

/**
 * Uppercase all of the characters in the given string. Alias for [uppercase](#uppercase).
 *
 * ```handlebars
 * {{upcase "aBcDeF"}}
 * <!-- results in:  'ABCDEF' -->
 * ```
 * @param {String} `string`
 * @return {String}
 * @alias uppercase
 * @api public
 */

helpers.upcase = function() {
  return helpers.uppercase.apply(this, arguments);
};

/**
 * Uppercase all of the characters in the given string. If used as a
 * block helper it will uppercase the entire block. This helper
 * does not support inverse blocks.
 *
 * ```handlebars
 * {{uppercase "aBcDeF"}}
 * <!-- results in:  'ABCDEF' -->
 * ```
 * @related capitalize capitalizeAll
 * @param {String} `str` The string to uppercase
 * @param {Object} `options` Handlebars options object
 * @return {String}
 * @block
 * @api public
 */

helpers.uppercase = function(str) {
  if (util.isObject(str) && str.fn) {
    return str.fn(this).toUpperCase();
  }
  if (!util.isString(str)) return '';
  return str.toUpperCase();
};
