#include "tools.h"

#include <maps/libs/common/include/exception.h>
#include <maps/libs/log8/include/log8.h>
#include <yandex/maps/wiki/common/default_config.h>
#include <yandex/maps/wiki/common/extended_xml_doc.h>

#include <boost/program_options/options_description.hpp>
#include <boost/program_options/parsers.hpp>
#include <boost/program_options/variables_map.hpp>

#include <memory>
#include <string>

namespace bpo = boost::program_options;
using namespace maps;
using namespace wiki;
using namespace wiki::contours;

namespace {

const std::string OPT_CFG = "cfg";
const std::string OPT_MIN_VERTICES = "min-vertices";
const std::string OPT_MODE = "mode";
const std::string OPT_SYSLOG_TAG = "syslog-tag";
const std::string OPT_TOLERANCE = "tolerance";
const std::string OPT_HELP = "help";
const std::string OPT_IDS_FILE = "ids-file";
const std::string OPT_BATCH_SIZE = "batch-size";

const std::string MODE_RESET = "reset";
const std::string MODE_UPDATE = "update";
const std::string MODE_PUBSUB = "pubsub";

const std::chrono::seconds WAIT_INTERVAL {1};

} // namespace

int main(int argc, char* argv[]) try
{
    Params params;
    bpo::options_description desc("Allowed options");
    desc.add_options()
        ( OPT_CFG.c_str()
        , bpo::value<std::string>()
        , "databases xml-config (optional)"
        )
        ( OPT_HELP.c_str()
        , "produce help message"
        )
        ( OPT_MIN_VERTICES.c_str()
        , bpo::value<size_t>()->default_value(params.minVerticesCount)
        , "points number not to split"
        )
        ( OPT_MODE.c_str()
        , bpo::value<std::string>()->default_value(MODE_PUBSUB)
        , std::string
            ( MODE_RESET + ": delete watermark to allow a service to resync view\n"
            + MODE_PUBSUB + ": start as a service\n"
            + MODE_UPDATE + ": update objects\n"
            ).c_str()
        )
        ( OPT_SYSLOG_TAG.c_str()
        , bpo::value<std::string>()
        , "enable syslog logging with the given tag (optional)"
        )
        ( OPT_TOLERANCE.c_str()
        , bpo::value<double>()->default_value(params.tolerance)
        , "mercator epsilon"
        )
        ( OPT_IDS_FILE.c_str()
        , bpo::value<std::string>()
        , "file with ids for update mode"
        )
        ( OPT_BATCH_SIZE.c_str()
        , bpo::value<size_t>()->default_value(params.commitsBatchSize)
        , "commits batch size for pubsub mode"
        )
        ;

    bpo::variables_map vm;
    bpo::store(bpo::parse_command_line(argc, argv, desc), vm);
    bpo::notify(vm);

    if (vm.count(OPT_HELP)) {
        std::cout << desc << std::endl;
        return EXIT_SUCCESS;
    }

    std::unique_ptr<wiki::common::ExtendedXmlDoc> doc;
    if (vm.count(OPT_CFG)) {
        doc.reset(new wiki::common::ExtendedXmlDoc(vm[OPT_CFG].as<std::string>()));
    } else {
        doc = wiki::common::loadDefaultConfig();
    }

    params.minVerticesCount = vm[OPT_MIN_VERTICES].as<size_t>();
    if (vm.count(OPT_SYSLOG_TAG)) {
        auto tag = vm[OPT_SYSLOG_TAG].as<std::string>();
        log8::setBackend(log8::toSyslog(tag));
    }
    params.tolerance = vm[OPT_TOLERANCE].as<double>();
    params.commitsBatchSize = vm[OPT_BATCH_SIZE].as<size_t>();

    const auto mode = vm[OPT_MODE].as<std::string>();

    auto viewPool = makePoolHolder(*doc, DB_VIEW_TRUNK, POOL_EDITOR_TOOL);
    if (MODE_RESET == mode) {
        resetWatermark(*viewPool, params);
    }  else if (MODE_UPDATE == mode) {
        if (!vm.count(OPT_IDS_FILE)) {
            std::cout << desc << std::endl;
            return EXIT_SUCCESS;
        }
        params.idsFile = vm[OPT_IDS_FILE].as<std::string>();
        auto revisionPool = makePoolHolder(*doc, DB_LONG_READ, POOL_EDITOR_TOOL);
        handleIdsFromFile(*revisionPool, *viewPool, params);
    } else if (MODE_PUBSUB == mode) {
        auto revisionPool = makePoolHolder(*doc, DB_LONG_READ, POOL_EDITOR_TOOL);
        while (!SignalListener::singleton().isStopped()) {
            try {
                if (handleNextCommits(*revisionPool, *viewPool, params)) {
                    continue;
                }
            } catch (const maps::Exception& e) {
                ERROR() << e;
            } catch (const std::exception& e) {
                ERROR() << e.what();
            }
            SignalListener::singleton().sleep(WAIT_INTERVAL);
        }
        INFO() << "service is shutting down";
    } else {
        throw maps::Exception() << "unexpected " << OPT_MODE << " arg: " << mode;
    }
    return EXIT_SUCCESS;
} catch (const maps::Exception& e) {
    ERROR() << e;
    return EXIT_FAILURE;
} catch (const std::exception& e) {
    ERROR() << e.what();
    return EXIT_FAILURE;
} catch (...) {
    ERROR() << "unknown error";
    return EXIT_FAILURE;
}
