#include <cppunit/BriefTestProgressListener.h>
#include <cppunit/CompilerOutputter.h>
#include <cppunit/TestPath.h>
#include <cppunit/TestResult.h>
#include <cppunit/TestResultCollector.h>
#include <cppunit/TestRunner.h>
#include <cppunit/TextOutputter.h>
#include <cppunit/TextTestProgressListener.h>
#include <cppunit/XmlOutputter.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/plugin/DynamicLibraryManagerException.h>
#include <cppunit/plugin/PlugInParameters.h>
#include <cppunit/plugin/PlugInManager.h>
#include <cppunit/plugin/TestPlugIn.h>
#include <cppunit/portability/Stream.h>
#include "CommandLineParser.h"


/* Notes:

  Memory allocated by test plug-in must be freed before unloading the test plug-in.
  That is the reason why the XmlOutputter is explicitely destroyed.
 */


/*! Runs the specified tests located in the root suite.
 * \param parser Command line parser.
 * \return \c true if the run succeed, \c false if a test failed or if a test
 *         path was not resolved.
 */
bool 
runTests( const CommandLineParser &parser )
{
  bool wasSuccessful = false;
  CPPUNIT_NS::PlugInManager plugInManager;

  // The following scope is used to explicitely free all memory allocated before
  // unload the test plug-ins (uppon plugInManager destruction).
  {
    CPPUNIT_NS::TestResult controller;
    CPPUNIT_NS::TestResultCollector result;
    controller.addListener( &result );        

    // Set up outputters
    CPPUNIT_NS::OStream *stream = &CPPUNIT_NS::stdCErr();
    if ( parser.useCoutStream() )
      stream = &CPPUNIT_NS::stdCOut();

    CPPUNIT_NS::OStream *xmlStream = stream;
    if ( !parser.getXmlFileName().empty() )
      xmlStream = new CPPUNIT_NS::OFileStream( parser.getXmlFileName().c_str() );

    CPPUNIT_NS::XmlOutputter xmlOutputter( &result, *xmlStream, parser.getEncoding() );
    xmlOutputter.setStyleSheet( parser.getXmlStyleSheet() );
    CPPUNIT_NS::TextOutputter textOutputter( &result, *stream );
    CPPUNIT_NS::CompilerOutputter compilerOutputter( &result, *stream );

    // Set up test listeners
    CPPUNIT_NS::BriefTestProgressListener briefListener;
    CPPUNIT_NS::TextTestProgressListener dotListener;
    if ( parser.useBriefTestProgress() )
      controller.addListener( &briefListener );
    else if ( !parser.noTestProgress() )
      controller.addListener( &dotListener );

    // Set up plug-ins
    for ( int index =0; index < parser.getPlugInCount(); ++index )
    {
      CommandLinePlugInInfo plugIn = parser.getPlugInAt( index );
      plugInManager.load( plugIn.m_fileName, plugIn.m_parameters );
    }

    // Registers plug-in specific TestListener (global setUp/tearDown, custom TestListener...)
    plugInManager.addListener( &controller );

    // Adds the default registry suite
    CPPUNIT_NS::TestRunner runner;
    runner.addTest( CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest() );

    // Runs the specified test
    try
    {
      runner.run( controller, parser.getTestPath() );
      wasSuccessful = result.wasSuccessful();
    }
    catch ( std::invalid_argument & )
    {
      CPPUNIT_NS::stdCOut()  <<  "Failed to resolve test path: "  
                             <<  parser.getTestPath() 
                             <<  "\n";
    }

    // Removes plug-in specific TestListener (not really needed but...)
    plugInManager.removeListener( &controller );

    // write using outputters
    if ( parser.useCompilerOutputter() )
      compilerOutputter.write();

    if ( parser.useTextOutputter() )
      textOutputter.write();

    if ( parser.useXmlOutputter() )
    {
      plugInManager.addXmlOutputterHooks( &xmlOutputter );
      xmlOutputter.write();
      plugInManager.removeXmlOutputterHooks();
    }

    if ( !parser.getXmlFileName().empty() )
      delete xmlStream;
  }

  return wasSuccessful;
}


void
printShortUsage( const std::string &applicationName )
{
   CPPUNIT_NS::stdCOut()  << "Usage:\n"
             << applicationName  <<  " [-c -b -n -t -o -w] [-x xml-filename]"
             "[-s stylesheet] [-e encoding] plug-in[=parameters] [plug-in...] [:testPath]\n\n";
}


void
printUsage( const std::string &applicationName )
{
  printShortUsage( applicationName );
  CPPUNIT_NS::stdCOut()  <<
"-c --compiler\n"
"	Use CompilerOutputter\n"
"-x --xml [filename]\n"
"	Use XmlOutputter (if filename is omitted, then output to cout or\n"
"	cerr.\n"
"-s --xsl stylesheet\n"
"	XML style sheet for XML Outputter\n"
"-e --encoding encoding\n"
"	XML file encoding (UTF8, shift_jis, ISO-8859-1...)\n"
"-b --brief-progress\n"
"	Use BriefTestProgressListener (default is TextTestProgressListener)\n"
"-n --no-progress\n"
"	Show no test progress (disable default TextTestProgressListener)\n"
"-t --text\n"
"	Use TextOutputter\n"
"-o --cout\n"
"	Ouputters output to cout instead of the default cerr.\n"
"-w --wait\n"
"	Wait for the user to press a return before exit.\n"
"filename[=\"options\"]\n"
"	Many filenames can be specified. They are the name of the \n"
"	test plug-ins to load. Optional plug-ins parameters can be \n"
"	specified after the filename by adding '='.\n"
"[:testpath]\n"
"	Optional. Only one test path can be specified. It must \n"
"	be prefixed with ':'. See TestPath constructor for syntax.\n"
"\n"
"'parameters' (test plug-in or XML filename, test path...) may contains \n"
"spaces if double quoted. Quote may be escaped with \".\n"
"\n"
"Some examples of command lines:\n"
"\n"
"DllPlugInTesterd_dll.exe -b -x tests.xml -c simple_plugind.dll CppUnitTestPlugInd.dll\n"
"\n"
" Will load 2 tests plug-ins (available in lib/), use the brief test\n"
"progress, output the result in XML in file tests.xml and also\n"
"output the result using the compiler outputter.\n"
"\n"
"DllPlugInTesterd_dll.exe ClockerPlugInd.dll=\"flat\" -n CppUnitTestPlugInd.dll\n"
"\n"
" Will load the 2 test plug-ins, and pass the parameter string \"flat\"\n"
"to the Clocker plug-in, disable test progress.\n\n";

}


/*! Main
 * 
 * Usage: 
 *
 * DllPlugInTester.exe dll-filename1 [dll-filename2 [dll-filename3 ...]] [:testpath]
 *
 * <em>dll-filename</em> must be the name of the DLL. If the DLL use some other DLL, they
 * should be in the path or in the same directory as the DLL. The DLL must export
 * a function named "GetTestPlugInInterface" with the signature
 * GetTestPlugInInterfaceFunction. Both are defined in:
 * \code
 * #include <msvc6/testrunner/TestPlugInInterface.h>
 * \endcode.
 *
 * If no test path is specified, they all the test of the suite returned by the DLL
 * are run. If a test path is specified, then only the specified test is run. The test
 * path must be prefixed by ':'.
 *
 * Test paths are resolved using Test::resolveTestPath() on the suite returned by
 * TestFactoryRegistry::getRegistry().makeTest();
 *
 * If all test succeed and no error happen then the application exit with code 0.
 * If any error occurs (failed to load dll, failed to resolve test paths) or a 
 * test fail, the application exit with code 1. If the application failed to
 * parse the command line, it exits with code 2.
 */
int 
main( int argc, 
      const char *argv[] )
{
  const int successReturnCode = 0;
  const int failureReturnCode = 1;
  const int badCommadLineReturnCode = 2;

  // check command line
  std::string applicationName( argv[0] );
  if ( argc < 2 )
  {
    printUsage( applicationName );
    return badCommadLineReturnCode;
  }

  CommandLineParser parser( argc, argv );
  try
  {
    parser.parse();
  }
  catch ( CommandLineParserException &e )
  {
    CPPUNIT_NS::stdCOut()  <<  "Error while parsing command line: "  <<  e.what()  
                           << "\n\n";
    printShortUsage( applicationName );
    return badCommadLineReturnCode;
  }

  bool wasSuccessful = false;
  try
  {
    wasSuccessful = runTests( parser );
  }
  catch ( CPPUNIT_NS::DynamicLibraryManagerException &e )
  {
    CPPUNIT_NS::stdCOut()  << "Failed to load test plug-in:\n"
                           << e.what() << "\n";
  }

#if !defined( CPPUNIT_NO_STREAM )
  if ( parser.waitBeforeExit() )
  {
    CPPUNIT_NS::stdCOut() << "Please press <RETURN> to exit\n";
    CPPUNIT_NS::stdCOut().flush();
    std::cin.get();
  }
#endif

  return wasSuccessful ? successReturnCode : failureReturnCode;
}


