#include "params.h"

#include <maps/libs/common/include/exception.h>
#include <yandex/maps/wiki/common/string_utils.h>

#include <boost/program_options/parsers.hpp>

#include <iostream>
#include <thread>

namespace maps::wiki::json2ymapsdf {

namespace po = boost::program_options;

namespace {

const std::string UNSPECIFIED_DEFAULT = "<DEFAULT>";

const std::string OPT_HELP = "help";
const std::string OPT_HELP_H = "help,h";
const std::string MSG_HELP = "produce help message";

const std::string OPT_CONN = "conn";
const std::string MSG_CONN = "connection string";

const std::string OPT_SCHEMA = "schema";
const std::string MSG_SCHEMA = "database schema";

const std::string OPT_JSON_FILE = "json-file";
const std::string DEFAULT_JSON_FILE = STDIN_FILENAME;
const std::string MSG_JSON_FILE = "input json file, read from stdin by default";

const std::string OPT_TMP_DIR = "tmp-dir";
const std::string DEFAULT_TMP_DIR = "./";
const std::string MSG_TMP_DIR = "directory for temporary files";

const std::string OPT_JSON_DIR = "json-dir";
const std::string MSG_JSON_DIR = "path to splitted json files";

const std::string OPT_JSON_LIST = "json-list";
const std::string MSG_JSON_LIST = "list of splitted json files, use '-' for stdin";

const std::string OPT_DELETE_JSON = "delete-json";
const std::string MSG_DELETE_JSON = "delete json files after processing";

const std::string OPT_OVERALL_VALIDATION = "overall-validation";
const std::string MSG_OVERALL_VALIDATION = "do not skip integrity checks on regional views";

const std::string OPT_LOG_LEVEL = "log-level";
const log8::Level DEFAULT_LOG_LEVEL = log8::Level::INFO;
const std::string MSG_LOG_LEVEL = "log level";

const std::string OPT_DATA_LOG = "data-error-log";
const std::string DEFAULT_DATA_LOG = STDOUT_FILENAME;
const std::string MSG_DATA_LOG = "tds data error log file, use '-' for stdout";

const std::string OPT_PROGRESS = "print-progress";
const std::string MSG_PROGRESS = "print progress info";

const std::string OPT_TRANSFORM_CFG = "transform-cfg";
const std::string DEFAULT_TRANSFORM_CFG = "/etc/yandex/maps/wiki/export/json2ymapsdf.xml";
const std::string MSG_TRANSFORM_CFG = "configuration file" \
    " (" + UNSPECIFIED_DEFAULT + "=" + DEFAULT_TRANSFORM_CFG + ")";

const std::string OPT_PRINT_CONFIGURATION = "print-configuration";
const std::string MSG_PRINT_CONFIGURATION = "print json2ymapsdf configuration";

const std::string OPT_REGIONS = "regions";
const std::string MSG_REGIONS = "export splitted datasets for specified regions";

const std::string OPT_THREADS = "threads";
const std::string MSG_THREADS = "the number of threads to use for parallelizing postgresql operations";

const std::string OPT_EXPERIMENT = "experiment";
const std::string MSG_EXPERIMENT = "use experimental config sections";

const std::string USAGE = "Usage: json2ymapsdf <options> [< <input-json-data>]";
const std::string OPTIONS_INTRO = "\nAllowed options";

const size_t MSG_LENGTH = 90;

enum Necessity
{
    Required,
    Optional
};

template <typename T>
void
loadParam(const po::variables_map& vm,
          const std::string& name,
          T& param,
          Necessity required = Optional)
{
    if (vm.count(name)) {
        param = vm[name].as<T>();
    } else if (required != Optional) {
        throw po::invalid_option_value("Missing required parameter: '" + name + "'");
    }
}

template <typename T = std::string>
T
loadParam(const po::variables_map& vm,
          const std::string& name)
{
    if (vm.count(name)) {
        return vm[name].as<T>();
    } else {
        throw po::invalid_option_value("Missing required parameter: '" + name + "'");
    }
}

void
loadParams(const po::variables_map& vm, Params& params)
{
    loadParam(vm, OPT_CONN, params.connStr, Required);
    loadParam(vm, OPT_SCHEMA, params.schema, Required);
    loadParam(vm, OPT_TRANSFORM_CFG, params.transformCfg);
    loadParam(vm, OPT_PRINT_CONFIGURATION, params.printConfigurationFile);

    loadParam(vm, OPT_JSON_FILE, params.jsonFile);
    loadParam(vm, OPT_TMP_DIR, params.tmpDir, Required);
    loadParam(vm, OPT_JSON_DIR, params.jsonDir);
    loadParam(vm, OPT_JSON_LIST, params.jsonList);
    if (params.jsonFile.empty() && params.jsonDir.empty() && params.jsonList.empty()) {
        params.jsonFile = DEFAULT_JSON_FILE;
    }
    REQUIRE (static_cast<int>(!params.jsonFile.empty())
        + static_cast<int>(!params.jsonDir.empty())
        + static_cast<int>(!params.jsonList.empty())
        == 1, "Can not use more then one input method");
    loadParam(vm, OPT_DELETE_JSON, params.deleteJson);
    loadParam(vm, OPT_OVERALL_VALIDATION, params.overallValidation);
    loadParam(vm, OPT_EXPERIMENT, params.experiment);

    if (params.experiment.empty()) {
        INFO() << "Experiment mode: [OFF]";
    } else {
        INFO() << "Experiment mode: [" << params.experiment << "]";
    }

    params.split = vm.count(OPT_REGIONS);
    if (params.split) {
        loadParam(vm, OPT_REGIONS, params.regionIds);
    }

    loadParam(vm, OPT_THREADS, params.threadCount);
}

size_t
defaultThreadsCount()
{
    auto threads = std::thread::hardware_concurrency();
    return std::max(1UL, static_cast<size_t>((threads * 2) / 3));
}

} // namespace

Params::Params()
    : threadCount(defaultThreadsCount())
{ }

Options::Options(int argc, char* argv[])
    : desc_(USAGE + OPTIONS_INTRO, MSG_LENGTH)
    , needHelp_(true)
{
    desc_.add_options()
        (OPT_HELP_H.c_str(),
            MSG_HELP.c_str())
        (OPT_CONN.c_str(),
            po::value<std::string>(),
            MSG_CONN.c_str())
        (OPT_SCHEMA.c_str(),
            po::value<std::string>(),
            MSG_SCHEMA.c_str())
        (OPT_TRANSFORM_CFG.c_str(),
            po::value<std::string>()->default_value(DEFAULT_TRANSFORM_CFG, UNSPECIFIED_DEFAULT),
            MSG_TRANSFORM_CFG.c_str())
        (OPT_TMP_DIR.c_str(),
            po::value<std::string>()->default_value(DEFAULT_TMP_DIR),
            MSG_TMP_DIR.c_str())
        (OPT_JSON_FILE.c_str(),
            po::value<std::string>(),
            MSG_JSON_FILE.c_str())
        (OPT_JSON_DIR.c_str(),
            po::value<std::string>(),
            MSG_JSON_DIR.c_str())
        (OPT_JSON_LIST.c_str(),
            po::value<std::string>(),
            MSG_JSON_LIST.c_str())
        (OPT_DELETE_JSON.c_str(),
            po::bool_switch()->default_value(false),
            MSG_DELETE_JSON.c_str())
        (OPT_OVERALL_VALIDATION.c_str(),
            po::bool_switch()->default_value(false),
            MSG_OVERALL_VALIDATION.c_str())
        (OPT_PROGRESS.c_str(),
            po::bool_switch(),
            MSG_PROGRESS.c_str())
        (OPT_DATA_LOG.c_str(),
            po::value<std::string>()->default_value(DEFAULT_DATA_LOG),
            MSG_DATA_LOG.c_str())
        (OPT_LOG_LEVEL.c_str(),
            po::value<log8::Level>()->default_value(DEFAULT_LOG_LEVEL),
            MSG_LOG_LEVEL.c_str())
        (OPT_PRINT_CONFIGURATION.c_str(),
            po::value<std::string>(),
            MSG_PRINT_CONFIGURATION.c_str())
        (OPT_REGIONS.c_str(),
            po::value<RegionIds>()->multitoken()->implicit_value({}, {}),
            MSG_REGIONS.c_str())
        (OPT_EXPERIMENT.c_str(),
            po::value<std::string>(),
            MSG_EXPERIMENT.c_str())
        (OPT_THREADS.c_str(),
            po::value<size_t>(),
            MSG_THREADS.c_str());

    try {
        po::store(po::parse_command_line(argc, argv, desc_), vm_);
        po::notify(vm_);

        if (argc == 1 || vm_.count(OPT_HELP)) {
            return;
        }

        loadParams(vm_, params_);

        if (loadParam<bool>(vm_, OPT_PROGRESS)) {
            ProgressLog::setOutput(STDIN_FILENAME);
        }
        dataErrorLog::setOutput(loadParam(vm_, OPT_DATA_LOG));
        log8::setLevel(loadParam<log8::Level>(vm_, OPT_LOG_LEVEL));

        needHelp_ = false;
    } catch (...) {
        return;
    }
}

} // namespace maps::wiki::json2ymapsdf
