#include "cmd_helpers.h"

#include "topology_loader.h"
#include "topology_data.h"
#include "common.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;
    fixer::SRID srid;
    std::string verbLevel;
};

int run(const Params& params)
{
    pqxx::connection conn(params.conn);
    pqxx::work work(conn);

    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 invalidCount = 0;
    size_t degeneratePerimeterCount = 0;
    size_t degenerateRatioCount = 0;

    INFO() << "Checking " << topology.ftGroup()->name() << " group";
    for (const auto& face: topology.faces()) {
        try {
            auto points = topology.faceVertices(face.first);
            auto poly = maps::geolib3::Polygon2(points);
            double area = poly.area();
            double perimeter = poly.perimeter();
            if (perimeter < fixer::MERCATOR_EPS) {
                INFO() << "Face " << face.first << " perimeter is degenerate";
                ++degeneratePerimeterCount;
                continue;
            }
            const double ratio = area / perimeter;
            if (ratio < fixer::MERCATOR_EPS) {
                INFO() << "Face " << face.first << " is degenarate: A/P ratio is " << ratio;
                ++degenerateRatioCount;
                continue;
            }
        } catch (const fixer::InvalidFaceException& e) {
            INFO() << "Face " << face.first << " is invalid: " << e.what();
            ++invalidCount;
        } catch (const std::exception& e) {
            INFO() << "Face " << face.first << " is invalid: " << e.what();
            ++invalidCount;
        } catch (...) {
            INFO() << "Face " << face.first << " is invalid: unknown error";
            ++invalidCount;
        }

        if (invalidCount > 0 && !(invalidCount % 1000)) {
            INFO() << invalidCount << " invalid faces found";
        }
    }
    INFO() << invalidCount << " invalid faces found";
    INFO() << degeneratePerimeterCount << " degenerate perimeter faces found";
    INFO() << degenerateRatioCount << " degenerate area/perimeter ratio faces found";

    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_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: faces_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()),
            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;
    }

}
