/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
 * Copyright (c) 2009 University of Washington
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "test.h"
#include "assert.h"
#include "abort.h"
#include <math.h>

bool gBreakOnFailure = false;

namespace ns3 {

//
// XML files have restrictions on certain characters that may be present in
// data.  We need to replace these characters with their alternate 
// representation on the way into the XML file.
//
std::string
ReplaceXmlSpecialCharacters (std::string xml)
{
  std::string specials = "<>&\"'";
  std::string replacements[] = {"&lt;", "&gt;", "&amp;", "&#39;", "&quot;"};
  std::string result;
  std::size_t index, length = xml.length ();

  for (size_t i = 0; i < length; ++i)
    {
      char character = xml[i];

      if ((index = specials.find (character)) == std::string::npos)
        {
          result.push_back (character);
        }
      else
        {
          result += replacements[index];
        }
    }
  return result;
}

bool
TestDoubleIsEqual (const double x1, const double x2, const double epsilon)
{
  int exponent;
  double delta, difference;

  //
  // Find exponent of largest absolute value
  //
  {
    double max = (fabs (x1) > fabs (x2)) ? x1 : x2;
    (void)frexp (max, &exponent);
  }

  //
  // Form a neighborhood of size  2 * delta
  //
  delta = ldexp (epsilon, exponent);
  difference = x1 - x2;

  if (difference > delta || difference < -delta)
    {
      return false;
    }
  return true;
} 


TestCase::TestCase (std::string name)
  : m_name (name), 
    m_verbose (false), 
    m_continueOnFailure (false), 
    m_detailsReported (false), 
    m_basedir ("invalid"), 
    m_tempdir ("invalid"), 
    m_ofs (0), 
    m_error (false)
{
}

TestCase::~TestCase ()
{
}

void
TestCase::ReportStart  (void)
{
  DoReportStart ();
}

void
TestCase::ReportCaseSuccess  (void)
{
  UpdateErrorStatus (false);
  DoReportCaseSuccess ();
}

void
TestCase::ReportCaseFailure  (void)
{
  UpdateErrorStatus (true);
  DoReportCaseFailure ();
}

void
TestCase::ReportTestFailure  (
  std::string cond, 
  std::string actual, 
  std::string limit, 
  std::string message, 
  std::string file, 
  int32_t line)
{
  UpdateErrorStatus (true);
  DoReportTestFailure (cond, actual, limit, message, file, line);
  m_detailsReported = true; 
}

void
TestCase::ReportEnd  (void)
{
  DoReportStart ();
}

bool
TestCase::Run (void)
{
  //
  // We set up a flag to make sure the user plays by the rules and actually
  // does something to report the details of an error.
  //
  m_detailsReported = false; 

  DoReportStart ();
  DoSetup ();

  bool result = DoRun ();
  UpdateErrorStatus (result);

  DoTeardown ();

  if (GetErrorStatus () == false)
    {
      DoReportCaseSuccess ();
    }
  else
    {
      //
      // It is a programming error to return an error from a test case without 
      // calling ReportTestFailure.  Typically this is done automagically when
      // using the ASSERT or EXPECT macros.  If you don't use these, you must
      // ReportTestFailure on any errors yourself, which will set 
      // m_detailsReported and make us happy.
      //
      NS_ASSERT_MSG (m_detailsReported, "The details of a failing test was not reported");

      DoReportCaseFailure ();
    }

  DoReportEnd ();

  return GetErrorStatus ();
}

void 
TestCase::SetVerbose (bool verbose)
{
  m_verbose = verbose;
}

void 
TestCase::SetContinueOnFailure (bool continueOnFailure)
{
  m_continueOnFailure = continueOnFailure;
}

void 
TestCase::SetName (std::string name)
{
  m_name = name;
}

std::string 
TestCase::GetName (void)
{
  return m_name;
}

void 
TestCase::SetBaseDir (std::string basedir)
{
  //
  // C and C++ allow one to use forward slashes even on systems where the 
  // separator is actually a backslash.
  //
  if (basedir[basedir.length () - 1] != '/')
    {
      m_basedir = basedir + "/";
    }
  else
    {
      m_basedir = basedir;
    }
}

std::string 
TestCase::GetBaseDir (void)
{
  return m_basedir;
}

void 
TestCase::SetTempDir (std::string tempdir)
{
  //
  // C and C++ allow one to use forward slashes even on systems where the 
  // separator is actually a backslash.
  //
  if (tempdir[tempdir.length () - 1] != '/')
    {
      m_tempdir = tempdir + "/";
    }
  else
    {
      m_tempdir = tempdir;
    }
}

std::string 
TestCase::GetTempDir (void)
{
  return m_tempdir;
}

std::string 
TestCase::GetSourceDir (std::string file)
{
  //
  // The <file> parameter is actually going to be __FILE__ which may have 
  // backslashes in it on win32 systems.  For example,
  //
  //   ..\src\common\pcap-file-test-suite.cc  (win32)
  //
  // or
  //
  //   ../src/common/pcap-file-test-suite.cc  (grown-up systems)
  //
#ifdef WIN32
  std::string::size_type relPathBegin = file.find_first_of ("\\");
  std::string::size_type relPathEnd = file.find_last_of ("\\");
#else
  std::string::size_type relPathBegin = file.find_first_of ("/");
  std::string::size_type relPathEnd = file.find_last_of ("/");
#endif

  NS_ABORT_MSG_IF (relPathBegin == std::string::npos, "TestCase::GetSourceDir(): Internal Error");
  NS_ABORT_MSG_IF (relPathEnd == std::string::npos, "TestCase::GetSourceDir(): Internal Error");

  return GetBaseDir () + file.substr (relPathBegin, relPathEnd + 1 - relPathBegin);
}

void 
TestCase::SetStream (std::ofstream *ofs)
{
  m_ofs = ofs;
}

std::ofstream *
TestCase::GetStream (void)
{
  return m_ofs;
}

void 
TestCase::UpdateErrorStatus (bool error)
{
  m_error |= error;
}

void 
TestCase::SetErrorStatus (bool error)
{
  m_error = error;
}

bool
TestCase::GetErrorStatus (void)
{
  return m_error;
}

bool
TestCase::ContinueOnFailure (void)
{
  return m_continueOnFailure;
}

void
TestCase::DoReportStart  (void)
{
  m_msClock.Start ();

  if (m_ofs == 0)
    {
      return;
    }
  *m_ofs << "  <TestCase>" << std::endl;
  *m_ofs << "    <CaseName>" << ReplaceXmlSpecialCharacters (GetName ()) << "</CaseName>" << std::endl;
}

void
TestCase::DoReportCaseSuccess  (void)
{
  if (m_ofs == 0)
    {
      return;
    }
  *m_ofs << "    <CaseResult>PASS</CaseResult>" << std::endl;
}

void
TestCase::DoReportCaseFailure  (void)
{
  if (m_ofs == 0)
    {
      return;
    }
  *m_ofs << "    <CaseResult>FAIL</CaseResult>" << std::endl;
}

void
TestCase::DoReportTestFailure  (
  std::string cond, 
  std::string actual, 
  std::string limit, 
  std::string message, 
  std::string file, 
  int32_t line)
{
  if (m_ofs == 0)
    {
      return;
    }

  *m_ofs << "    <FailureDetails>" << std::endl;
  *m_ofs << "      <Condition>" << ReplaceXmlSpecialCharacters (cond) << "</Condition>" << std::endl;
  *m_ofs << "      <Actual>" << ReplaceXmlSpecialCharacters (actual) << "</Actual>" << std::endl;
  *m_ofs << "      <Limit>" << ReplaceXmlSpecialCharacters (limit) << "</Limit>" << std::endl;
  *m_ofs << "      <Message>" << ReplaceXmlSpecialCharacters (message) << "</Message>" << std::endl;
  *m_ofs << "      <File>" << ReplaceXmlSpecialCharacters (file) << "</File>" << std::endl;
  *m_ofs << "      <Line>" << line << "</Line>" << std::endl;
  *m_ofs << "    </FailureDetails>" << std::endl;
}

void
TestCase::DoReportEnd  (void)
{
  m_msClock.End ();

  if (m_ofs == 0)
    {
      return;
    }

  (*m_ofs).precision (3);
  *m_ofs << std::fixed;

  const double MS_PER_SEC = 1000.;

  *m_ofs << "    <CaseTime>" << "real " << m_msClock.GetElapsedReal () / MS_PER_SEC
                             << " user " << m_msClock.GetElapsedUser () / MS_PER_SEC
                             << " system " << m_msClock.GetElapsedSystem () / MS_PER_SEC
         << "</CaseTime>" << std::endl;

  *m_ofs << "  </TestCase>" << std::endl;
}

void 
TestCase::DoSetup (void)
{
}

void 
TestCase::DoTeardown (void)
{
}

TestSuite::TestSuite (std::string name, TestType type)
  : m_name (name), 
    m_verbose (false), 
    m_basedir ("invalid"), 
    m_tempdir ("invalid"), 
    m_ofs (0), 
    m_error (false), 
    m_type (type)
{
  TestRunner::AddTestSuite (this);
}

TestSuite::~TestSuite ()
{
  for (TestCaseVector_t::iterator i = m_tests.begin (); i != m_tests.end (); ++i)
    {
      delete *i;
      *i = 0;
    }

  m_tests.erase (m_tests.begin (), m_tests.end ());
}

void
TestSuite::ReportStart (void)
{
  DoReportStart ();
}

void
TestSuite::ReportSuccess (void)
{
  UpdateErrorStatus (false);
  DoReportSuccess ();
}

void
TestSuite::ReportFailure (void)
{
  UpdateErrorStatus (true);
  DoReportFailure ();
}

void
TestSuite::ReportEnd (void)
{
  DoReportEnd ();
}

bool
TestSuite::Run (void)
{
  DoReportStart ();

  DoSetup (); 

  bool result = DoRun ();
  UpdateErrorStatus (result);

  DoTeardown ();

  if (GetErrorStatus () == false)
    {
      DoReportSuccess ();
    }
  else
    {
      DoReportFailure ();
    }

  DoReportEnd ();

  return GetErrorStatus ();
}

uint32_t
TestSuite::AddTestCase (TestCase *testCase)
{
  uint32_t index = m_tests.size ();
  m_tests.push_back (testCase);
  return index;
}

uint32_t 
TestSuite::GetNTestCases (void)
{
  return m_tests.size ();
}

TestCase *
TestSuite::GetTestCase (uint32_t n)
{
  return m_tests[n];
}

TestSuite::TestType 
TestSuite::GetTestType (void)
{
  return m_type;
}

void 
TestSuite::SetVerbose (bool verbose)
{
  m_verbose = verbose;
}

void 
TestSuite::SetContinueOnFailure (bool continueOnFailure)
{
  m_continueOnFailure = continueOnFailure;
}

void 
TestSuite::SetName (std::string name)
{
  m_name = name;
}

std::string 
TestSuite::GetName (void)
{
  return m_name;
}

void 
TestSuite::SetBaseDir (std::string basedir)
{
  //
  // C and C++ allow one to use forward slashes even on systems where the 
  // separator is actually a backslash.
  //
  if (basedir[basedir.length () - 1] != '/')
    {
      m_basedir = basedir + "/";
    }
  else
    {
      m_basedir = basedir;
    }
}

std::string 
TestSuite::GetBaseDir (void)
{
  return m_basedir;
}

void 
TestSuite::SetTempDir (std::string tempdir)
{
  //
  // C and C++ allow one to use forward slashes even on systems where the 
  // separator is actually a backslash.
  //
  if (tempdir[tempdir.length () - 1] != '/')
    {
      m_tempdir = tempdir + "/";
    }
  else
    {
      m_tempdir = tempdir;
    }
}

std::string 
TestSuite::GetTempDir (void)
{
  return m_tempdir;
}

void 
TestSuite::SetStream (std::ofstream *ofs)
{
  m_ofs = ofs;
}

void 
TestSuite::UpdateErrorStatus (bool error)
{
  m_error |= error;
}

void 
TestSuite::SetErrorStatus (bool error)
{
  m_error = error;
}

bool
TestSuite::GetErrorStatus (void)
{
  return m_error;
}

bool
TestSuite::ContinueOnFailure (void)
{
  return m_continueOnFailure;
}

void
TestSuite::DoReportStart (void)
{
  m_msClock.Start ();
  
  if (m_ofs == 0)
    {
      return;
    }
  *m_ofs << "<TestSuite>" << std::endl;
  *m_ofs << "  <SuiteName>" << ReplaceXmlSpecialCharacters (GetName ()) << "</SuiteName>" << std::endl;
}

void
TestSuite::DoReportFailure (void)
{
  if (m_ofs == 0)
    {
      return;
    }
  *m_ofs << "  <SuiteResult>FAIL</SuiteResult>" << std::endl;
}

void
TestSuite::DoReportSuccess (void)
{
  if (m_ofs == 0)
    {
      return;
    }
  *m_ofs << "  <SuiteResult>PASS</SuiteResult>" << std::endl;
}

void
TestSuite::DoReportEnd (void)
{
  m_msClock.End ();
  
  if (m_ofs == 0)
    {
      return;
    }

  (*m_ofs).precision (3);
  *m_ofs << std::fixed;

  const double MS_PER_SEC = 1000.;

  *m_ofs << "  <SuiteTime>" << "real " << m_msClock.GetElapsedReal () / MS_PER_SEC
                            << " user " << m_msClock.GetElapsedUser () / MS_PER_SEC
                            << " system " << m_msClock.GetElapsedSystem () / MS_PER_SEC
         << "</SuiteTime>" << std::endl;

  *m_ofs << "</TestSuite>" << std::endl;
}

void 
TestSuite::DoSetup (void)
{
}

bool
TestSuite::DoRun (void)
{
  SetErrorStatus (false);

  for (TestCaseVector_t::iterator i = m_tests.begin (); i != m_tests.end (); ++i)
    {
      //
      // Set the current options for the test case
      //
      (*i)->SetVerbose (m_verbose);
      (*i)->SetContinueOnFailure (m_continueOnFailure);
      (*i)->SetBaseDir (m_basedir);
      (*i)->SetTempDir (m_tempdir);
      (*i)->SetStream (m_ofs);

      //
      // Run the test case
      //
      bool result = (*i)->Run ();
      UpdateErrorStatus (result);

      //
      // Exit if we have detected an error and we are not allowing multiple filures
      //
      if (GetErrorStatus () && m_continueOnFailure == false)
        {
          return GetErrorStatus ();
        }
    }

  return GetErrorStatus ();
}

void 
TestSuite::DoTeardown (void)
{
}

class TestRunnerImpl
{
public:
  uint32_t AddTestSuite (TestSuite *testSuite);
  uint32_t GetNTestSuites (void);
  TestSuite *GetTestSuite (uint32_t n);
  bool RunTestSuite (uint32_t n);

  static TestRunnerImpl *Instance (void);
private:
  TestRunnerImpl ();
  ~TestRunnerImpl ();

  typedef std::vector<TestSuite *> TestSuiteVector_t;
  TestSuiteVector_t m_suites;
};

TestRunnerImpl::TestRunnerImpl ()
{
}

TestRunnerImpl::~TestRunnerImpl ()
{
}

TestRunnerImpl *
TestRunnerImpl::Instance (void)
{
  static TestRunnerImpl runner;
  return &runner;
}

uint32_t
TestRunnerImpl::AddTestSuite (TestSuite *testSuite)
{
  uint32_t index = m_suites.size ();
  m_suites.push_back (testSuite);
  return index;
}

uint32_t 
TestRunnerImpl::GetNTestSuites (void)
{
  return m_suites.size ();
}

TestSuite *
TestRunnerImpl::GetTestSuite (uint32_t n)
{
  return m_suites[n];
}

uint32_t
TestRunner::AddTestSuite (TestSuite *testSuite)
{
  return TestRunnerImpl::Instance ()->AddTestSuite (testSuite);
}

uint32_t
TestRunner::GetNTestSuites (void)
{
  return TestRunnerImpl::Instance ()->GetNTestSuites ();
}

TestSuite *
TestRunner::GetTestSuite (uint32_t n)
{
  return TestRunnerImpl::Instance ()->GetTestSuite (n);
}

} // namespace ns3
