'use strict';

var _ = require('lodash'),
    listing = require('./listing'),
    minifyEscapes = listing.minifyEscapes;

/** Used to minify variables and string values to a single character. */
var minNames = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
_.each(minNames, function(value) { minNames.push(value + value); });

/*----------------------------------------------------------------------------*/

/**
 * Pre-process `source` to prepare it for minification.
 *
 * @param {string} [source=''] The source to process.
 * @param {Object} [options={}] The options object.
 * @returns {string} Returns the processed source.
 */
function preprocess(source, options) {
  source = _.toString(source);
  options = _.cloneDeep(options);

  // Remove unrecognized JSDoc tags so the Closure Compiler won't complain.
  source = source.replace(/@(?:alias|category)\b.*/g, '');

  if (options.isTemplate) {
    return source;
  }
  // Remove whitespace from `_.template` related regexes.
  source = source.replace(/(reEmptyString\w+\s*=)([\s\S]+?)(?=[,;]\n)/g, function(match, left, value) {
    return left + value.replace(/\s|\\n/g, '');
  });

  // Remove whitespace from `_.template`.
  source = source.replace(/^( *)function template\b[\s\S]+?\n\1}/m, function(snippet) {
    // Remove whitespace from string literals.
    snippet = snippet.replace(/^((?:[ "'$\w]+:)?\s*)"[^"\n\\]*?(?:\\.[^"\n\\]*?)*"|'[^'\n\\]*?(?:\\.[^'\n\\]*?)*'/gm, function(string, left) {
      // Clip `string` after an object literal property name or leading spaces.
      if (left) {
        string = string.slice(left.length);
      }
      // Avoids removing the '\n' of the `stringEscapes` object.
      string = string.replace(/\[object |delete |else (?!{)|function | in | instanceof |return\s+["'$\w]|throw |typeof |use strict|var |# |(["'])(?:\\n| )\1|\\\\n|\\n|\s+/g, function(match) {
        return match == false || match == '\\n' ? '' : match;
      });
      // Unclip `string`.
      return (left || '') + string;
    });

    // Remove newline from double-quoted strings.
    snippet = snippet
      .replace('"__p += \'"', '"__p+=\'"')
      .replace('"\';\\n"', '"\';"');

    // Add a newline back so "evaluate" delimiters can support single line comments.
    snippet = snippet.replace('";__p+=\'"', '";\\n__p+=\'"');

    // Remove default `sourceURL` value.
    return snippet.replace(/^( *)(var\s+sourceURL\s*=\s*)[\s\S]+?(?=;\n$)/m, function(match, indent, left) {
      return indent + left + [
        "hasOwnProperty.call(options, 'sourceURL')",
        indent + "  ? ('//# sourceURL=' +",
        indent + "     (options.sourceURL + '').replace(/\\s/g, ' ') +",
        indent + "     '\\n')",
        indent + "  : ''"
      ].join('\n');
    });
  });

  // Minify internal properties.
  (function() {
    var funcNames = [
      'baseOrderBy',
      'baseSortBy',
      'compareMultiple',
    ];

    var props = [
      'criteria',
      'index',
      'value'
    ];

    // Minify properties used in `funcNames` functions.
    var snippets = source.match(RegExp('^( *)(?:var|function) +(?:' + funcNames.join('|') + ')\\b[\\s\\S]+?\\n\\1}', 'gm'));
    _.each(snippets, function(snippet) {
      var modified = snippet;
      _.each(props, function(prop, index) {
        var minName = minNames[index],
            reDotProp = RegExp('\\.' + prop + '\\b', 'g'),
            rePropName = RegExp("'" + prop + "'", 'g');

        modified = modified
          .replace(rePropName, "'" + minName + "'")
          .replace(reDotProp, "['" + minName + "']");
      });

      // Replace with modified snippet.
      source = source.replace(snippet, function() {
        return modified;
      });
    });
  }());

  // Add brackets to escaped properties so the Closure Compiler won't mung them.
  // See http://code.google.com/closure/compiler/docs/api-tutorial3.html#export.
  return source.replace(RegExp('(["\'])(?:(?!\\1)[^\\n\\\\]|\\\\.)*\\1|\\.(' + minifyEscapes.join('|') + ')\\b', 'g'), function(match, quote, prop) {
    return quote ? match : "['" + prop + "']";
  });
}

module.exports = preprocess;
