//
// Copyright (c) 2008, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   10 Dec 08  Andy Frank  Creation
//

using [java] java.lang
using [java] javax.script

**
** TestRunner is the command line tool to run Fantom unit tests
** against their JavaScript implementations.
**
class TestRunner
{

//////////////////////////////////////////////////////////////////////////
// Main
//////////////////////////////////////////////////////////////////////////

  Void main(Str[] args := Env.cur.args)
  {
    if (args.size != 1)
    {
      help
      return
    }

    // get args
    arg    := args.first
    pod    := arg
    type   := "*"
    method := "*"

    // check for type
    if (pod.contains("::"))
    {
      i := pod.index("::")
      type = pod[i+2..-1]
      pod  = pod[0..i-1]
    }

    // check for method
    if (type.contains("."))
    {
      i := type.index(".")
      method = type[i+1..-1]
      type   = type[0..i-1]
    }

    // create engine and eval pods
    p := Pod.find(pod)
    evalPod(p)

    // run tests
    t1 := Duration.now
    if (type != "*")
    {
      runTests(Type.find("$pod::$type"), method)
    }
    else if (pod != null)
    {
      p.types.each |t| { if (t.fits(Test#) && t.hasFacet(Js#)) runTests(t, "*") }
    }
    else throw Err("Pattern not supported: $arg")
    t2 := Duration.now

    echo("")
    echo("Time: ${(t2-t1).toMillis}ms")
    echo("")
    results
  }

  Void evalPod(Pod p)
  {
    if (engine == null) engine = ScriptEngineManager().getEngineByName("js");
    Runner.evalPodScript(engine, p)
    if (p.name == "testSys") evalTestSys(p)
  }

  private Void evalTestSys(Pod p)
  {
    try
    {
      if (p.name != "testSys") return
      buf := StrBuf()
      out := buf.out

      // index
      JsIndexedProps().write(out, [Pod.find("testSys")])

      // locales
      JsProps.writeProps(out, Pod.find("sys"), `locale/fi.props`, 1sec)
      JsProps.writeProps(out, Pod.find("sys"), `locale/fr.props`, 1sec)
      JsProps.writeProps(out, p, `locale/en-US.props`, 1sec)
      JsProps.writeProps(out, p, `locale/es.props`, 1sec)
      JsProps.writeProps(out, p, `locale/es-MX.props`, 1sec)

      // timezones
      out.printLine((Env.cur.homeDir + `etc/sys/tz.js`).readAllStr)

      // unit db
      JsUnitDatabase().write(out)

      engine.eval(buf.toStr)
    }
    catch (Err e) throw Err("Locale eval failed: $p.name", e)
  }

  Void results()
  {
    if (failureNames.size > 0)
    {
      echo("Failed:")
      failureNames.each |Str s| { echo("  $s") }
      echo("")
    }

    echo("***")
    echo("*** " +
      (failures == 0 ? "All tests passed!" : "$failures  FAILURES") +
      " [$testCount tests, $methodCount methods, $totalVerifyCount verifies]")
    echo("***")
  }

//////////////////////////////////////////////////////////////////////////
// Methods
//////////////////////////////////////////////////////////////////////////

  Void runTests(Type type, Str methodName := "*")
  {
    //if (skip(type, methodName)) return

    echo("")
    methods := methods(type, methodName)
    methods.each |Method m|
    {
      echo("-- Run: ${m}...")
      verifyCount := runTest(m)
      if (verifyCount < 0)
      {
        failures++
        failureNames.add(m.qname)
      }
      else
      {
        echo("   Pass: $m  [$verifyCount]");
        methodCount++
        totalVerifyCount += verifyCount;
      }
    }
    testCount++
  }

  Method[] methods(Type type, Str methodName)
  {
    return type.methods.findAll |Method m->Bool|
    {
      if (m.isAbstract) return false
      if (m.name.startsWith("test"))
      {
        if (methodName == "*") return true
        return methodName == m.name
      }
      return false
    }
  }

  Int runTest(Method m)
  {
    try
    {
      // env dirs
      homeDir := Env.cur.homeDir
      workDir := Env.cur.workDir
      tempDir := Env.cur.tempDir

      js  := "fan.${m.parent.pod}.${m.parent.name}"
      ret := engine.eval(
       "var testRunner = function()
        {
          var test;
          var doCatchErr = function(err)
          {
            if (err == undefined) print('Undefined error\\n');
            else if (err.trace) err.trace();
            else
            {
              var file = err.fileName;   if (file == null) file = 'Unknown';
              var line = err.lineNumber; if (line == null) line = 'Unknown';
              print(err + ' (' + file + ':' + line + ')\\n');
            }
          }

          try
          {
            fan.sys.Env.cur().m_homeDir = fan.sys.File.os($homeDir.osPath.toCode);
            fan.sys.Env.cur().m_workDir = fan.sys.File.os($workDir.osPath.toCode);
            fan.sys.Env.cur().m_tempDir = fan.sys.File.os($tempDir.osPath.toCode);

            test = ${js}.make();
            test.setup();
            test.${m.name}();
            return test.verifyCount;
          }
          catch (err)
          {
            doCatchErr(err);
            return -1;
          }
          finally
          {
            try { test.teardown(); }
            catch (err) { doCatchErr(err); }
          }
        }
        testRunner();")
      return ret->toInt
    }
    catch (Err e)
    {
      echo("")
      echo("TEST FAILED")
      e.trace
      return -1
    }
  }

  Void help()
  {
    echo("Fantom Test");
    echo("Usage:");
    //echo("  fant [options] -all");
    //echo("  fant [options] <pod> [pod]*");
    echo("  fant [options] <pod>");
    echo("  fant [options] <pod>::<test>");
    echo("  fant [options] <pod>::<test>.<method>");
    //echo("Note:");
    //echo("  You can use * to indicate wildcard for all pods");
    //echo("Options:");
    //echo("  -help, -h, -?  print usage help");
    //echo("  -version       print version");
    //echo("  -v             verbose mode");
    //echo("  -all           test all pods");
  }

//////////////////////////////////////////////////////////////////////////
// Fields
//////////////////////////////////////////////////////////////////////////

  ScriptEngine? engine
  Int testCount        := 0
  Int methodCount      := 0
  Int totalVerifyCount := 0
  Int failures         := 0
  Str[] failureNames   := [,]

}