#ifndef NXM_TEST_HH_
#define NXM_TEST_HH_
//******************************************************************************
// Description: Simple assertion methods/macros and test setup/reporting framework
//              for writing test cases under C++ to test X-Midas APIs under
//              XBC (X-Midas Backwards Compatibility) and possibly under NeXtOpt.
//
// Author : Neon Ngo
// Since: NeXtMidas 2.9.0 (and 2.8.3+)
// Version: $Id: nxm_test.hh,v 1.7 2015/08/04 19:39:47 ntn Exp $
//
/* Typical usage:

   #include <primitive.h>
   #include "nxm_test.hh"  // NeXtMidas C++ test framework for XBC/NeXtOpt

   void mainroutine () {
     nxm_assert_init("DEBUG", "STATUS");

     m_sync();

     // do stuff

     // --- begin tests case ---

     // most common way to run test case
     nxm_assert("test case description", (1 == 2));
     nxm_assert_eq("test case description", expected_int_4, actual_int4);

     // example using assert macros that throws std::runtime_error on failure (not commonly used)
     NXM_ASSERT_THAT("description'", (1 == 1));

     // --- end of test cases ---

     // report results to user and save to overal test result to specified
     nxm_assert_auto_report();

     m_return();
   } // end mainroutine()
*/
//******************************************************************************

#include "cdefs.h"
#include "midas.h"
#include <sstream>    // for ostringstream

const std::string PREPEND_PAD = "  ";

// define _DEBUG_NXM_TEST to enable more debugging
 // #define _DEBUG_NXM_TEST

// The "#" token is a special C++ preprocessor directive that turns a symbol into a string literal in a #define macro
#define NXM_STRINGIFY_HELPER(a) #a
// To properly use stringizing ('#') preprocessing operator all cases (e.g. with __LINE__),
// we need to wrap the above NXM_STRINGIFY2 macro in another macro.
// See http://www.decompile.com/cpp/faq/file_and_line_error_string.htm for more info
#define NXM_STRINGIFY(x) NXM_STRINGIFY_HELPER(x)

// Assert Macro that throws runtime_error if test condition fails. On failure, this
// should print the "test case":
//   1. source file and line number where the failure occurred
//   2. the failure condition (expression)
// WARNING: this will stop program execution if exception is not handled on a test failure
#define NXM_ASSERT_THAT(expression) \
  if (!(expression)) \
    throw std::runtime_error(__FILE__ " line " NXM_STRINGIFY(__LINE__) \
                             ": condition '" NXM_STRINGIFY(expression) "' FAIL <--")

// max length to result name to receive the status of the overall test cases
#define L_assert_res_name 80
// structure to total up the total number of test cases executed, test failures, etc.
typedef struct {
  bool_4 verbose;                      // display messages (like individual test cases as they are executed)
  int_4  num_tests;                    // number of test cases executed
  int_4  num_failed;                   // number of failed test cases
  int_4  res_name_len;                 // length of results label (res_name)
  char   res_name[L_assert_res_name];  // name of results label to put "PASS" or "FAIL" status
} NXM_ASSERT_RESULTS_STRUCT;
#define L_assert_results_struct sizeof(NXM_ASSERT_RESULTS_STRUCT)

// place to store test results in memory.
// put in thread local storage (TLS) to be multi-thread safe
__thread NXM_ASSERT_RESULTS_STRUCT NXM_TestResults = {
  false, // verbose
  0,     // num_tests
  0,     // num_failed
  0,     // res_name_len
};

// *****************************************************************************
// Performs test run initialization (for a set of test cases)
// @param verboseSwitchName      name of switch to use for setting the verbose flag
// @param statusResultSwitchName name of switch that will contain result name to put
//                               final test result status as "PASS" or "FAIL" string
//                               when nxm_assert_put_status() or nxm_assert_auto_report()
// *****************************************************************************
void nxm_assert_init (const std::string verboseSwitchName = "VERBOSE",
                      const std::string statusSwitchName = "") {
  NXM_TestResults.verbose      = m_get_sswitch_def(verboseSwitchName, 1);
  NXM_TestResults.num_tests    = 0;
  NXM_TestResults.num_failed   = 0;
  NXM_TestResults.res_name_len = 0;
  NXM_TestResults.res_name[0]  = '\0';

  fstring_buf fstrbuf(NXM_TestResults.res_name, L_assert_res_name);
  NXM_TestResults.res_name_len = m_get_uswitch(statusSwitchName, fstrbuf);

#if defined(_DEBUG_NXM_TEST)
    cout << "--- nxm_assert_init() --- " << endl
         << " verbose      = " << NXM_TestResults.verbose          << endl
         << " num_tests    = " << NXM_TestResults.num_tests        << endl
         << " num_failed   = " << NXM_TestResults.num_failed       << endl
         << " res_name_len = " << NXM_TestResults.res_name_len     << endl
         << " res_name     = [" << NXM_TestResults.res_name << "]" << endl
         << " verboseSwitchName = " << verboseSwitchName << endl
         << " statusSwitchName  = " << statusSwitchName << endl;
#endif

} // end nxm_assert_init()

// *****************************************************************************
// Assert a test case. increment number tested and mark failure (if result is false).
// if verbose is on, then print output via m_info() for success and m_warning() for failures.
// *****************************************************************************
void nxm_assert (const std::string message, const bool testresult) {
  NXM_TestResults.num_tests++;    // increment number of tests executed

  if (!testresult) {
    NXM_TestResults.num_failed++; // increment number of failed tests
  }
  if (NXM_TestResults.verbose) {
    // pad string len is 63 (to account for prefix and postfix (17 chars) in output, to pad out to 80 chars terminal
    std::string pad = "..............................................................";
    size_t lmsg = message.size();
    size_t lpad = (pad.size() <= lmsg)? 0: (pad.size()-lmsg);
    std::ostringstream ostr; // output stringstream for output messages
    ostr << PREPEND_PAD << message << " " << pad.substr(0, lpad) << (testresult? ". PASS" : ". FAIL <--");
    if (testresult) { m_info(ostr.str());    } // print info for success
    else            { m_warning(ostr.str()); } // print warning for failures
  }
} // end nxm_assert()

// *****************************************************************************
// Assert a test case for two numbers (int_4) being equal.
// Increment number tested and mark failure (if result is false).
// if verbose is on, then print output via m_info() for success and m_warning() for failures.
// *****************************************************************************
void nxm_assert_eq (const std::string& message, const int_4& expected, const int_4& actual) {
  NXM_TestResults.num_tests++;    // increment number of tests executed
  bool testresult = (actual == expected);
  std::string failedvals;

  if (!testresult) {
    NXM_TestResults.num_failed++; // increment number of failed tests
    // test failure values = expect:<3> actual:<4>
    std::ostringstream ostr;
    ostr << "expect:<" << expected << "> actual:<" << actual << ">";
    failedvals = ostr.str();
  }
  if (NXM_TestResults.verbose) {
    // pad string len is 63 (to account for prefix and postfix (17 chars) in output, to pad out to 80 chars terminal
    std::string pad = "..............................................................";
    size_t lmsg = message.size() + failedvals.size();
    size_t lpad = (pad.size() <= lmsg)? 0: (pad.size()-lmsg);
    std::ostringstream ostr; // output stringstream for output messages
    ostr << PREPEND_PAD << message << " " << failedvals << pad.substr(0, lpad) << (testresult? ". PASS" : ". FAIL <--");
    if (testresult) { m_info(ostr.str());    } // print info for success
    else            { m_warning(ostr.str()); } // print warning for failures
  }
} // end nxm_assert_eq()

// *****************************************************************************
// Assert a test case for two numbers (real_8) being equal.
// Increment number tested and mark failure (if result is false).
// if verbose is on, then print output via m_info() for success and m_warning() for failures.
// Since NeXtMidas 3.6.0
// *****************************************************************************
void nxm_assert_eq (const std::string& message, const real_8& expected, const real_8& actual) {
  NXM_TestResults.num_tests++;    // increment number of tests executed
  bool testresult = (actual == expected);
  std::string failedvals;

  if (!testresult) {
    NXM_TestResults.num_failed++; // increment number of failed tests
    // test failure values = expect:<3.1> actual:<4.2>
    std::ostringstream ostr;
    ostr << "expect:<" << expected << "> actual:<" << actual << ">";
    failedvals = ostr.str();
  }
  if (NXM_TestResults.verbose) {
    // pad string len is 63 (to account for prefix and postfix (17 chars) in output, to pad out to 80 chars terminal
    std::string pad = "..............................................................";
    size_t lmsg = message.size() + failedvals.size();
    size_t lpad = (pad.size() <= lmsg)? 0: (pad.size()-lmsg);
    std::ostringstream ostr; // output stringstream for output messages
    ostr << PREPEND_PAD << message << " " << failedvals << pad.substr(0, lpad) << (testresult? ". PASS" : ". FAIL <--");
    if (testresult) { m_info(ostr.str());    } // print info for success
    else            { m_warning(ostr.str()); } // print warning for failures
  }
} // end nxm_assert_eq()

// *****************************************************************************
// Assert a test case for two strings being equal.
// Increment number tested and mark failure (if result is false).
// if verbose is on, then print output via m_info() for success and m_warning() for failures.
// *****************************************************************************
void nxm_assert_eqs (const std::string& message, const std::string& expected, const std::string& actual) {
  NXM_TestResults.num_tests++;    // increment number of tests executed
  bool testresult = (actual == expected);
  std::string failedvals;

  if (!testresult) {
    NXM_TestResults.num_failed++; // increment number of failed tests
    // test failure values = expect:<exp_string> actual:<actual_string>
    std::ostringstream ostr;
    ostr << "expect:<" << expected << "> actual:<" << actual << ">";
    failedvals = ostr.str();
  }
  if (NXM_TestResults.verbose) {
    // pad string len is 63 (to account for prefix and postfix (17 chars) in output, to pad out to 80 chars terminal
    std::string pad = "..............................................................";
    size_t lmsg = message.size() + failedvals.size();
    size_t lpad = (pad.size() <= lmsg)? 0: (pad.size()-lmsg);
    std::ostringstream ostr; // output stringstream for output messages
    ostr << PREPEND_PAD << message << " " << failedvals << pad.substr(0, lpad) << (testresult? ". PASS" : ". FAIL <--");
    if (testresult) { m_info(ostr.str());    } // print info for success
    else            { m_warning(ostr.str()); } // print warning for failures
  }
} // end nxm_assert_eqs()

// *****************************************************************************
// reset num test and failed
// *****************************************************************************
void nxm_assert_reset () {
  NXM_TestResults.num_tests  = 0;
  NXM_TestResults.num_failed = 0;
} // end nxm_assert_reset()

// *****************************************************************************
// get number of test executed so far
// *****************************************************************************
int_4 nxm_assert_get_num_tests () {
  return NXM_TestResults.num_tests;
}

// *****************************************************************************
// get number of test failures so far
// *****************************************************************************
int_4 nxm_assert_get_num_failed () {
  return NXM_TestResults.num_failed;
}

// *****************************************************************************
// dump overall test results (number tested and number failed) using m_info()
// *****************************************************************************
void nxm_assert_dump_test_results () {
  std::ostringstream ostr;
  ostr << "  Ran " << NXM_TestResults.num_tests  << " tests, "
                   << NXM_TestResults.num_failed << " failed.";
  m_info(ostr.str());
} // end nxm_assert_dump_test_results()

// *****************************************************************************
// put overall test results into specified result name as "PASS" or "FAIL"
// *****************************************************************************
void nxm_assert_put_status (const std::string& res_name) {
  std::string testResult;
  if (NXM_TestResults.num_failed > 0) {
    testResult = "FAIL";
  } else {
    testResult = "PASS";
  }
  m_put_result(res_name, testResult.data(), testResult.size(), 0, 'A', 0);
} // end nxm_assert_put_status()

// *****************************************************************************
// display overal status (by calling nxm_assert_dump_test_results()) and
// saves overall test result (by calling nxm_assert_dump_test_results()
// *****************************************************************************
void nxm_assert_auto_report () {
#if defined(_DEBUG_NXM_TEST)
    cout << "--- nxm_assert_auto_report() ---" << endl
         << " verbose      = " << NXM_TestResults.verbose          << endl
         << " num_tests    = " << NXM_TestResults.num_tests        << endl
         << " num_failed   = " << NXM_TestResults.num_failed       << endl
         << " res_name_len = " << NXM_TestResults.res_name_len     << endl
         << " res_name     = [" << NXM_TestResults.res_name << "]" << endl
         << endl;
#endif

  if (NXM_TestResults.verbose) {
    nxm_assert_dump_test_results();
  }

  if (NXM_TestResults.res_name_len > 0) {
    std::string resname = NXM_TestResults.res_name;
    nxm_assert_put_status(resname);
  }
} // end nxm_assert_auto_report()

#endif  //  NXM_TEST_HH_
