'use strict';

var spawn = require("cross-spawn");
var _ = require("lodash");
var elmBinaryName = "elm";
var fs = require("fs");
var path = require("path");
var temp = require("temp").track();
var findAllDependencies = require("find-elm-dependencies").findAllDependencies;

var defaultOptions     = {
  spawn:      spawn,
  cwd:        undefined,
  pathToElm:  undefined,
  help:       undefined,
  output:     undefined,
  report:     undefined,
  warn:       undefined,
  debug:      undefined,
  verbose:    false,
  processOpts: undefined,
  docs:       undefined,
};

var supportedOptions = _.keys(defaultOptions);

function prepareSources(sources) {
  if (!(sources instanceof Array || typeof sources === "string")) {
    throw "compile() received neither an Array nor a String for its sources argument.";
  }

  return typeof sources === "string" ? [sources] : sources;
}

function prepareOptions(options, spawnFn) {
  return _.defaults({ spawn: spawnFn }, options, defaultOptions);
}

function prepareProcessArgs(sources, options) {
  var preparedSources = prepareSources(sources);
  var compilerArgs = compilerArgsFromOptions(options);

  return ["make"].concat(preparedSources ? preparedSources.concat(compilerArgs) : compilerArgs);
}

function prepareProcessOpts(options) {
  var env = _.merge({LANG: 'en_US.UTF-8'}, process.env);
  return _.merge({ env: env, stdio: "inherit", cwd: options.cwd }, options.processOpts);

}

function runCompiler(sources, options, pathToElm) {
  if (typeof options.spawn !== "function") {
    throw "options.spawn was a(n) " + (typeof options.spawn) + " instead of a function.";
  }

  var processArgs = prepareProcessArgs(sources, options);
  var processOpts = prepareProcessOpts(options);

  if (options.verbose) {
    console.log(["Running", pathToElm].concat(processArgs).join(" "));
  }

  return options.spawn(pathToElm, processArgs, processOpts);
}

function compilerErrorToString(err, pathToElm) {
  if ((typeof err === "object") && (typeof err.code === "string")) {
    switch (err.code) {
      case "ENOENT":
        return ("Could not find Elm compiler \"" + pathToElm + "\". Is it installed?")

      case "EACCES":
        return ("Elm compiler \"" + pathToElm + "\" did not have permission to run. Do you need to give it executable permissions?");

      default:
        return ("Error attempting to run Elm compiler \"" + pathToElm + "\":\n" + err);
    }
  } else {
    return ("Exception thrown when attempting to run Elm compiler " + JSON.stringify(pathToElm) + ":\n");
  }
}

function compileSync(sources, options) {
  var optionsWithDefaults = prepareOptions(options, options.spawn || spawn.sync);
  var pathToElm = options.pathToElm || elmBinaryName;

  try {
    return runCompiler(sources, optionsWithDefaults, pathToElm);
  } catch (err) {
    throw compilerErrorToString(err, pathToElm);
  }
}

function compile(sources, options) {
  var optionsWithDefaults = prepareOptions(options, options.spawn || spawn);
  var pathToElm = options.pathToElm || elmBinaryName;


  try {
    return runCompiler(sources, optionsWithDefaults, pathToElm)
      .on('error', function(err) { throw(err); });
  } catch (err) {
    throw compilerErrorToString(err, pathToElm);
  }
}

// write compiled Elm to a string output
// returns a Promise which will contain a Buffer of the text
// If you want html instead of js, use options object to set
// output to a html file instead
// creates a temp file and deletes it after reading
function compileToString(sources, options){
  if (typeof options.output === "undefined"){
    options.output = '.js';
  }

  return new Promise(function(resolve, reject){
    temp.open({ suffix: options.output }, function(err, info){
      if (err){
        return reject(err);
      }

      options.output = info.path;
      options.processOpts = { stdio: 'pipe' }

      var compiler;

      try {
        compiler = compile(sources, options);
      } catch(compileError) {
        return reject(compileError);
      }

      compiler.stdout.setEncoding("utf8");
      compiler.stderr.setEncoding("utf8");

      var output = '';
      compiler.stdout.on('data', function(chunk) {
        output += chunk;
      });
      compiler.stderr.on('data', function(chunk) {
        output += chunk;
      });

      compiler.on("close", function(exitCode) {
          if (exitCode !== 0) {
            return reject(new Error('Compilation failed\n' + output));
          } else if (options.verbose) {
            console.log(output);
          }

          fs.readFile(info.path, {encoding: "utf8"}, function(err, data){
            return err ? reject(err) : resolve(data);
          });
        });
    });
  });
}

function compileToStringSync(sources, options) {
  if (typeof options.output === "undefined"){
    options.output = '.js';
  }

  const file = temp.openSync({ suffix: options.output });
  options.output = file.path;
  compileSync(sources, options);

  return fs.readFileSync(file.path, {encoding: "utf8"});
}

// Converts an object of key/value pairs to an array of arguments suitable
// to be passed to child_process.spawn for elm-make.
function compilerArgsFromOptions(options) {
  return _.flatten(_.map(options, function(value, opt) {
    if (value) {
      switch(opt) {
        case "help":   return ["--help"];
        case "output": return ["--output", value];
        case "report": return ["--report", value];
        case "warn":   return ["--warn"];
        case "debug":  return ["--debug"];
        case "docs":   return ["--docs", value]
        case "runtimeOptions":   return ["+RTS", value, "-RTS"]
        default:
          if (supportedOptions.indexOf(opt) === -1) {
              if (opt === "yes") {
                throw new Error('node-elm-compiler received the `yes` option, but that was removed in Elm 0.19. Try re-running without passing the `yes` option.');
              } else {
                throw new Error('node-elm-compiler was given an unrecognized Elm compiler option: ' + opt);
              }
          }

          return [];
      }
    } else {
      return [];
    }
  }));
}

module.exports = {
  compile: compile,
  compileSync: compileSync,
  compileWorker: require("./worker.js")(compile),
  compileToString: compileToString,
  compileToStringSync: compileToStringSync,
  findAllDependencies: findAllDependencies,
  _prepareProcessArgs: prepareProcessArgs
};

@@ -444,33 +444,8 @@
 ed,%0A
-  warn:       undefined,%0A
   de
@@ -537,16 +537,41 @@
 efined,%0A
+  optimize:   undefined,%0A
 %7D;%0A%0Avar 
@@ -5411,50 +5411,8 @@
 e%5D;%0A
-        case %22warn%22:   return %5B%22--warn%22%5D;%0A
     
@@ -5498,16 +5498,66 @@
  value%5D%0A
+        case %22optimize%22:   return %5B%22--optimize%22%5D;%0A
         

Support Elm 0.19's --optimize flag