#include "cmd_line_options.h"

#include "exit_codes.h"

#include <maps/libs/common/include/exception.h>
#include <maps/libs/log8/include/log8.h>
#include <yandex/maps/program_options/option_description.h>
#include <maps/libs/stringutils/include/join.h>
#include <maps/libs/stringutils/include/to_string.h>

#include <iostream>
#include <stdexcept>
#include <string>

namespace maps {
namespace wiki {
namespace ad_geometry_builder {
namespace {

const std::string PROGRAM_DESCRIPTION =
R"(Builds AD's geometry and fills the table `ad_geom` with this
information.

The geometry is built by means of the information from the following
ymapsdf tables:
* ad;
* ad_face;
* face_edge; and
* edge.

Usage)";

const std::vector<log8::Level> LOG_LEVELS = {
    log8::Level::FATAL, log8::Level::ERROR, log8::Level::WARNING,
    log8::Level::INFO, log8::Level::DEBUG
};

const std::string LOG_LEVELS_STR =
    "(" + stringutils::join(stringutils::toStrings(LOG_LEVELS), ", ") + ")";

} // namespace


CmdLineOptions parseCommandLine(int argc, char* argv[])
{
    using namespace program_options;

    options_description options{PROGRAM_DESCRIPTION};
    OptionDescription<std::string> connOpt(&options, "conn,c", "A connection string.");
    OptionDescription<std::string> inSchemaOpt(&options, "in-schema,i", "An input schema.");
    OptionDescription<std::string> outSchemaOpt(&options, "out-schema,o", "An output schema.");
    OptionDescription<size_t> levelKindOpt(&options, "level-kind,l", "A level kind [default: 1 - country].", 1);
    // The default value below must be synchronized with the value from
    // garden/modules/geometry_collector/pymod/yandex/maps/garden/modules/geometry_collector/constants.py
    OptionDescription<double> perimeterTolerance(&options, "perimeter-tolerance,t",
        "The tolerance used for determining if a built geometry is valid "
        "in a sense of having large enough perimeter as compared "
        "to the length of underlying edges. A value from the range (0; 1).",
        0.9);
    OptionDescription<log8::Level> logLevelOpt(&options, "log-level", ("A log level " + LOG_LEVELS_STR + ".").c_str(), log8::Level::INFO);

    try {
        program_options::parseCommandLine(argc, argv, options, true);
    } catch (const HelpOptionSpecifiedException& e) {
        std::cout << e.what() << std::endl;
        exit(EXITCODE_OK);
    } catch (const boost::program_options::error& e) {
        FATAL() << e.what();
        std::cout << options;
        exit(EXITCODE_WRONG_CMD_LINE_OPTION_ERROR);
    } catch (const std::runtime_error& e) {
        FATAL() << e.what();
        std::cout << options;
        exit(EXITCODE_WRONG_CMD_LINE_OPTION_ERROR);
    }

    try {
        connOpt.required();
        inSchemaOpt.required();
        outSchemaOpt.required();
        REQUIRE(*levelKindOpt >= 1,
                "Level kind must be greater than or equal to 1 (country).");
        REQUIRE(*levelKindOpt <= 7,
                "Level kind must be less than or equal to 7 (sub-districts, city blocks and localities within city limits).");
        REQUIRE(*perimeterTolerance > 0,
                "Perimeter tolerance must be greater than 0.");
        REQUIRE(*perimeterTolerance < 1,
                "Perimeter tolerance must be less than 1.");
    } catch (const RuntimeError& e) {
        FATAL() << e.what();
        std::cout << options;
        exit(EXITCODE_WRONG_CMD_LINE_OPTION_ERROR);
    }

    return {*connOpt, *inSchemaOpt, *outSchemaOpt, *levelKindOpt, *perimeterTolerance, *logLevelOpt};
}


} // namespace ad_geometry_builder
} // namespace wiki
} // namespace maps
