/* eslint-env node */
/* eslint-disable no-console */
'use strict';

// istanbul ignore next

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

// istanbul ignore next

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

var _neoAsync = require('neo-async');

var _neoAsync2 = _interopRequireDefault(_neoAsync);

var _fs = require('fs');

var _fs2 = _interopRequireDefault(_fs);

var _handlebars = require('./handlebars');

var Handlebars = _interopRequireWildcard(_handlebars);

var _path = require('path');

var _sourceMap = require('source-map');

module.exports.loadTemplates = function (opts, callback) {
  loadStrings(opts, function (err, strings) {
    if (err) {
      callback(err);
    } else {
      loadFiles(opts, function (err, files) {
        if (err) {
          callback(err);
        } else {
          opts.templates = strings.concat(files);
          callback(undefined, opts);
        }
      });
    }
  });
};

function loadStrings(opts, callback) {
  var strings = arrayCast(opts.string),
      names = arrayCast(opts.name);

  if (names.length !== strings.length && strings.length > 1) {
    return callback(new Handlebars.Exception('Number of names did not match the number of string inputs'));
  }

  _neoAsync2['default'].map(strings, function (string, callback) {
    if (string !== '-') {
      callback(undefined, string);
    } else {
      (function () {
        // Load from stdin
        var buffer = '';
        process.stdin.setEncoding('utf8');

        process.stdin.on('data', function (chunk) {
          buffer += chunk;
        });
        process.stdin.on('end', function () {
          callback(undefined, buffer);
        });
      })();
    }
  }, function (err, strings) {
    strings = strings.map(function (string, index) {
      return {
        name: names[index],
        path: names[index],
        source: string
      };
    });
    callback(err, strings);
  });
}

function loadFiles(opts, callback) {
  // Build file extension pattern
  var extension = (opts.extension || 'handlebars').replace(/[\\^$*+?.():=!|{}\-[\]]/g, function (arg) {
    return '\\' + arg;
  });
  extension = new RegExp('\\.' + extension + '$');

  var ret = [],
      queue = (opts.files || []).map(function (template) {
    return { template: template, root: opts.root };
  });
  _neoAsync2['default'].whilst(function () {
    return queue.length;
  }, function (callback) {
    var _queue$shift = queue.shift();

    var path = _queue$shift.template;
    var root = _queue$shift.root;

    _fs2['default'].stat(path, function (err, stat) {
      if (err) {
        return callback(new Handlebars.Exception('Unable to open template file "' + path + '"'));
      }

      if (stat.isDirectory()) {
        opts.hasDirectory = true;

        _fs2['default'].readdir(path, function (err, children) {
          /* istanbul ignore next : Race condition that being too lazy to test */
          if (err) {
            return callback(err);
          }
          children.forEach(function (file) {
            var childPath = path + '/' + file;

            if (extension.test(childPath) || _fs2['default'].statSync(childPath).isDirectory()) {
              queue.push({ template: childPath, root: root || path });
            }
          });

          callback();
        });
      } else {
        _fs2['default'].readFile(path, 'utf8', function (err, data) {
          /* istanbul ignore next : Race condition that being too lazy to test */
          if (err) {
            return callback(err);
          }

          if (opts.bom && data.indexOf('﻿') === 0) {
            data = data.substring(1);
          }

          // Clean the template name
          var name = path;
          if (!root) {
            name = _path.basename(name);
          } else if (name.indexOf(root) === 0) {
            name = name.substring(root.length + 1);
          }
          name = name.replace(extension, '');

          ret.push({
            path: path,
            name: name,
            source: data
          });

          callback();
        });
      }
    });
  }, function (err) {
    if (err) {
      callback(err);
    } else {
      callback(undefined, ret);
    }
  });
}

module.exports.cli = function (opts) {
  if (opts.version) {
    console.log(Handlebars.VERSION);
    return;
  }

  if (!opts.templates.length && !opts.hasDirectory) {
    throw new Handlebars.Exception('Must define at least one template or directory.');
  }

  if (opts.simple && opts.min) {
    throw new Handlebars.Exception('Unable to minimize simple output');
  }

  var multiple = opts.templates.length !== 1 || opts.hasDirectory;
  if (opts.simple && multiple) {
    throw new Handlebars.Exception('Unable to output multiple templates in simple mode');
  }

  // Force simple mode if we have only one template and it's unnamed.
  if (!opts.amd && !opts.commonjs && opts.templates.length === 1 && !opts.templates[0].name) {
    opts.simple = true;
  }

  // Convert the known list into a hash
  var known = {};
  if (opts.known && !Array.isArray(opts.known)) {
    opts.known = [opts.known];
  }
  if (opts.known) {
    for (var i = 0, len = opts.known.length; i < len; i++) {
      known[opts.known[i]] = true;
    }
  }

  var objectName = opts.partial ? 'Handlebars.partials' : 'templates';

  if (opts.namespace && !isValidNamespace(opts.namespace)) {
    throw new Handlebars.Exception('Invalid namespace format');
  }

  var output = new _sourceMap.SourceNode();
  if (!opts.simple) {
    if (opts.amd) {
      var runtimeModulePath = (opts.handlebarPath || '') + 'handlebars.runtime';
      output.add('define([' + quoteForJavaScript(runtimeModulePath) + '], function(Handlebars) {\n  Handlebars = Handlebars["default"];');
    } else if (opts.commonjs) {
      output.add('var Handlebars = require(' + quoteForJavaScript(opts.commonjs) + ');');
    } else {
      output.add('(function() {\n');
    }
    output.add('  var template = Handlebars.template, templates = ');
    if (opts.namespace) {
      output.add(opts.namespace);
      output.add(' = ');
      output.add(opts.namespace);
      output.add(' || ');
    }
    output.add('{};\n');
  }

  opts.templates.forEach(function (template) {
    var options = {
      knownHelpers: known,
      knownHelpersOnly: opts.o
    };

    if (opts.map) {
      options.srcName = template.path;
    }
    if (opts.data) {
      options.data = true;
    }

    var precompiled = Handlebars.precompile(template.source, options);

    // If we are generating a source map, we have to reconstruct the SourceNode object
    if (opts.map) {
      var consumer = new _sourceMap.SourceMapConsumer(precompiled.map);
      precompiled = _sourceMap.SourceNode.fromStringWithSourceMap(precompiled.code, consumer);
    }

    if (opts.simple) {
      output.add([precompiled, '\n']);
    } else {
      if (!template.name) {
        throw new Handlebars.Exception('Name missing for template');
      }

      if (opts.amd && !multiple) {
        output.add('return ');
      }
      output.add([objectName, '[', quoteForJavaScript(template.name), '] = template(', precompiled, ');\n']);
    }
  });

  // Output the content
  if (!opts.simple) {
    if (opts.amd) {
      if (multiple) {
        output.add(['return ', objectName, ';\n']);
      }
      output.add('});');
    } else if (!opts.commonjs) {
      output.add('})();');
    }
  }

  if (opts.map) {
    output.add('\n//# sourceMappingURL=' + sanitizeSourceMapComment(opts.map) + '\n');
  }

  output = output.toStringWithSourceMap();
  output.map = output.map + '';

  if (opts.min) {
    output = minify(output, opts.map);
  }

  if (opts.map) {
    _fs2['default'].writeFileSync(opts.map, output.map, 'utf8');
  }
  output = output.code;

  if (opts.output) {
    _fs2['default'].writeFileSync(opts.output, output, 'utf8');
  } else {
    console.log(output);
  }
};

function arrayCast(value) {
  value = value != null ? value : [];
  if (!Array.isArray(value)) {
    value = [value];
  }
  return value;
}

/*
 * Safely quotes a value for embedding in generated JavaScript strings
 *
 * Uses JSON.stringify which handles all special characters.
 */
function quoteForJavaScript(value) {
  return JSON.stringify(String(value));
}

/**
 * Validates that a namespace is a legitimate dotted JavaScript identifier
 * (e.g. "App.templates") to prevent arbitrary code injection
 */
function isValidNamespace(namespace) {
  return (/^[A-Za-z_$][A-Za-z0-9_$]*(\.[A-Za-z_$][A-Za-z0-9_$]*)*$/.test(namespace)
  );
}

/**
 * Strips line terminators from source map URLs to prevent injection of new
 * JavaScript lines via the sourceMappingURL comment
 */
function sanitizeSourceMapComment(value) {
  return String(value).replace(/[\r\n\u2028\u2029]/g, '');
}

/**
 * Run uglify to minify the compiled template, if uglify exists in the dependencies.
 *
 * We are using `require` instead of `import` here, because es6-modules do not allow
 * dynamic imports and uglify-js is an optional dependency. Since we are inside NodeJS here, this
 * should not be a problem.
 *
 * @param {string} output the compiled template
 * @param {string} sourceMapFile the file to write the source map to.
 */
function minify(output, sourceMapFile) {
  try {
    // Try to resolve uglify-js in order to see if it does exist
    require.resolve('uglify-js');
  } catch (e) {
    if (e.code !== 'MODULE_NOT_FOUND') {
      // Something else seems to be wrong
      throw e;
    }
    // it does not exist!
    console.error('Code minimization is disabled due to missing uglify-js dependency');
    return output;
  }
  return require('uglify-js').minify(output.code, {
    sourceMap: {
      content: output.map,
      url: sourceMapFile
    }
  });
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,[encoded data, 24960 characters]
