#include "cmd_helpers.h"

#include "topology_loader.h"
#include "topology_deleter.h"
#include "topology_uploader.h"
#include "topology_fixer.h"
#include "topology_data.h"
#include "db_strings.h"
#include "common.h"
#include "topology_group.h"

#include "params.h"

#include <maps/libs/common/include/exception.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/libs/geolib/include/polygon.h>

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

#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <vector>
#include <list>
#include <future>

namespace po = boost::program_options;
namespace fixer = maps::wiki::topology_fixer;
namespace cmd = fixer::cmd;

namespace {

struct Params {
    std::string conn;
    std::string inputSchema;
    std::string outputSchema;
    std::string ftGroup;
    std::string restrictionsConfig;
    fixer::DBIdType maxYmapsdfId;
    fixer::SRID srid;
    size_t threads;
    std::string verbLevel;
};

void
deleteTopology(const Params& params, const fixer::TopologyGroup& group)
{
    INFO() << "Deleting data from schema " << params.outputSchema;
    fixer::TopologyDeleter deleter(params.conn, params.outputSchema);
    deleter.deleteTopology(group);
    INFO() << "Deleting data from schema " << params.outputSchema << ": [Done]";
}

int run(const Params& params)
{
    maps::xml3::Doc configDoc(params.restrictionsConfig);
    fixer::config::Restrictions restrictions(configDoc.root());

    INFO() << "Max ymapsdf id in output schema: " << params.maxYmapsdfId;
    std::shared_ptr<fixer::IdGenerator> gen =
        std::make_shared<fixer::IdGenerator>(params.maxYmapsdfId);

    fixer::TopologyLoader loader(params.conn, params.inputSchema, gen, params.srid);

    INFO() << "Loading data from schema " << params.inputSchema;
    ASSERT(!params.ftGroup.empty());
    auto group = fixer::topologyGroupsRegistry()[params.ftGroup];
    fixer::TopologyData topology = loader.loadData(group);
    INFO() << "Loading data from schema " << params.inputSchema << ": [Done]";

    std::future<void> deleteTopologyResult =
        std::async(std::launch::async, deleteTopology, params, *group);

    fixer::TopologyFixer fixer(topology, restrictions, params.threads);
    const auto& topologyGroup = topology.ftGroup();
    INFO() << "Fixing " << topologyGroup->name() << " group";
    INFO() << "max threads to use: " << params.threads;
    fixer();
    INFO() << "Fixing " << topologyGroup->name() << " [Done]";

    deleteTopologyResult.get();

    fixer::TopologyUploader uploader(params.conn, params.outputSchema, params.inputSchema);
    INFO() << "Uploading data for " << topologyGroup->name();
    uploader.uploadData(topology);
    INFO() << "Uploading " << topologyGroup->name() << " [Done]";

    INFO() << "Done.";

    return 0;
}

} // namespace

int main(int argc, char* argv[])
{
    po::options_description desc("Allowed options");
    cmd::addOptionDescription(desc,
        cmd::g_connectionOption(),
        cmd::g_inputSchemaOption(), cmd::g_outputSchemaOption(),
        cmd::g_ftGroupOption(), cmd::g_restrictionsConfigOption(), cmd::g_sridOption(),
        cmd::g_startYMapsDFIdOption(),
        cmd::g_threadsOption(),
        cmd::g_helpOption(), cmd::g_verboseOption());

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

    if (argc < 2 || vm.count(cmd::OPT_HELP)) {
        std::cerr << "Usage: topology_fixer <options> " << std::endl;
        std::cerr << desc << std::endl;
        return 1;
    }

    try {
        Params params = {
            cmd::loadParam(vm, cmd::g_connectionOption()),
            cmd::loadParam(vm, cmd::g_inputSchemaOption()),
            cmd::loadParam(vm, cmd::g_outputSchemaOption()),
            cmd::loadParam(vm, cmd::g_ftGroupOption()),
            cmd::loadParam(vm, cmd::g_restrictionsConfigOption()),
            cmd::loadParam(vm, cmd::g_startYMapsDFIdOption()),
            fixer::sridFromString(cmd::loadParam(vm, cmd::g_sridOption())),
            cmd::loadParam(vm, cmd::g_threadsOption()),
            cmd::loadParam(vm, cmd::g_verboseOption())};

        maps::log8::setLevel(params.verbLevel);
        return run(params);
    }
    catch (const maps::Exception& e) {
         FATAL() << "FAIL: " << e;
         return 1;
    }
    catch (const std::exception& e) {
        FATAL() << "FAIL: " << e.what();
        return 1;
    }
}
