// 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 non-failure results to standard out.");

        unsigned long num = 1;

        // add the options for all the different tests
        for (auto& kv : testers())
        {
            tester& test = *kv.second;
            parser.add_option(test.cmd_line_switch(), test.description(), test.num_of_args());
            if (test.num_of_args()==0) 
                parser.add_option("no_"+test.cmd_line_switch(), "Don't run this option when using --runall.");
        }

        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
            for(auto& kv : testers())
            {
                tester& test= *kv.second;
                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)
                {
                    // If this round through the loop is from the runall option being present.
                    if (j == opt.count())
                    {
                        // Don't run options that take arguments or have had --no_ applied to them.
                        if (test.num_of_args() > 0 || parser.option("no_"+test.cmd_line_switch()))
                            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)
                    {
                        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
        {
            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;
    }
}

