#include "feature_iterator.h"
#include "road_markings.h"

#include <maps/renderer/libs/base/include/geom/wkb_reader.h>
#include <maps/renderer/libs/base/include/string_util.h>
#include <maps/renderer/libs/base/include/json_fwd.h>
#include <maps/renderer/libs/feature/include/yandex/maps/renderer/feature/attributes.h>
#include <maps/renderer/libs/postgres/include/postgres.h>
#include <maps/renderer/rapidjson-helper/include/yandex/maps/rapidjsonhelper/parse.h>

#include <maps/renderer/libs/geos_geometry/include/io.h>

#include <pqxx/binarystring>

using namespace maps::renderer;
using namespace maps::renderer::postgres;

namespace geom = maps::renderer::geos_geometry;

namespace maps::wiki::renderer {

namespace {

void to(const pqxx::field& field, base::Value* var)
{
    if (field.is_null()) {
        var->setNull();
        return;
    }
    switch (field.type()) {
        case OID_INT2:
        case OID_INT4:
            var->set(field.as<int32_t>());
            break;
        case OID_INT8:
            var->set(field.as<int64_t>());
            break;
        case OID_FLOAT4:
        case OID_FLOAT8:
            var->set(field.as<double>());
            break;
        case OID_BOOL:
            var->set(field.as<bool>());
            break;
        case OID_TEXT:
        case OID_CHAR_N:
        case OID_VARCHAR:
        case OID_NUMERIC:
            var->set(base::s2ws(field.as<std::string>()));
            break;
        case OID_BYTEA:
        case OID_UNKNOWN:
        default:
            std::vector<uint8_t> data(
                (uint8_t*)field.c_str(),
                (uint8_t*)field.c_str() + field.size());
            var->set(std::move(data));
            break;
    }
}

void to(const pqxx::field& field, std::vector<std::string>* array) {
    auto parser = field.as_array();
    auto next = parser.get_next();
    while (next.first != pqxx::array_parser::juncture::done) {
        if (next.first == pqxx::array_parser::juncture::string_value)
            array->push_back(next.second);
        next = parser.get_next();
    }
}

void addAttribute(
    const pqxx::field& field,
    rjhelper::ObjectBuilder& out)
{
    if (field.type() == postgres::OID_JSON) {
        out.PutVariant(field.name(), [&](mr::json::MutValueRef ref) {
            if (field.is_null()) {
                // Interpret null value as empty object for
                // backward-compatibility with hstore: MAPSRENDER-2568
                // TODO: remove nulls from data and remove this check
                ref->SetObject();
            } else {
                mr::json::Document doc;
                rjhelper::ParseStringChecked(field.c_str(), &doc);
                ref.Set(doc);
            }
        });
        return;
    }

    if (field.type() == postgres::OID_TEXTARRAY) {
        std::vector<std::string> array;
        to(field, &array);
        out.PutArray(field.name(), [&](rjhelper::ArrayBuilder a) {
            a.PutRange(array.begin(), array.end());
        });
        return;
    }

    base::Value value;
    to(field, &value);

    out.PutVariant(field.name(), [&](mr::json::MutValueRef ref) {
        base::valueToJson(value, ref);
    });
}

} // namespace

class MarkingGeometryIter: public feature::FeatureIter
{
public:
    MarkingGeometryIter(const PostgisFeatureIterable* host, const pqxx::row& tuple)
        : host_{host}
        , feature_{host->ftType_}
    {
        auto it = FindIf(host_->attrColumnIndexes, [](const auto& pair) {
            return pair.first == "yaw"; });
        if (it != host_->attrColumnIndexes.end())
            yawIdx_ = it->second;
        attach(tuple);
    }

    void attach(const pqxx::row& tuple)
    {
        tuple_ = tuple;

        const auto& h = *host_;
        InputMarking input;
        auto geomField = tuple_.at(h.geomIdx);
        input.geom = geos_geometry::readEwkbGeometry(pqxx::binarystring(geomField).str());
        input.ftTypeId = static_cast<ymapsdf::ft::Type>(tuple_.at("ft_type_id").as<int16_t>(
            static_cast<int16_t>(ymapsdf::ft::Type::None)));
        if (yawIdx_)
            input.yaw = tuple_.at("yaw").as<int>(0);

        markings_ = generate(std::move(input), h.zoom_);
        markingIt_ = markings_.begin();
    }

    virtual bool hasNext() const
    {
        return markingIt_ != markings_.end();
    }

    virtual feature::Feature& next()
    {
        ASSERT(hasNext());

        const auto& h = *host_;

        auto fId = tuple_.at(h.idIdx).as<uint64_t>();
        feature_.setId(fId);
        feature_.setSourceId(fId);

        auto detailRange = feature::DetailRange(host_->layerMinZoom_,
                                                base::MAX_ZOOM);
        if (h.minzoomIdx) {
            ASSERT(h.maxzoomIdx);
            const auto zmin = tuple_.at(*h.minzoomIdx).as<uint32_t>();
            const auto zmax = tuple_.at(*h.maxzoomIdx).as<uint32_t>();
            detailRange = base::math::intersection(detailRange,
                                                   feature::DetailRange(zmin, zmax));
        }
        feature_.add(
            feature::CapabilityFeatureTypes::CapabilityFeatureScaling);
        ASSERT(detailRange.isValid());
        feature_.setDetailRange(detailRange);

        if (h.zlevBegIdx) {
            ASSERT(h.zlevEndIdx);
            feature_.zOrder() = {tuple_.at(*h.zlevBegIdx).as<int32_t>(),
                                 tuple_.at(*h.zlevEndIdx).as<int32_t>()};
        }

        auto attr = feature_.attr.init();
        if (!h.attrColumnIndexes.empty()) {
            rjhelper::ObjectBuilder objBuilder(attr.get());
            for (const auto& [name, idx] : host_->attrColumnIndexes) {
                addAttribute(tuple_.at(idx), objBuilder);
            }
        }

        // set geometry
        const OutputMarking& marking = *markingIt_;

        base::Vertices vertices;
        feature_.geom().init();

        auto bstr = geom::writeEwkbGeometry(*marking.geom);
        auto geom = pqxx::binarystring(bstr);
        base::readWkb({geom.get(), geom.size()}, &vertices);
        feature_.geom().shapes().addPath(vertices);

        if (h.transform_)
            feature_.transformVertices(h.transform_);

        // set marking properties
        rjhelper::MutableValueRef jsAttr(attr.get());
        for (const auto& [key, value] : marking.attrs) {
            auto it = jsAttr->FindMember(key.c_str());
            if (it != jsAttr->MemberEnd())
                jsAttr->EraseMember(it);
            jsAttr.AddMember(key, value);
        }

        markingIt_++;
        return feature_;
    }

private:
    const PostgisFeatureIterable* host_;
    feature::Feature feature_;
    pqxx::row tuple_;
    Markings markings_;
    Markings::const_iterator markingIt_;
    std::optional<PostgisFeatureIterable::ColumnIndex> yawIdx_;
};

class MarkingFeatureIter: public feature::FeatureIter
{
public:
    MarkingFeatureIter(const PostgisFeatureIterable* host)
        : host_{host}
        , idx_(0)
    {
        findNext();
    }

    virtual bool hasNext() const
    {
        if (currGenerator_ && currGenerator_->hasNext())
            return true;
        if (nextGenerator_ && nextGenerator_->hasNext())
            return true;
        return false;
    }

    virtual feature::Feature& next()
    {
        ASSERT(hasNext());

        if (currGenerator_ && currGenerator_->hasNext())
            return currGenerator_->next();

        std::swap(currGenerator_, nextGenerator_);
        findNext();
        return currGenerator_->next();
    }

    void findNext()
    {
        const auto& h = *host_;
        for (; idx_ < h.data_->size(); ++idx_) {
            auto tuple = h.data_->at(idx_);

            if (nextGenerator_)
                nextGenerator_->attach(tuple);
            else
                nextGenerator_ = std::make_unique<MarkingGeometryIter>(host_, tuple);

            if (nextGenerator_->hasNext())
                break;
        }
        idx_++;
    }

private:
    const PostgisFeatureIterable* host_;
    size_t idx_;
    std::unique_ptr<MarkingGeometryIter> currGenerator_;
    std::unique_ptr<MarkingGeometryIter> nextGenerator_;
};

PostgisFeatureIter::PostgisFeatureIter(const PostgisFeatureIterable* host)
    : host_{host}
    , idx_{0}
    , feature_{host_->ftType_}
{
}

bool PostgisFeatureIter::hasNext() const
{
    return idx_ < host_->data_->size();
}

feature::Feature& PostgisFeatureIter::next()
{
    ASSERT(hasNext());

    const auto& h = *host_;
    const auto& tuple = h.data_->at(idx_);
    ++idx_;

    auto fId = tuple.at(h.idIdx).as<uint64_t>();
    feature_.setId(fId);
    feature_.setSourceId(fId);

    auto geomField = tuple.at(h.geomIdx);
    auto geom = pqxx::binarystring(geomField);
    feature_.geom().init();
    base::readWkb({geom.get(), geom.size()},
                  &feature_.geom().shapes());

    if (h.transform_)
        feature_.transformVertices(h.transform_);

    auto detailRange = feature::DetailRange(host_->layerMinZoom_,
                                            base::MAX_ZOOM);
    if (h.minzoomIdx) {
        ASSERT(h.maxzoomIdx);
        const auto zmin = tuple.at(*h.minzoomIdx).as<uint32_t>();
        const auto zmax = tuple.at(*h.maxzoomIdx).as<uint32_t>();
        detailRange = base::math::intersection(detailRange,
                                               feature::DetailRange(zmin, zmax));
    }
    feature_.add(
        feature::CapabilityFeatureTypes::CapabilityFeatureScaling);
    ASSERT(detailRange.isValid());
    feature_.setDetailRange(detailRange);

    if (h.zlevBegIdx) {
        ASSERT(h.zlevEndIdx);
        feature_.zOrder() = {tuple.at(*h.zlevBegIdx).as<int32_t>(),
                             tuple.at(*h.zlevEndIdx).as<int32_t>()};
    }

    if (!h.attrColumnIndexes.empty()) {
        setAttributes(tuple);
    }

    return feature_;
}

void PostgisFeatureIter::setAttributes(const pqxx::row& tuple)
{
    auto attr = feature_.attr.init();
    rjhelper::ObjectBuilder objBuilder(attr.get());
    for (const auto& [name, idx] : host_->attrColumnIndexes) {
        addAttribute(tuple.at(idx), objBuilder);
    }
}

std::unique_ptr<feature::FeatureIter> PostgisFeatureIterable::iterator() const
{
    if (featureGenerator_) {
        switch(featureGenerator_.value()) {
            case FeatureGeneratorType::RoadMarkingSymbol:
            case FeatureGeneratorType::RoadMarkingLine:
            case FeatureGeneratorType::RoadMarkingPolygon:
                return std::make_unique<MarkingFeatureIter>(this);
        }
    }
    return std::make_unique<PostgisFeatureIter>(this);
}

PostgisFeatureIterable::PostgisFeatureIterable(
    std::shared_ptr<pqxx::result> data,
    const SourceLayer& srcLayer,
    const Transform& transform,
    base::Zoom zoom)
    : data_{std::move(data)}
    , ftType_(srcLayer.featureType)
    , layerMinZoom_{srcLayer.minzoom}
    , transform_(transform)
    , featureGenerator_(srcLayer.featureGenerator)
    , zoom_(zoom)
{
    ASSERT(!data_->empty());
    idIdx = data_->column_number(srcLayer.featureId);
    geomIdx = data_->column_number(srcLayer.geometryColumn);
    if (srcLayer.zoomRange) {
        minzoomIdx = data_->column_number(srcLayer.zoomRange->first);
        maxzoomIdx = data_->column_number(srcLayer.zoomRange->second);
    }
    if (srcLayer.zlevel) {
        zlevBegIdx = data_->column_number(srcLayer.zlevel->first);
        zlevEndIdx = data_->column_number(srcLayer.zlevel->second);
    }

    for (const auto& [name, _] : srcLayer.propertyExpressions) {
        attrColumnIndexes.emplace_back(name, data_->column_number(name));
    }
}

} // namespace maps::wiki::renderer
