#include <maps/wikimap/mapspro/services/mrc/libs/yt/include/serialization.h>

#include <maps/libs/introspection/include/introspect.h>
#include <maps/libs/introspection/include/tuple_for_each.h>

namespace maps::mrc::yt {

/* Complex types */

NYT::TNode serialize(const geolib3::Point2& point)
{
    return NYT::TNode::CreateMap()
        ("x", point.x())
        ("y", point.y());
}

NYT::TNode serialize(const geolib3::Polyline2& polyline)
{
    return serialize(polyline.points());
}

NYT::TNode serialize(const geolib3::LinearRing2& lineRing)
{
    auto result = NYT::TNode::CreateList();

    for (size_t i = 0; i < lineRing.pointsNumber(); ++i) {
        result.Add(serialize(lineRing.pointAt(i)));
    }

    return result;
}

NYT::TNode serialize(const geolib3::Polygon2& polygon)
{
    auto interiorRings = NYT::TNode::CreateList();

    for (size_t i = 0; i < polygon.interiorRingsNumber(); ++i) {
        interiorRings.Add(serialize(polygon.interiorRingAt(i)));
    }

    return NYT::TNode::CreateMap()
            ("exteriorRing", serialize(polygon.exteriorRing()))
            ("interiorRings", interiorRings);
}

NYT::TNode serialize(const json::Value& value)
{
    std::stringstream out;
    out << value;
    return serialize(out.str());
}

NYT::TNode serialize(const common::ImageBox& box)
{
    return NYT::TNode::CreateList()
        .Add(box.minX())
        .Add(box.minY())
        .Add(box.maxX())
        .Add(box.maxY());
}

NYT::TNode serialize(const cv::Point2f& point)
{
    NYT::TNode node = NYT::TNode::CreateList();
        node.Add(point.x);
        node.Add(point.y);
    return node;
}

namespace impl {

traffic_signs::TrafficSign deserialize(const NYT::TNode& node, const traffic_signs::TrafficSign*)
{
    return traffic_signs::stringToTrafficSign(yt::deserialize<std::string>(node));
}

geolib3::Heading deserialize(const NYT::TNode& node, const geolib3::Heading*)
{
    return geolib3::Heading(node.AsDouble());
}

geolib3::Point2 deserialize(const NYT::TNode& node, const geolib3::Point2*)
{
    return geolib3::Point2(
        node["x"].As<double>(),
        node["y"].As<double>()
    );
}

geolib3::Polyline2 deserialize(const NYT::TNode& node, const geolib3::Polyline2*)
{
    return geolib3::Polyline2(yt::deserialize<geolib3::PointsVector>(node));
}

geolib3::LinearRing2 deserialize(const NYT::TNode& node, const geolib3::LinearRing2*)
{
    return geolib3::LinearRing2(yt::deserialize<geolib3::PointsVector>(node));
}

geolib3::Polygon2 deserialize(const NYT::TNode& node, const geolib3::Polygon2*)
{
    return geolib3::Polygon2(
        yt::deserialize<geolib3::LinearRing2>(node["exteriorRing"]),
        yt::deserialize<std::vector<geolib3::LinearRing2>>(node["interiorRings"])
    );
}

Bytes deserialize(const NYT::TNode& node, const Bytes*)
{
    const auto bytes = node.AsString();
    const auto begin = reinterpret_cast<const uint8_t*>(bytes.data());

    return Bytes(begin, begin + bytes.size());
}

json::Value deserialize(const NYT::TNode& node, const json::Value*)
{
    return json::Value::fromString(yt::deserialize<std::string>(node));
}

common::ImageOrientation deserialize(const NYT::TNode& node, const common::ImageOrientation*)
{
    return common::ImageOrientation::fromExif(node.AsInt64());
}

common::ImageBox deserialize(const NYT::TNode& node, const common::ImageBox*)
{
    return {
        node[0].As<size_t>(),
        node[1].As<size_t>(),
        node[2].As<size_t>(),
        node[3].As<size_t>()
    };
}

cv::Point2f deserialize(const NYT::TNode& node, const cv::Point2f*)
{
    return cv::Point2f{
        static_cast<float>(node[0].AsDouble()),
        static_cast<float>(node[1].AsDouble())};
}

} // namespace impl

} // namespace maps::mrc::yt
