#!/usr/bin/env node

(function() {
  require('source-map-support').install({ hookRequire: true });
  var fs = require("fs");
  var path = require("path");
  var assert = require("assert");
  var prelude = require("prelude-ls");

  global.LiveScript = require("..");
  global.command = require("../lib/command");

  var bold = "\33[0;1m";
  var red = "\33[0;31m";
  var green = "\33[0;32m";
  var reset = "\33[0m";
  function tint(text, color) {
    if (color == null) {
      color = green;
    }
    return color + text + reset;
  }

  function testSyntax(code) {
    try {
      eval(code);
    } catch (e) {
      return false;
    }
    return true;
  }

  var startTime = Date.now();
  var passedTests = 0;
  var failedTests = 0;

  var key;
  for (key in assert) {
    (function (name, func){
      global[name] = function(){
        func.apply(global, arguments);
        ++passedTests;
      };
    }(key, assert[key]));
  }

  global.eq = strictEqual;

  global.throws = function(msg, fun){
    try {
      fun();
    } catch (e) {
      return eq(e != null ? e.message : void 8, msg);
    }
    ok(false, "should throw: " + msg);
  };

  global.compileThrows = function(msg, lno, code){
    try {
      throws(msg + " on line " + lno, function(){
        LiveScript.compile(code);
      });
    } catch (e) {
      e.code = code;
      throw e;
    }
  };

  var activeCommandTests = 0;
  global.commandEq = function(args, arr, afterTests) {
    activeCommandTests++;
    afterTests = afterTests || prelude.id;
    var i = 0;
    var res = [];
    // switch to mocha in the future, better way to deal with async tests
    var maybeTestResults = function() {
        if (i === arr.length - 1) {
            arr.forEach(function (x, j) {
                if (x instanceof RegExp) {
                    ok(x.test(res[j]), "should pass regex:\n" + x + "\n.test\n" + res[j]);
                } else {
                    eq(x, res[j], "should equal:\n" + x + "\n==\n" + res[j]);
                }
            });
            afterTests();
            activeCommandTests--;
        } else if (i >= arr.length) {
            console.error("more results than expected!\n" + args + "\n" + res[i-1]);
        }
    };
    var log = function(msg) {
        res.push(msg);
        maybeTestResults();
        i++;
    };
    var die = function(msg) {
        res.push(msg);
        maybeTestResults();
        i++;
    };

    try {
        command(args, {say: log, warn: log, die: die, sayWithTimestamp: log});
    } catch (e) {
        ok(false, e.message);
    }
  };

  global.fileRead = function(filename) {
      try {
          return fs.readFileSync(filename, {
              encoding: "utf8"
          });
      } catch (e) {
          return "Error: " + e.message;
      }
  };

  global.fileExists = function(filename) {
      try {
          return fs.statSync(filename).isFile();
      } catch (e) {
          console.error(e.message);
          return false;
      }
  };

  global.fileDelete = function(filename) {
      try {
          fs.unlinkSync(filename);
      } catch (e) {
          console.error(e.message);
      }
  };

  process.on("unhandledRejection", function(e) {
      console.error("Promise rejected: " + e.message);
      failedTests++;
  });

  process.once("beforeExit", function(){
      var printResults = function() {
          var time = ((Date.now() - startTime) / 1e3).toFixed(2);
          var message = "passed " + passedTests + " tests in " + time + " seconds";

          console.log(failedTests
            ? tint("failed " + failedTests + " and " + message, red)
            : tint(message));

          if (failedTests) {
            process.exit(1);
          }
      };
      var waitCountdown = 200; // wait at most one second
      var check = function() {
          if (activeCommandTests > 0 && waitCountdown-- > 0) {
              setTimeout(check, 5);
          } else {
              failedTests += activeCommandTests;
              printResults();
          }
      };
      check();
  });

  var files = fs.readdirSync("test");

  if (testSyntax("(async function () {})")) {
    console.log("Testing with async.");
  } else {
    files.splice(files.indexOf("async.ls"), 1);
  }
  if (testSyntax("(async function* () {})")) {
    console.log("Testing with async generators.");
  } else {
    files.splice(files.indexOf("async-generators.ls"), 1);
  }

  files.forEach(function(file){
    var stk, msg, m, ref, num, row, col, that, lines, line;

    if (!/\.ls$/i.test(file)) {
      return;
    }

    var filename = path.join("test", file);
    var code = fs.readFileSync(filename, "utf8");

    try {
      console.log(filename);
      LiveScript.run(code, { filename: filename, map: 'embedded', warn: false });
    } catch (e) {
      ++failedTests;

      if (!(stk = e != null ? e.stack : null)) {
        console.error(e);
      }

      msg = e.message || "" + /^[^]+?(?=\n    at )/.exec(stk);

      m = /^(AssertionError:) "(.+)" (===) "(.+)"$/.exec(msg)
      if (m) {
        for (num = 2; num <= 4; num += 2) {
          m[num] = tint(m[num].replace(/\\n/g, "\n"), bold);
        }
        msg = m.slice(1).join("\n");
      }

      if ((ref = RegExp(filename + ":(\\d+):(\\d+)\\)?$", "m").exec(stk)) != null) {
        row = ref[1];
        col = ref[2];
      } else if ((ref = RegExp(filename + "(js):(\\d+):(\\d+)\\)?$", "m").exec(stk)) != null) {
        row = ref[1];
        col = ref[2];
        code = LiveScript.compile(code, {
          bare: true,
          warn: false
        });
      }

      if (row && col) {
        console.error(tint(msg + "\n" + red + "at " + filename + ":" + row-- + ":" + col--, red));
      } else if (that = /\bon line (\d+)\b/.exec(msg)) {
        console.error(tint(msg, red));
        row = that[1] - 1;
        col = 0;
        if (e.code) {
          code = e.code;
        }
      } else {
        console.error(stk);
        return;
      }

      line = (lines = code.split("\n"))[row];
      lines[row] = line.slice(0, col) + tint(line.slice(col), bold);
      console.error(lines.slice((ref = row - 8) > 0 ? ref : 0, row + 9).join("\n"));
    }
  });
}());
