#include "thread_geometry.h"
#include "thread_geometry_helpers.h"

#include <maps/wikimap/mapspro/libs/masstransit/masstransit.h>

#include <yandex/maps/wiki/common/misc.h>
#include <maps/libs/geolib/include/serialization.h>

namespace maps::wiki::masstransit {

YmapsdfBusGeometryHandler::YmapsdfBusGeometryHandler(Masstransit& masstransit)
    : YmapsdfObjectHandler(masstransit, masstransit.threadGeometries)
{ }

std::vector<FtType>
YmapsdfBusGeometryHandler::ftTypes() const
{
    return {FtType::TransportBusThread};
}

std::string
YmapsdfBusGeometryHandler::loadRowsSqlTemplate() const
{
    return BUS_ROWS_SQL_TEMPLATE;
}

void
YmapsdfBusGeometryHandler::addObject(const pqxx::row& tuple)
{
    const auto threadId = getAttr<DBID>(tuple, ymapsdf::ID);
    if (!masstransit_.threadGeometries.count(threadId)) {
        auto threadGeom = std::make_shared<ThreadGeometry>(threadId);
        insert(threadId, threadGeom);
    }
    auto& threadGeom = masstransit_.threadGeometries[threadId];

    addRoutedElement(threadGeom, tuple);
}

void
YmapsdfBusGeometryHandler::updateObject(Object& object)
{
    auto& threadGeom = dynamic_cast<ThreadGeometry&>(object);
    if (!common::isIn(masstransit_.threads[threadGeom.threadId].ftType, ftTypes())) {
        return;
    }

    routeThreadGeometry(threadGeom, masstransit_, UseConditions::Yes);
}

YmapsdfTramGeometryHandler::YmapsdfTramGeometryHandler(Masstransit& masstransit)
    : YmapsdfObjectHandler(masstransit, masstransit.threadGeometries)
{ }

std::vector<FtType>
YmapsdfTramGeometryHandler::ftTypes() const
{
    return {FtType::TransportTramThread};
}

std::string
YmapsdfTramGeometryHandler::loadRowsSqlTemplate() const
{
    return TRAM_ROWS_SQL_TEMPLATE;
}

void
YmapsdfTramGeometryHandler::addObject(const pqxx::row& tuple)
{
    const auto threadId = getAttr<DBID>(tuple, ymapsdf::ID);
    if (!masstransit_.threadGeometries.count(threadId)) {
        auto threadGeom = std::make_shared<ThreadGeometry>(threadId);
        insert(threadId, threadGeom);
    }
    auto& threadGeom = masstransit_.threadGeometries[threadId];

    addRoutedElement(threadGeom, tuple);
}

void
YmapsdfTramGeometryHandler::updateObject(Object& object)
{
    auto& threadGeom = dynamic_cast<ThreadGeometry&>(object);
    if (!common::isIn(masstransit_.threads[threadGeom.threadId].ftType, ftTypes())) {
        return;
    }

    routeThreadGeometry(threadGeom, masstransit_, UseConditions::Yes);
}

YmapsdfMetroGeometryHandler::YmapsdfMetroGeometryHandler(Masstransit& masstransit)
    : YmapsdfObjectHandler(masstransit, masstransit.threadGeometries)
{ }

std::vector<FtType>
YmapsdfMetroGeometryHandler::ftTypes() const
{
    return {
        FtType::TransportMetroThread,
        FtType::TransportWaterwayThread
    };
}

std::string
YmapsdfMetroGeometryHandler::loadRowsSqlTemplate() const
{
    return METRO_ROWS_SQL_TEMPLATE;
}

void
YmapsdfMetroGeometryHandler::addObject(const pqxx::row& tuple)
{
    const auto threadId = getAttr<DBID>(tuple, ymapsdf::ID);
    const auto shape = getAttr<std::string>(tuple, ymapsdf::SHAPE);
    const auto edgePolyline = geolib3::WKT::read<Polyline>(shape);

    auto threadGeom = std::make_shared<ThreadGeometry>(threadId);
    threadGeom->polyline = edgePolyline;

    insert(threadId, threadGeom);
}

void
YmapsdfMetroGeometryHandler::updateObject(Object& object)
{
    auto& threadGeom = dynamic_cast<ThreadGeometry&>(object);
    const auto threadId = threadGeom.threadId;

    try {
        const auto& thread = masstransit_.threads[threadId];
        if (!common::isIn(thread.ftType, ftTypes())) {
            return;
        }

        switch (thread.type) {
            case ThreadType::Linear:
                adjustLinearThread(masstransit_, threadGeom);
                break;
            case ThreadType::Circular:
                adjustCircularThread(masstransit_, threadGeom);
                break;
        }
    } catch (std::exception& e) {
        DATA_ERROR() << "Thread id=" << threadId << " geometry error: " << e.what();
        throw;
    }
}

MtrThreadGeometryHandler::MtrThreadGeometryHandler(Masstransit& masstransit)
    : MtrObjectHandler(masstransit, masstransit.threadGeometries)
{ }

void
MtrThreadGeometryHandler::writeObject(StreamMap& ostreams, const Object& object)
{
    const auto& threadGeom = dynamic_cast<const ThreadGeometry&>(object);
    auto mtrThreadId = idMap().getMtrId(threadGeom.threadId);

    auto& ostream = ostreams[fileNames().front()];
    for (size_t seqId = 0; seqId != threadGeom.polyline.pointsNumber(); ++seqId) {
        const auto point = threadGeom.polyline.pointAt(seqId);
        ostream
            << mtrThreadId << FIELD_SEPARATOR
            << seqId << FIELD_SEPARATOR
            << point.y() << FIELD_SEPARATOR
            << point.x() << std::endl;
    }
}

} // namespace maps::wiki::masstransit
