/*===========================================================================*\
 *                                                                           *
 *                               OpenMesh                                    *
 *      Copyright (C) 2001-2014 by Computer Graphics Group, RWTH Aachen      *
 *                           www.openmesh.org                                *
 *                                                                           *
 *---------------------------------------------------------------------------* 
 *  This file is part of OpenMesh.                                           *
 *                                                                           *
 *  OpenMesh is free software: you can redistribute it and/or modify         * 
 *  it under the terms of the GNU Lesser General Public License as           *
 *  published by the Free Software Foundation, either version 3 of           *
 *  the License, or (at your option) any later version with the              *
 *  following exceptions:                                                    *
 *                                                                           *
 *  If other files instantiate templates or use macros                       *
 *  or inline functions from this file, or you compile this file and         *
 *  link it with other files to produce an executable, this file does        *
 *  not by itself cause the resulting executable to be covered by the        *
 *  GNU Lesser General Public License. This exception does not however       *
 *  invalidate any other reasons why the executable file might be            *
 *  covered by the GNU Lesser General Public License.                        *
 *                                                                           *
 *  OpenMesh 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 Lesser General Public License for more details.                      *
 *                                                                           *
 *  You should have received a copy of the GNU LesserGeneral Public          *
 *  License along with OpenMesh.  If not,                                    *
 *  see <http://www.gnu.org/licenses/>.                                      *
 *                                                                           *
\*===========================================================================*/ 

/*===========================================================================*\
 *                                                                           *             
 *   $Revision: 990 $                                                         *
 *   $Date: 2014-02-05 10:01:07 +0100 (Mi, 05. Feb 2014) $                   *
 *                                                                           *
\*===========================================================================*/

#ifndef TESTINGFRAMEWORK_HH
#define TESTINGFRAMEWORK_HH
// ----------------------------------------------------------------------------

/** \file TestingFramework.hh
    This file contains a little framework for test programms
*/


// ----------------------------------------------------------------------------

#include "Config.hh"
#include <iosfwd>
#include <sstream>
#include <vector>
#include <algorithm>
#include <stdexcept>
#include <OpenMesh/Core/Utils/Noncopyable.hh>

// ------------------------------------------------------------- namespace ----

namespace OpenMesh { // BEGIN_NS_OPENMESH
namespace Utils { // BEGIN_NS_UTILS


// ----------------------------------------------------------------- class ----
//
// Usage Example
//
// #include <iostream>
// #include <.../TestingFramework.hh>
//
// struct test_func : public TestingFramework::TestFunc
// {
//    typedef test_func Self;
//
//    // define ctor and copy-ctor
//    test_func( TestingFramework& _th, std::string _n ) : TestingFramework::TestFunc( _th, _n ) { }
//    test_func( Self& _cpy ) : TestingFramework::TestFunc(_cpy) { }
//
//    // overload body()
//    void body()
//    {
//
//       // Do the tests
//       // direct call to verify
//       verify( testResult, expectedResult, "additional information" );
//
//       // or use the define TH_VERIFY. The test-expression will be used as the message string
//       TH_VERIFY( testResult, expectedResult );
//
//       ...
//    }
// };
//
// int main(...)
// {
//    TestingFramework testSuite(std::cout); // send output to stdout
//
//    new test_func(testSuite);        // create new test instance. It registers with testSuite.
//    return testSuite.run();
// }
//

// 
#define TH_VERIFY( expr, expt ) \
     verify( expr, expt, #expr )

//
#define TH_VERIFY_X( expr, expt ) \
     verify_x( expr, expt, #expr )

/** Helper class for test programms.
    \internal
 */
class TestingFramework : Noncopyable
{
public:

  typedef TestingFramework Self;
  typedef std::logic_error verify_error;
   
#ifndef DOXY_IGNORE_THIS
  class TestFunc
  {
  public:
    TestFunc( TestingFramework& _th, const std::string& _n ) 
      : th_(_th), name_(_n)
    {
      th_.reg(this);
    }
    
    virtual ~TestFunc()
    { }
    
    void operator() ( void )
    {
      prolog();
      try
      {
        body();
      }
      catch( std::exception& x )
      {
        std::cerr << "<<Error>>: Cannot proceed test due to failure of last"
                  << "  test: " << x.what() << std::endl;
      }
      catch(...)
      {
        std::cerr << "Fatal: cannot proceed test due to unknown error!" 
                  << std::endl;
      }
      epilog();
    }
    
    const TestingFramework& testHelper() const { return th_; }
    
  protected:
    
    virtual void prolog(void)
    {
      begin(name_);
    }
    
    virtual void body(void) = 0;
    
    virtual void epilog(void)
    {
      end();
    }
    
  protected:
    
    TestingFramework& testHelper() { return th_; }
    
    TestFunc( const TestFunc& _cpy ) : th_(_cpy.th_), name_(_cpy.name_) { }
    
    
    // Use the following method in prolog()
    TestFunc& begin(std::string _title, const std::string& _info = "")
    { th_.begin(_title,_info); return *this; }
    
    
    // Use the following method in epilog()
    TestFunc& end(void)
    { th_.end(); return *this; }
    
    
    // Use the followin methods in body()
    
    template <typename ValueType>
    bool
    verify( const ValueType& _rc, const ValueType& _expected, 
            std::string _info )
    { return th_.verify( _rc, _expected, _info ); }
    
    template <typename ValueType>
    void
    verify_x( const ValueType& _rc, const ValueType& _expected, 
              std::string _info )
    {
      if ( !verify(_rc, _expected, _info) )
        throw verify_error(_info);
    }
    
    TestFunc& info(const std::string& _info) 
    { th_.info(_info); return *this; }

    TestFunc& info(const std::ostringstream& _ostr) 
    { th_.info(_ostr); return *this; }
    
  private:      
    TestFunc();
    
  protected:
    TestingFramework& th_;
    std::string name_;      
  };
#endif

  typedef TestFunc*                TestFuncPtr;
  typedef std::vector<TestFuncPtr> TestSet;
  
public:
  
  TestingFramework(std::ostream& _os) 
    : errTotal_(0),    errCount_(0),
      verifyTotal_(0), verifyCount_(0),
      testTotal_(0),   testCount_(0),
      os_(_os)
  { }
  
protected:
  
#ifndef DOXY_IGNORE_THIS
  struct TestDeleter
  {
    void operator() (TestFuncPtr _tfptr) { delete _tfptr; }
  };
#endif

public:   

   virtual ~TestingFramework()
   {
      std::for_each(tests_.begin(), tests_.end(), TestDeleter() );
   }

public:
   
   template <typename ValueType>
   bool verify(const ValueType& _rc, 
	       const ValueType& _expected, 
	       const std::string& _info)
   {
      ++verifyTotal_;
      if ( _rc == _expected )
      {
         os_ << "    " << _info << ", result: " << _rc << ", OK!" << std::endl;
         return true;
      }
      ++errTotal_;
      os_ << "    " << _info << ", result: " << _rc << " != " << _expected
          << " <<ERROR>>" << std::endl;
      return false;
   }
   
   Self& begin(std::string _title, const std::string& _info = "")
   {
      std::ostringstream ostr;
      
      testTitle_ = _title;
      errCount_  = errTotal_;
      ++testTotal_;
      
      ostr << _title;
      if ( !_info.empty() )
         ostr << " ["<< _info << "]";
      testTitle_ = ostr.str();
      os_ << "Begin " << testTitle_ << std::endl;
      return *this;
   }
   
   Self& end()
   {
      if (errorCount()==0)
         ++testCount_;
      
      os_ << "End " << testTitle_ << ": " << errorCount() << " Error(s)." << std::endl;
      return *this;
   }

   Self& info(const std::string& _info)
   {
      os_ << "  + " << _info << std::endl;
      return *this;
   }
   
   Self& info(const std::ostringstream& _ostr)
   {
      os_ << "  + " << _ostr.str() << std::endl;
      return *this;
   }
   
   size_t errorTotal()  const { return errTotal_; }
   size_t errorCount()  const { return errTotal_ - errCount_; }
   size_t verifyTotal() const { return verifyTotal_; }
   size_t verifyCount() const { return verifyTotal_ - verifyCount_; }
   size_t goodTotal()   const { return verifyTotal() - errorTotal(); }
   size_t goodCount()   const { return verifyCount() - errorCount(); }
   
   size_t testTotal()   const { return testTotal_; }
   size_t testCount()   const { return testCount_; }

public:

   int run(void)
   {
      os_ << "Test started\n";
      TestRunner executer;
      std::for_each(tests_.begin(), tests_.end(), executer );
      os_ << std::endl;
      os_ << "All tests completed" << std::endl
          << "   #Tests: " << testCount() << "/" << testTotal() << std::endl
          << "  #Errors: " << errorTotal() << "/" << verifyTotal() << std::endl;
      return errorTotal();
   }

protected:

#ifndef DOXY_IGNORE_THIS
  struct TestRunner
  {
    void operator() (TestFuncPtr _tfptr) { (*_tfptr)(); }
  };   
#endif

  int reg(TestFuncPtr _tfptr)
  {
    tests_.push_back(_tfptr);
    return true;
  }

  friend class TestFunc;
   
private:

  size_t errTotal_;    
  size_t errCount_;    
  size_t verifyTotal_;
  size_t verifyCount_;   
  size_t testTotal_;   // #Tests
  size_t testCount_;   // #Tests ohne Fehler
  
  std::string testTitle_;
  std::ostream& os_;
  
  TestSet tests_;
  
};

// ============================================================================
} // END_NS_UTILS
} // END_NS_OPENMESH
// ============================================================================
#endif // TESTINGFRMEWORK_HH
// ============================================================================
