#pragma once

#ifndef WMCBASEOPTIONS_H_INCLUDED_
#define WMCBASEOPTIONS_H_INCLUDED_

//! \file base_options.h

#include <iostream>
#include <fstream>
#include <sstream>

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

#include <Poco/Util/AbstractConfiguration.h>
#include <Poco/Util/Option.h>
#include <Poco/Util/OptionSet.h>
#include <Poco/Util/OptionCallback.h>


namespace NWebmaster {
/*!
 * \defgroup WMCApplicationOptions
 * \brief The set of template mixin classes provides orthogonal implementation of typical cmdline options: logging, debug, DB, etc
 * \par Mixin definition from GoF
 * A mixin class is a class that's intended to provide an optional interface or
 * functionality to other classes. It's similar to an abstract class in that it's not intended to be instantiated.
 * Mixin classes require multiple inheritance
 *
 */

/*! \par Rationale:
 * Option strings below declared as static const instead of static const class members
 * to make their actual values visible for a user of the class - no need to open .cpp file
 * Additionally, design of these classes does not assume implementation files at all (headers only)
 */
static const char *const OP_WORKING_DIR = "working-dir";            //!< --working-dir=<path>
static const char *const OP_LOG = "log";                            //!< --log=<file::level> or -l- for stdout

static const char *const OP_DUMP_CONFIG_RAW = "dump-config-raw";    //!< --dump-config-raw[=<file>] Dump current config (unresolved)
static const char *const OP_DUMP_CONFIG = "dump-config";            //!< --dump-config[=<file>] Dump current config (resolved)
static const char *const OP_DRYRUN = "dry-run";                     //!< --dry-run  "Dry run" mode requested. Provides method isDryRun()



/*! \brief Mix-in (policy) class defines command-line options "working-dir" and "log".
 * \ingroup WMCApplicationOptions
 * Example of usage:
 * \code
 *  class MyApplication
 *      : public WMCBaseApplication
 *      , public DebugOptions<MyApplication>
 *      , public LoggingOptions<MyApplication>
 *  {
 *  protected:
 *      void defineOptions(OptionSet& options) {
 *
 *          WMCBaseApplication::defineOptions(options);
 *          DebugOptions<MyApplication>::defineOptions(options);
 *          LoggingOptions<MyApplication>::defineOptions(options);
 *
 *          // Add your specific options here
 *      }
 *
 *      virtual int doMain(const std::vector<std::string>& args);
 *  };
 * \endcode
 * Class LoggingOptions defines command-line options "working-dir" and "log".
 * Parameters accessible as
 * \code
 *  std::string wd_path =
 *      config().hasOption(OP_WORKING_DIR) ?
 *          config().getString(OP_WORKING_DIR)
 *          : "";
 *
 *  if ( isDryRun() ) {
 *      // dry run mode
 *  }
 * \endcode
 */
template <class App>
class LoggingOptions {
protected:
    // non-virtual, the class is not intended for polymorphic usage
    void defineOptions(Poco::Util::OptionSet &options) {
        options.addOption(
            Poco::Util::Option(OP_WORKING_DIR, "d",
                               "Working directory - overwrites ${working-dir} property.\n"
                               "Can be used as a root directory for the logs and temporary data files\n")
            .required(false)
            .repeatable(false)
            .argument("path")
            .binding(OP_WORKING_DIR)
        );

        options.addOption(
            Poco::Util::Option(OP_LOG, "l",
                               "Log file path or '-' for stdout\n"
                               "Use FILE::LEVEL for logging level. Available levels are:\n"
                               "- fatal critical error warning notice information debug trace -\n"
                               "FILE or ::LEVEL (but not both) can be omitted\n"
                              )
            .required(false)
            .repeatable(false)
            .argument("<FILE::LEVEL>")
            //    .binding("log")
            .callback(OptionCallback<LoggingOptions>(this, &LoggingOptions::optionLogCallback))
        );
    }

private:
    void optionLogCallback(const std::string & /* name */, const std::string &value) {
        const App *app = static_cast<const App *>(this);
        std::string logPath = value;
        size_t n = logPath.find("::");
        if (n != std::string::npos) {
            std::string level(logPath.begin() + n+2, logPath.end()); // Poco will perform validity check
            logPath.erase(n);
            app->config().setString("logging.loggers.root.level",  level);
        }

        if (logPath == "-") {
            app->config().setString("logging.loggers.root.channel",  "stdout");
        } else if (!logPath.empty()) {
            app->config().setString("logging.loggers.root.channel",  "logfile");
            app->config().setString("logging.channels.logfile.path",  logPath);
        }
    }

protected:  // mix-in class; shall not be instantiated. NO FIELD MEMBERS ALLOWED
    LoggingOptions() {}
    ~LoggingOptions() {}
};



/*! \brief Mix-in class defines command-line options "dump-config", "dump-config-raw" and "dry-run".
 * \ingroup WMCApplicationOptions
 * Example of usage:
 * \code
 *  class MyApplication
 *      : public WMCBaseApplication
 *      , public DebugOptions<MyApplication>
 *      , public LoggingOptions<MyApplication>
 *  {
 *  protected:
 *      void defineOptions(OptionSet& options) {
 *
 *          WMCBaseApplication::defineOptions(options);
 *          DebugOptions<MyApplication>::defineOptions(options);
 *          LoggingOptions<MyApplication>::defineOptions(options);
 *
 *          // Add your specific options here
 *      }
 *
 *      virtual int doMain(const std::vector<std::string>& args);
 *  };
 * \endcode
 */
template <class App>
class DebugOptions {
protected:
    // non-virtual, the class is not intended for polymorphic usage
    void defineOptions(Poco::Util::OptionSet &options) {
        options.addOption(
            Poco::Util::Option(OP_DUMP_CONFIG, "",
                               "Dump configuration to the file. If the value contains references "
                               "to other properties (${<property>}), these are expanded. "
                               "If no argument passed then write to standard output\n")
            .required(false)
            .repeatable(false)
            .argument("<config-file>", false)
            .binding(OP_DUMP_CONFIG)
            .callback(OptionCallback<DebugOptions>(this, &DebugOptions::dumpConfigCallback))
        );

        options.addOption(
            Poco::Util::Option(OP_DUMP_CONFIG_RAW, "",
                               "Dump configuration to the file as raw strings. "
                               "If no argument passed then write to standard output\n")
            .required(false)
            .repeatable(false)
            .argument("<config-file>", false)
            .binding(OP_DUMP_CONFIG_RAW)
            .callback(OptionCallback<DebugOptions>(this, &DebugOptions::dumpConfigRawCallback))
        );

        options.addOption(
            Poco::Util::Option(OP_DRYRUN, "",
                               "Perform a trial run with no changes made\n")
            .binding(OP_DRYRUN)
            //  .callback(Poco::Util::OptionCallback<DebugOptions>(this, &DebugOptions::dryRunCallback))
        );
    }

public:
    //! \return true if dry run mode was requested (cmdline option --dry-run)
    bool isDryRun() const {
        const App *app = static_cast<const App *>(this);
        return app->config().hasOption(OP_DRYRUN);
    }


    //! Print the current command line options as pairs key=value
    void dumpCmdlineOptions(std::ostream &os) const {
        const App *app = static_cast<const App *>(this);
        Poco::Util::OptionSet opts = app->options();
        for (Poco::Util::OptionSet::Iterator o = opts.begin(); o!= opts.end(); ++o) {
            const std::string &binding = o->binding();
            if ( !binding.empty() && app->config().hasOption(binding)) {
                std::string value = app->config().getString(binding);
                os << o->fullName() << " = " << value << '\n';
            }
        }
    }

    //! Print original command line
    void dumpCmdline(std::ostream &os) const {
        const App *app = static_cast<const App *>(this);
        int n = app->config().hasOption("application.argc") ? app->config().getInt("application.argc") : 0;
        for (int k=0; k!=n; ++k) {
            std::ostringstream o;
            o << "application.argv[" << k << "]";
            std::string v = app->config().hasOption(o.str()) ? app->config().getRawString(o.str()) : "";
            if (!v.empty()) {
                os << v << " ";
            }
        }
    }

    //! Dump the current config
    void dumpConfig(std::ostream &os, bool expand = false, const std::string &base = "") const {
        Poco::Util::AbstractConfiguration::Keys keys;
        const App *app = static_cast<const App *>(this);
        app->config().keys(base, keys);
        for (Poco::Util::AbstractConfiguration::Keys::const_iterator k=keys.begin(); k!=keys.end(); ++k) {
            std::string s = !base.empty() ? base + "." : "";
            s+=*k;
            if (app->config().hasProperty(s)) {
                os << s;
                os << " = " <<
                   (expand ? app->config().getString(s) : app->config().getRawString(s));
                os << '\n';
            }
            dumpConfig(os, expand, s);
        }
    }

private:
    void dumpConfigCallback_(bool expand, const std::string &value) {
        if (value.empty() || value == "-") {
            dumpConfig(std::cout, expand, "");
        } else {
            std::ofstream out(value.c_str());
            dumpConfig(out, expand, "");
        }
    }

    void dumpConfigCallback(const std::string & /* name */, const std::string &value) {
        dumpConfigCallback_(true, value);
    }

    void dumpConfigRawCallback(const std::string & /* name */, const std::string &value) {
        dumpConfigCallback_(false, value);
    }

protected:  // mix-in class; shall not be instantiated. NO FIELD MEMBERS ALLOWED
    DebugOptions() {}
    ~DebugOptions() {}
};

} // namespace NWebmaster

#endif // WMCBASEOPTIONS_H_INCLUDED_

