    // Copyright (C) 2006  Davis E. King (davis@dlib.net)
    // License: Boost Software License   See LICENSE.txt for the full license.

    #include <iostream>
    #include <fstream>
    #include <dlib/cmd_line_parser.h>
    #include "tester.h"
    #include <dlib/string.h>

    using namespace std;
    using namespace dlib;
    using namespace test;

    typedef cmd_line_parser<char>::check_1a_c clp;

    static logger dlog("test.main");

    int main (int argc, char** argv)
    {
        try
        {
            clp parser;

            parser.add_option("runall","Run all the tests that don't take any arguments.");
            parser.add_option("h","Displays this information.");
            parser.add_option("n","How many times to run the selected tests. The default is 1.",1);
            parser.add_option("d","log debugging statements to file debug.txt.");
            parser.add_option("l","Set the logging level (all, trace, debug, info, warn, error, or fatal), the default is all.",1);
            parser.add_option("a","Append debugging messages to debug.txt rather than clearing the file at program startup.");
            parser.add_option("q","Be quiet.  Don't print the testing progress or results to standard out.");

            unsigned long num = 1;

            // add the options for all the different tests
            testers().reset();
            while (testers().move_next())
            {
                tester& test = *testers().element().value();
                parser.add_option(test.cmd_line_switch(), test.description(), test.num_of_args());
            }

            parser.parse(argc,argv);

            parser.check_option_arg_range("n",1,1000000000);
            const char* singles[] = {"d","l","a","n","h","runall","q"};
            parser.check_one_time_options(singles);
            const char* d_sub[] = {"l","a"};
            const char* l_args[] = {"all", "trace", "debug", "info", "warn", "error", "fatal"};
            parser.check_sub_options("d",d_sub);
            parser.check_option_arg_range("l",l_args);

            if (parser.option("n"))
            {
                num = string_cast<unsigned long>(parser.option("n").argument());
            }

            if (parser.option("q"))
            {
                be_verbose = false;
            }

            if (parser.option("h"))
            {
                cout << "Usage: test [options]\n";
                parser.print_options(cout);
                cout << "\n\n";
                return 0;
            }

            ofstream fout;
            if (parser.option("d"))
            {
                if (parser.option("a"))
                    fout.open("debug.txt",ios::app);
                else
                    fout.open("debug.txt");

                set_all_logging_output_streams(fout);

                if (parser.option("l").count() == 0)
                    set_all_logging_levels(LALL);
                else if (parser.option("l").argument() == "all")
                    set_all_logging_levels(LALL);
                else if (parser.option("l").argument() == "trace")
                    set_all_logging_levels(LTRACE);
                else if (parser.option("l").argument() == "debug")
                    set_all_logging_levels(LDEBUG);
                else if (parser.option("l").argument() == "info")
                    set_all_logging_levels(LINFO);
                else if (parser.option("l").argument() == "warn")
                    set_all_logging_levels(LWARN);
                else if (parser.option("l").argument() == "error")
                    set_all_logging_levels(LERROR);
                else if (parser.option("l").argument() == "fatal")
                    set_all_logging_levels(LFATAL);
            }
            else
            {
                set_all_logging_levels(LNONE);
            }

            unsigned long num_of_failed_tests = 0;
            unsigned long num_of_passed_tests = 0;
            for (unsigned long i = 0; i < num; ++i)
            {
                dlog << LINFO << "************ Starting Test Run " << i+1 << " of " << num << ". ************";

                // loop over all the testers and see if they are supposed to run
                testers().reset();
                while (testers().move_next())
                {
                    tester& test= *testers().element().value();
                    const clp::option_type& opt = parser.option(test.cmd_line_switch());
                    // run the test for this option as many times as the user has requested.
                    for (unsigned long j = 0; j < parser.option("runall").count() + opt.count(); ++j)
                    {
                        // quit this loop if this option has arguments and this round through the loop is
                        // from the runall option being present.
                        if (test.num_of_args() > 0 && j == opt.count())
                            break;

                        if (be_verbose)
                            cout << "Running " << test.cmd_line_switch() << "   " << flush;

                        dlog << LINFO << "Running " << test.cmd_line_switch();
                        try
                        {
                            switch (test.num_of_args())
                            {
                                case 0:
                                    test.perform_test();
                                    break;
                                case 1:
                                    test.perform_test(opt.argument(0,j));
                                    break;
                                case 2:
                                    test.perform_test(opt.argument(0,j), opt.argument(1,j));
                                    break;
                                default:
                                    cerr << "\n\nThe test '" << test.cmd_line_switch() << "' requested " << test.num_of_args()
                                        << " arguments but only 2 are supported." << endl;
                                    dlog << LINFO << "The test '" << test.cmd_line_switch() << "' requested " << test.num_of_args()
                                        << " arguments but only 2 are supported.";
                                    break;
                            }
                            if (be_verbose)
                                cout << "\r                                                                               \r";

                            ++num_of_passed_tests;

                        }
                        catch (std::exception& e)
                        {
                            if (be_verbose)
                            {
                                cout << "\n\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n";
                                cout << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!! TEST FAILED: " << test.cmd_line_switch()
                                    << " !!!!!!!!!!!!!!!!!!!!!!!!!!!!!";
                                cout << "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n";
                                cout << "Failure message from test: " << e.what() << endl;
                            }

                            dlog << LERROR << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!";
                            dlog << LERROR << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!! TEST FAILED: " << test.cmd_line_switch()
                                << " !!!!!!!!!!!!!!!!!!!!!!!!!!!!!";
                            dlog << LERROR << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!";
                            dlog << LERROR << "Failure message from test: " << e.what();
                            ++num_of_failed_tests;
                        }
                    }
                }
            }
            dlog << LINFO << "Testing Finished";
            if (num_of_passed_tests == 0 && num_of_failed_tests == 0)
            {
                cout << "You didn't select any tests to run.\n";
                cout << "Try the -h option for more information.\n";
            }
            else if (num_of_failed_tests == 0)
            {
                if (be_verbose)
                {
                    cout << "\n\nTesting Finished\n";
                    cout << "Total number of individual testing statements executed: "<< number_of_testing_statements_executed() << endl;
                    cout << "All tests completed successfully\n\n";
                }
                dlog << LINFO << "Total number of individual testing statements executed: "<< number_of_testing_statements_executed();
                dlog << LINFO << "All tests completed successfully";
            }
            else
            {
                if (be_verbose)
                {
                    cout << "\n\nTesting Finished\n";
                    cout << "Total number of individual testing statements executed: "<< number_of_testing_statements_executed() << endl;
                    cout << "Number of failed tests: " << num_of_failed_tests << "\n";
                    cout << "Number of passed tests: " << num_of_passed_tests << "\n\n";
                }
                dlog << LINFO << "Total number of individual testing statements executed: "<< number_of_testing_statements_executed();
                dlog << LWARN << "Number of failed tests: " << num_of_failed_tests;
                dlog << LWARN << "Number of passed tests: " << num_of_passed_tests;
            }

            return num_of_failed_tests;
        }
        catch (exception& e)
        {
            cout << e.what() << endl;
            cout << "\nTry the -h option for more information.\n";
            cout << endl;
        }
    }