#include "cmd_helpers.h"

#include "topology_loader.h"
#include "topology_data.h"
#include "common.h"
#include "params.h"
#include "utils/geom_helpers.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 <string>
#include <vector>
#include <list>

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

namespace {

struct Params {
    std::string conn;
    std::string schema;
    std::string ftGroup;
    std::string restrictionsConfig;
    fixer::SRID srid;
    std::string verbLevel;
};

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

    std::shared_ptr<fixer::IdGenerator> gen = std::make_shared<fixer::IdGenerator>(0);

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

    INFO() << "Loading data from schema " << params.schema;

    auto group = fixer::topologyGroupsRegistry()[params.ftGroup];

    auto topology = loader.loadData(group);
    size_t shortSegmentsCount = 0;
    size_t edgesWithShortSegmentsCount = 0;
    size_t edgesWithVertexLimitExceededCount = 0;
    size_t invalidEdgesCount = 0;

    INFO() << "Checking " << topology.ftGroup()->name() << " group";
    INFO() << "max edge vertices: " << restrictions.edgeGeometry().maxEdgeVertices();
    INFO() << "min segment length: " << restrictions.edgeGeometry().minSegmentLength();
    for (auto edgeId : topology.edgeIds()) {
        const fixer::Edge& edge = topology.edge(edgeId);
        const auto& geom = edge.linestring();
        if (geom.points().size() > restrictions.edgeGeometry().maxEdgeVertices()) {
            INFO() << "Edge " << edgeId << " has too many vertices: " << geom.points().size();
            ++edgesWithVertexLimitExceededCount;
        }
        if (!fixer::isSimple(geom.points(), edge.fnodeId() == edge.tnodeId(), true)) {
            INFO() << "Edge " << edgeId << " geometry is not simple";
            ++invalidEdgesCount;
        }
        bool hasShortSegments = false;
        for (size_t i = 0; i < geom.segmentsNumber(); ++i) {
            if (maps::geolib3::length(geom.segmentAt(i)) <
                restrictions.edgeGeometry().minSegmentLength())
            {
                ++shortSegmentsCount;
                hasShortSegments = true;
            }
        }
        if (hasShortSegments) {
            INFO() << "Edge " << edgeId << " has too short segments";
            ++edgesWithShortSegmentsCount;
        }
    }

    INFO() << edgesWithVertexLimitExceededCount << " edges with too many vertices";
    INFO() << shortSegmentsCount << " too short segments";
    INFO() << edgesWithShortSegmentsCount << " edges with too short segments";
    INFO() << invalidEdgesCount << " edges with invalid geometry";

    INFO() << "Done.";

    return 0;
}

} // namespace

int main(int argc, char* argv[])
{
    Params params;

    po::options_description desc("Allowed options");
    cmd::addOptionDescription(desc,
        cmd::g_connectionOption(), cmd::g_schemaOption(),
        cmd::g_ftGroupOption(),
        cmd::g_restrictionsConfigOption(), cmd::g_sridOption(),
        cmd::g_verboseOption(), cmd::g_helpOption());

    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: edges_validator <options> " << std::endl;
        std::cerr << desc << std::endl;
        return 1;
    }

    try {
        Params params = {
            cmd::loadParam(vm, cmd::g_connectionOption()),
            cmd::loadParam(vm, cmd::g_schemaOption()),
            cmd::loadParam(vm, cmd::g_ftGroupOption()),
            cmd::loadParam(vm, cmd::g_restrictionsConfigOption()),
            fixer::sridFromString(cmd::loadParam(vm, cmd::g_sridOption())),
            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;
    }
}
