/*=============================================================================
    Boost.Wave: A Standard compliant C++ preprocessor library
    http://www.boost.org/

    Copyright (c) 2001-2008 Hartmut Kaiser. Distributed under the Boost
    Software License, Version 1.0. (See accompanying file
    LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
=============================================================================*/

// system headers
#include <string>
#include <iostream>
#include <vector>

// include boost
#include <boost/config.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>

//  test application related headers
#include "cmd_line_utils.hpp"
#include "testwave_app.hpp"

namespace po = boost::program_options;
namespace fs = boost::filesystem;

///////////////////////////////////////////////////////////////////////////////
//
//  The debuglevel command line parameter is used to control the amount of text 
//  printed by the testwave application. 
//
//  level 0:    prints nothing except serious failures preventing the testwave
//              executable from running, the return value of the executable is 
//              equal to the number of failed tests
//  level 1:    prints a short summary only
//  level 2:    prints the names of the failed tests only
//  level 3:    prints the expected and real result for failed tests
//  level 4:    prints the outcome of every test
//  level 5:    prints the real result even for succeeded tests
//  level 6:    prints the real hooks information recorded, even for succeeded 
//              tests
//
//  level 9:    prints information about almost everything
//
//  The default debug level is 1.
//
///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
int 
main(int argc, char *argv[])
{
    int error_count = 0;
    int config_file_error_count = 0;
    try {
    // analyze the command line options and arguments
        po::options_description desc_cmdline ("Options allowed on the command line");
        desc_cmdline.add_options()
            ("help,h", "print out program usage (this message)")
            ("version,v", "print the version number")
            ("copyright,c", "print out the copyright statement")
            ("config-file", po::value<std::vector<std::string> >()->composing(), 
                "specify a config file (alternatively: @arg)")
            ("hooks", po::value<bool>()->default_value(true), 
                "test preprocessing hooks")
            ("debug,d", po::value<int>(), "set the debug level (0...9)")
        ;

    // Hidden options, will be used in in config file analysis to allow to
    // recognize positional arguments, will not be shown to the user.
        po::options_description desc_hidden("Hidden options");
        desc_hidden.add_options()
            ("input", po::value<std::vector<std::string> >()->composing(), 
                "inputfile")
        ;        

    // this is the test application object
        po::variables_map vm;
        testwave_app app(vm);

    // all command line and config file options
        po::options_description cmdline_options;
        cmdline_options.add(desc_cmdline).add(app.common_options());

    // parse command line
        // (the (int) cast is to make the True64 compiler happy)
        using namespace boost::program_options::command_line_style;
        po::parsed_options opts(po::parse_command_line(argc, argv, 
            cmdline_options, (int)unix_style, cmd_line_utils::at_option_parser));
        
        po::store(opts, vm);
        po::notify(vm);

    // ... act as required 
        if (vm.count("help")) {
            po::options_description desc_help (
                "Usage: testwave [options] [@config-file(s)] file(s)");
            desc_help.add(desc_cmdline).add(app.common_options());
            std::cout << desc_help << std::endl;
            return 0;
        }
        
    // debug flag
        if (vm.count("debug")) {
            int debug_level = vm["debug"].as<int>();
            if (debug_level < 0 || debug_level > 9) {
                std::cerr 
                    << "testwave: please use an integer in the range [0..9] "
                    << "as the parameter to the debug option!" 
                    << std::endl;
            }
            else {
                app.set_debuglevel(debug_level);
            }
        }
        
        if (vm.count("version")) {
            return app.print_version();
        }

        if (vm.count("copyright")) {
            return app.print_copyright();
        }
        
    // If there is specified at least one config file, parse it and add the 
    // options to the main variables_map
    // Each of the config files is parsed into a separate variables_map to 
    // allow correct paths handling.
        int input_count = 0;
        if (vm.count("config-file")) {
            std::vector<std::string> const &cfg_files = 
                vm["config-file"].as<std::vector<std::string> >();

            if (9 == app.get_debuglevel()) {
                std::cerr << "found " << (unsigned)cfg_files.size() 
                          << " config-file arguments" << std::endl;
            }
            
            std::vector<std::string>::const_iterator end = cfg_files.end();
            for (std::vector<std::string>::const_iterator cit = cfg_files.begin(); 
                 cit != end; ++cit)
            {
                if (9 == app.get_debuglevel()) {
                    std::cerr << "reading config_file: " << *cit << std::endl;
                }
                
            // parse a single config file and store the results, config files
            // may only contain --input and positional arguments 
                po::variables_map cvm;
                if (!cmd_line_utils::read_config_file(app.get_debuglevel(), 
                    *cit, desc_hidden, cvm)) 
                {
                    if (9 == app.get_debuglevel()) {
                        std::cerr << "failed to read config_file: " << *cit 
                                  << std::endl;
                    }
                    ++config_file_error_count;
                }
                
                if (9 == app.get_debuglevel()) {
                    std::cerr << "succeeded to read config_file: " << *cit 
                              << std::endl;
                }
                
            // correct the paths parsed into this variables_map
                if (cvm.count("input")) {
                    std::vector<std::string> const &infiles = 
                        cvm["input"].as<std::vector<std::string> >();
                    
                    if (9 == app.get_debuglevel()) {
                        std::cerr << "found " << (unsigned)infiles.size() 
                                  << " entries" << std::endl;
                    }
                    
                    std::vector<std::string>::const_iterator iend = infiles.end();
                    for (std::vector<std::string>::const_iterator iit = infiles.begin(); 
                         iit != iend; ++iit)
                    {
                    // correct the file name (pre-pend the config file path)
                        fs::path cfgpath = fs::complete(
                            fs::path(*cit, fs::native), fs::current_path());
                        fs::path filepath = 
                            cfgpath.branch_path() / fs::path(*iit, fs::native);
                        
                        if (9 == app.get_debuglevel()) {
                            std::cerr << std::string(79, '-') << std::endl;
                            std::cerr << "executing test: " 
                                      << filepath.native_file_string()
                                      << std::endl;
                        }
                    
                    // execute this unit test case
                        if (!app.test_a_file(filepath.native_file_string())) {
                            if (9 == app.get_debuglevel()) {
                                std::cerr << "failed to execute test: " 
                                          << filepath.native_file_string()
                                          << std::endl;
                            }
                            ++error_count;
                        }
                        else if (9 == app.get_debuglevel()) {
                            std::cerr << "succeeded to execute test: " 
                                      << filepath.native_file_string()
                                      << std::endl;
                        }
                        ++input_count;
                        
                        if (9 == app.get_debuglevel()) {
                            std::cerr << std::string(79, '-') << std::endl;
                        }
                    }
                }
                else if (9 == app.get_debuglevel()) {
                    std::cerr << "no entries found" << std::endl;
                }
            }
        }

    // extract the arguments from the parsed command line
        std::vector<po::option> arguments;
        std::remove_copy_if(opts.options.begin(), opts.options.end(), 
            std::back_inserter(arguments), cmd_line_utils::is_argument());

        if (9 == app.get_debuglevel()) {
            std::cerr << "found " << (unsigned)arguments.size() 
                      << " arguments" << std::endl;
        }
        
    // iterate over remaining arguments
        std::vector<po::option>::const_iterator arg_end = arguments.end();
        for (std::vector<po::option>::const_iterator arg = arguments.begin();
             arg != arg_end; ++arg)
        {
            fs::path filepath((*arg).value[0], fs::native);

            if (9 == app.get_debuglevel()) {
                std::cerr << std::string(79, '-') << std::endl;
                std::cerr << "executing test: " 
                          << filepath.native_file_string()
                          << std::endl;
            }
                    
            if (!app.test_a_file(filepath.native_file_string())) {
                if (9 == app.get_debuglevel()) {
                    std::cerr << "failed to execute test: " 
                              << filepath.native_file_string()
                              << std::endl;
                }
                ++error_count;
            }
            else if (9 == app.get_debuglevel()) {
                std::cerr << "succeeded to execute test: " 
                          << filepath.native_file_string()
                          << std::endl;
            }

            if (9 == app.get_debuglevel()) {
                std::cerr << std::string(79, '-') << std::endl;
            }
            ++input_count;
        }

    // print a message if no input is given
        if (0 == input_count) {
            std::cerr 
                << "testwave: no input file specified, " 
                << "try --help to get a hint." 
                << std::endl;
            return (std::numeric_limits<int>::max)() - 3;
        }
        else if (app.get_debuglevel() > 0) {
            std::cout 
                << "testwave: " << input_count-error_count 
                << " of " << input_count << " test(s) succeeded";
            if (0 != error_count) {
                std::cout 
                    << " (" << error_count << " test(s) failed)";
            }
            std::cout << "." << std::endl;
        }
    }
    catch (std::exception const& e) {
        std::cerr << "testwave: exception caught: " << e.what() << std::endl;
        return (std::numeric_limits<int>::max)() - 1;
    }
    catch (...) {
        std::cerr << "testwave: unexpected exception caught." << std::endl;
        return (std::numeric_limits<int>::max)() - 2;
    }

    return error_count + config_file_error_count;
}
