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

#include <yandex/maps/coverage5/builder.h>
#include <yandex/maps/wiki/common/string_utils.h>

#include <boost/lexical_cast.hpp>
#include <boost/program_options.hpp>

#include <maps/libs/geolib/include/multipolygon.h>
#include <maps/libs/geolib/include/serialization.h>

#include <pqxx/pqxx>
#include <cstdint>
#include <iostream>

namespace po = boost::program_options;

namespace maps {
namespace wiki {
namespace {

const std::string OPT_HELP = "help";
const std::string OPT_CONN = "conn";
const std::string OPT_SCHEMANAME = "schemaname";
const std::string OPT_LAYER = "layer";
const std::string OPT_LEVEL_KIND = "level-kind";

const int EXITCODE_OK = 0;
const int EXITCODE_LOGIC_ERROR = 1;
const int EXITCODE_RUNTIME_ERROR = 2;

template <typename Type>
Type
getValue(const po::variables_map& vm, const std::string& option)
{
    if (!vm.count(option)) {
        return {};
    }
    try {
        return vm[option].as<Type>();
    } catch (...) {
        ERROR() << "invalid type of option: " << option;
        throw;
    }
}


class Runner
{
public:
    explicit Runner(const po::variables_map& vm)
        : vm(vm)
    {}

    int run() const
    {
        pqxx::connection conn(getValue<std::string>(vm, OPT_CONN));
        pqxx::work work(conn);
        work.exec("set search_path=" + getValue<std::string>(vm, OPT_SCHEMANAME) + ",public");

        auto layer = getValue<std::string>(vm, OPT_LAYER);
        std::string version = "1";
        auto builder = coverage5::dataLayerBuilder(
            layer + ".mms." + version, layer, version, boost::none);

        auto query =
            "SELECT ad_id,isocode,ST_AsBinary(ST_Multi(ST_Buffer(shape,0.000002)))"
            " FROM ad JOIN ad_geom USING(ad_id)"
            " WHERE level_kind IN (" + getValue<std::string>(vm, OPT_LEVEL_KIND) + ")"
            " AND g_ad_id IS NULL"
            " AND isocode IS NOT NULL";
        auto rows = work.exec(query);
        INFO() << "ad size: " << rows.size();

        typedef int64_t ID;

        std::map<std::string, std::set<ID>> isocode2ids;
        for (const auto& row : rows) {
            auto id = row[0].as<ID>(0);
            auto isocode = row[1].as<std::string>({});
            auto wkb = pqxx::binarystring(row[2]).str();

            try {
                std::istringstream rawStream(wkb);
                auto geoms = geolib3::WKB::read<geolib3::MultiPolygon2>(rawStream);
                INFO() << id << " : " << isocode << " polygons: " << geoms.polygonsNumber();

                builder->addRegion(id, boost::none, boost::none, std::string(isocode), {}, geoms);
                isocode2ids[isocode].insert(id);
            } catch (maps::Exception& e) {
                ERROR() << "invalid geometry, id: " << id << " isocode: " << isocode << " error: " << e;
            }
        }
        builder->build();

        INFO() << "isocodes: " << isocode2ids.size();
        for (const auto& pair : isocode2ids) {
            INFO() << pair.first << " : " << common::join(pair.second, ',');
        }

        return EXITCODE_OK;
    }

private:
    const po::variables_map& vm;
};

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

int main(int argc, char** argv)
{
    using namespace maps::wiki;

    po::options_description desc("Usage: <tool> <options>\nOptions");
    desc.add_options()
        (OPT_HELP.c_str(),
            "show help message")
        (OPT_CONN.c_str(), po::value<std::string>(),
            "connection string)")
        (OPT_SCHEMANAME.c_str(), po::value<std::string>(),
            "ymapsdf schema name")
        (OPT_LAYER.c_str(), po::value<std::string>()->default_value("ad1"),
            "layer name")
        (OPT_LEVEL_KIND.c_str(), po::value<std::string>()->default_value("1"),
            "level kinds (csv string, example: 1,2,3,4,5)")
    ;

    po::variables_map vm;
    try {
        po::store(po::parse_command_line(argc, argv, desc), vm);
    } catch (po::error& e) {
        std::cerr << "error parsing command line: " << e.what() << std::endl;
        return EXITCODE_LOGIC_ERROR;
    }
    po::notify(vm);

    if (argc == 1 || vm.count(OPT_HELP)) {
        std::cerr << desc << std::endl;
        return EXITCODE_LOGIC_ERROR;
    }

    try {
        return Runner(vm).run();
    } catch(maps::Exception& ex) {
        ERROR() << ex;
    } catch(std::exception& ex) {
        ERROR() << ex.what();
    }
    return EXITCODE_RUNTIME_ERROR;
}
