package nxm.sys.intr;

import nxm.sys.lib.Intrinsic;
import nxm.sys.lib.Midas;
import nxm.sys.lib.MidasException;
import nxm.sys.lib.StringUtil;

/** Handle an ASSERT test statement.

    @author Jeff Schoen / Jay Hallam (initial version)
    @author J. Czechowski IV / Neon Ngo (updated version)
 */
public class Assert extends Intrinsic {
  private static final String    ERR_FLAG = " <--"; // Marks errors to make them easy to find.
  private static final TestCount global   = new TestCount();
  private static final TestCount local    = new TestCount();
  private static nxm.sys.lib.Table gStatus=null; // Global output

  /** default constructor */
  public Assert () {}

  @Override
  public int process () {
    nxm.sys.lib.Table group;

    if (MA.find("/INIT")) {
      global.reset();
      local.reset();
      gStatus = new nxm.sys.lib.Table();
      MR.put(MA.getS("/INIT"),gStatus);
      return FINISH;
    }
    String  tableID = MA.getS("/TID","TEST");                  // Name of the test (as inserted into table)
    String  text    = MA.getS("/TEXT",null);                   // Description of the test
    int     ltext   = MA.getL("/LTEXT",80);                    // Total length of an output message
    int     pad     = (MA.getState("/PAD",true))? ltext-Unittest.OUTPUT_OVERHEAD_LENGTH : 0;   // Actual pad length (0=disable padding)
    boolean fail    = false;                                   // Expect a failure, but no Exception
    boolean except  = MA.getState("/EXCEPT",false);            // Expect to catch an exception if true
            group   = MA.getTable("/GROUP");                   // Grouping options
    boolean summary = MA.getState("/SUMMARY");                 // Summary for group
    boolean defDebug= true;

    if (group != null) {
      String txt = group.getS("TEXT");
      if (txt != null) {
        text = (text == null)? txt : txt+" - "+text;
      }
      defDebug = summary;
    }
    else if (summary) {
      throw new MidasException("Can not use /SUMMARY without /GROUP=");
    }

    if (MA.isPresent("/TEST")) {
      fail = !MA.getState("/TEST");

           if (text         != null) text = text + " - "+MA.getRawArgs().trim();
      else if (M.getMacro() == null) text = "ASSERT: "+MA.getRawArgs().trim();
      else                           text = M.getMacro().getName()+": "+MA.getRawArgs().trim();

      if (fail)   text += " fails";
      if (except) text += " throws Exception";
    }

    if (text == null) text = tableID;

    String  msgOut  = StringUtil.padRight(text, pad, '.');  // Output message
    String  stat    = MA.getU("/STAT");                     // result to get PASS, FAIL output
    String  total   = MA.getS("/TOTAL");                    // Output totals (""=don't output)
    String  noop    = null;                                 // NOOP details  (null=not a NOOP)
    boolean test    = false;                                // Results of test (true=PASS, false=FAIL)
    boolean miss    = false;                                // Missing test case (/NOOP="MISS")
    boolean debug   = MA.getState("/DEBUG",defDebug);       // Print debug?
    boolean count   = true;                                 // Count this test?
    String  label   = MA.getU("/LABEL","");                 // Overrides global output table
    boolean hidefail= MA.getState("/HIDEFAIL",false);       // turn on to hide failures from displaying (ONLY USED for testing ASSERT)
    String  passFlag= "";                                   // flag for passing test (usually "")

    // DO TESTING
    if (total.length()>0) {
      TestCount tc = null;

      if (total.equals("TEST"))    { global.add(local); tc = local;  }
      if (total.equals("RESET"))   {                    tc = local; test = true; } // Reset, don't include in global (ONLY USED for testing ASSERT)
      if (total.equals("TESTALL")) { global.add(local); tc = global; }
      if (tc == null) {
        throw new MidasException("Illegal value for /TOTAL= given '"+total+"' expected 'TEST' or 'TESTALL'");
      }
      count = false; // don't count the overall failure
      test  = test || (tc.failCount == 0);
      if (label.length() > 0) MR.put(label, tc.toTable());
      else                    gStatus.put(tableID, tc.toTable());
      local.reset();
    }
    else if (MA.isPresent("/NOOP")) {
      noop = MA.getS("/NOOP");
      miss = noop.equals("MISS");
      if (noop.length() == 0) noop = "N/A";
    }
    else if (except) {
      try {
        MA.logicalTest();     // <- Use of /EXCEPT means that this should throw an exception
        test = false;         // No exception  -> FAIL
      } catch (Exception e) {
        test = true;          // Got exception -> PASS
      }
    }
    else if (summary) {
      int numMiss  = group.getL("MISS",0);
      int numNA    = group.getL("N/A", 0);
      int numPass  = group.getL("PASS",0);
      int numFail  = group.getL("FAIL",0);
      int numTests = numMiss + numNA + numPass + numFail;
      String txt = "PASS="+numPass+" FAIL="+numFail;

      if (numFail == 0) {
        test = true;
        miss = (numTests == 0);
        if (numMiss > 0) { passFlag = ERR_FLAG; txt+=" MISS="+numMiss; miss = true;  }
        if (numNA   > 0) { passFlag = ERR_FLAG; txt+=" N/A="+numNA;    noop = "N/A"; }
      }

      msgOut = text+"."+StringUtil.padLeft("["+txt+"]", pad-text.length()-1, '.');
      group  = null; // don't count as part of group below
    }
    else {
      test = MA.logicalTest();
    }

    if (fail) test = !test; // Looking for it to fail
    // OUTPUT RESULTS
    if (miss) {
      MR.put(stat,"N/A");
      if (debug) M.message(msgOut+"..MISS"+ERR_FLAG, Midas.INFO, null);
      if (count) local.missCount++;
      if (group != null) group.put("MISS", group.getL("MISS",0)+1);
    }
    else if (noop != null) {
      MR.put(stat,"N/A");
      if (debug) M.message(msgOut+".."+noop+ERR_FLAG, Midas.INFO, null);
      if (count) local.noopCount++;
      if (group != null) group.put("N/A", group.getL("N/A",0)+1);
    }
    else if (test) {
      MR.put(stat,"PASS");
      if (debug) M.message(msgOut+"..PASS"+passFlag, Midas.INFO, null);
      if (count) local.passCount++;
      if (group != null) group.put("PASS", group.getL("PASS",0)+1);
    }
    else {
      MR.put(stat,"FAIL");
      if (!hidefail) M.message(msgOut+"..FAIL"+ERR_FLAG, Midas.WARNING, null);
      if (count) local.failCount++;
      if (group != null) group.put("FAIL", group.getL("FAIL",0)+1);
    }
    return FINISH;
  }

  /** Class to hold statistics about the tests. */
  private static class TestCount {
    int passCount, failCount, missCount, noopCount;

    void reset () {
      passCount = failCount = missCount = noopCount = 0;
    }

    void add (TestCount tc) {
      passCount += tc.passCount;
      failCount += tc.failCount;
      missCount += tc.missCount;
      noopCount += tc.noopCount;
    }

    nxm.sys.lib.Table toTable () {
      nxm.sys.lib.Table tbl = new nxm.sys.lib.Table();
      tbl.put("MSG",        ((failCount==0)? "PASSED ALL TESTS" : "FAILED SOME TESTS"));
      tbl.put("STATUS",     ((failCount==0)? 1 : -1));
      tbl.put("TEST_COUNT", (passCount+failCount+missCount+noopCount));
      tbl.put("PASS_COUNT", passCount);
      tbl.put("FAIL_COUNT", failCount);
      tbl.put("MISS_COUNT", missCount);
      tbl.put("NOOP_COUNT", noopCount);
      return tbl;
    }

    @Override
    public String toString () { return "TestCount: "+toTable(); }
  }
}

