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

    Copyright (c) 2001-2006 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)
=============================================================================*/

#if !defined(BOOST_WAVE_LIBS_WAVE_TEST_CMD_LINE_UTILS_HPP)
#define BOOST_WAVE_LIBS_WAVE_TEST_CMD_LINE_UTILS_HPP

#include <string>
#include <fstream>
#include <vector>

#include <boost/config.hpp>
#include <boost/assert.hpp>
#include <boost/any.hpp>

///////////////////////////////////////////////////////////////////////////////
// forward declarations only
namespace cmd_line_utils 
{
    class include_paths;
}

namespace boost { namespace program_options 
{
    void validate(boost::any &v, std::vector<std::string> const &s,
        cmd_line_utils::include_paths *, int);
}} // boost::program_options

///////////////////////////////////////////////////////////////////////////////
#include <boost/program_options.hpp>

///////////////////////////////////////////////////////////////////////////////
namespace cmd_line_utils {

    namespace po = boost::program_options;

    ///////////////////////////////////////////////////////////////////////////
    // Additional command line parser which interprets '@something' as an 
    // option "config-file" with the value "something".
    inline std::pair<std::string, std::string> 
    at_option_parser(std::string const& s)
    {
        if ('@' == s[0]) 
            return std::make_pair(std::string("config-file"), s.substr(1));
        else
            return std::pair<std::string, std::string>();
    }

    ///////////////////////////////////////////////////////////////////////////
    // class, which keeps the include file information read from the options
    class include_paths 
    {
    public:
        include_paths() : seen_separator(false) {}

        std::vector<std::string> paths;       // stores user paths
        std::vector<std::string> syspaths;    // stores system paths
        bool seen_separator;                  // options contain a '-I-' option

        // Function which validates additional tokens from the given option.
        static void 
        validate(boost::any &v, std::vector<std::string> const &tokens)
        {
            if (v.empty())
                v = boost::any(include_paths());

            include_paths *p = boost::any_cast<include_paths>(&v);
            BOOST_ASSERT(NULL != p);

        // Assume only one path per '-I' occurrence.
            std::string const& t = po::validators::get_single_string(tokens);
            if (t == "-") {
            // found -I- option, so switch behaviour
                p->seen_separator = true;
            } 
            else if (p->seen_separator) {
            // store this path as a system path
                p->syspaths.push_back(t); 
            } 
            else {
            // store this path as an user path
                p->paths.push_back(t);
            }            
        }
    };

    ///////////////////////////////////////////////////////////////////////////
    // Read all options from a given config string, parse and add them to the
    // given variables_map
    inline void 
    read_config_options(int debuglevel, std::string const &indata, 
        po::options_description const &desc, po::variables_map &vm)
    {
        if (9 == debuglevel) {
            std::cerr << "read_config_options: reading config options" 
                      << std::endl;
        }

        std::istringstream istrm(indata);
        
        std::vector<std::string> options;
        std::string line;

        while (std::getline(istrm, line)) {
        // skip empty lines
            std::string::size_type pos = line.find_first_not_of(" \t");
            if (pos == std::string::npos) 
                continue;

        // skip comment lines
            if ('#' != line[pos])
                options.push_back(line);
        }

        if (options.size() > 0) {
            if (9 == debuglevel) {
                std::cerr << "read_config_options: options size is: " 
                          << (int)options.size() << std::endl;
            }

            // (the (int) cast is to make the True64 compiler happy)
            using namespace boost::program_options::command_line_style;
            po::store(po::command_line_parser(options)
                .options(desc).style((int)unix_style).run(), vm);
            po::notify(vm);
        }

        if (9 == debuglevel) {
            std::cerr << "read_config_options: succeeded to read config options" 
                      << std::endl;
        }
    }

    ///////////////////////////////////////////////////////////////////////////
    // Read all options from a given config file, parse and add them to the
    // given variables_map
    inline bool
    read_config_file(int debuglevel, std::string const &filename, 
        po::options_description const &desc, po::variables_map &vm)
    {
        if (9 == debuglevel) {
            std::cerr << "read_config_file: reading config options" 
                      << std::endl;
        }

        std::ifstream ifs(filename.c_str());

        if (!ifs.is_open()) {
            std::cerr 
                << "testwave: " << filename 
                << ": command line warning: config file not found"
                << std::endl;
            return false;
        }

    // parse the file and extract all given arguments and options
        std::vector<std::string> options;
        std::string line;

        while (std::getline(ifs, line)) {
        // skip empty lines
            std::string::size_type pos = line.find_first_not_of(" \t");
            if (pos == std::string::npos) 
                continue;

        // skip comment lines
            if ('#' != line[pos]) 
                options.push_back(line);
        }

        if (options.size() > 0) {
            if (9 == debuglevel) {
                std::cerr << "read_config_file: options size is: " 
                          << (int)options.size() << std::endl;
            }

        // treat positional arguments as --input parameters
            po::positional_options_description p;
            p.add("input", -1);

        // parse the vector of lines and store the results into the given
        // variables map
            // (the (int) cast is to make the True64 compiler happy)
            using namespace boost::program_options::command_line_style;
            po::store(po::command_line_parser(options)
                .options(desc).positional(p).style((int)unix_style).run(), vm);
            po::notify(vm);
        }


        if (9 == debuglevel) {
            std::cerr << "read_config_file: succeeded to read config options" 
                      << std::endl;
        }
        return true;
    }

    ///////////////////////////////////////////////////////////////////////////
    // predicate to extract all positional arguments from the command line
    struct is_argument 
    {
        bool operator()(po::option const &opt)
        {
            return (opt.position_key == -1) ? true : false;
        }
    };

///////////////////////////////////////////////////////////////////////////////
}   // namespace cmd_line_utils

///////////////////////////////////////////////////////////////////////////////
//
//  Special validator overload, which allows to handle the -I- syntax for
//  switching the semantics of an -I option.
//
//  This must be injected into the boost::program_options namespace
//
///////////////////////////////////////////////////////////////////////////////
namespace boost { namespace program_options {

    inline void 
    validate(boost::any &v, std::vector<std::string> const &s,
        cmd_line_utils::include_paths *, int) 
    {
        cmd_line_utils::include_paths::validate(v, s);
    }

}}  // namespace boost::program_options

#endif // !defined(BOOST_WAVE_LIBS_WAVE_TEST_CMD_LINE_UTILS_HPP)
