#include "object_impl.h"
#include "extractors.h"
#include "helpers.h"

#include <yandex/maps/wiki/diffalert/revision/editor_config.h>

#include <maps/libs/common/include/exception.h>
#include <yandex/maps/wiki/common/retry_duration.h>
#include <yandex/maps/wiki/revision/objectrevision.h>

namespace mwr = maps::wiki::revision;

namespace maps {
namespace wiki {
namespace diffalert {
namespace {

Relations extractRelations(const mwr::Revisions& relRevs)
{
    Relations rels;
    for (const auto& relRev : relRevs) {
        rels.insert(extractRelation(relRev));
    }
    return rels;
}

LongtaskObject::Impl::TDSType
categoryTdsType(const EditorConfig& config, const std::string& categoryId)
{
    if (config.isFaceElementCategory(categoryId)) {
        return LongtaskObject::Impl::TDSType::FaceElement;
    }
    if (config.isFaceJunctionCategory(categoryId)) {
        return LongtaskObject::Impl::TDSType::FaceJunction;
    }
    return LongtaskObject::Impl::TDSType::Other;
}

} // namespace

LongtaskObject::Impl::Impl(
        const mwr::ObjectRevision& objRev,
        RevSnapshotFactory revSnapshotFct_,
        const EditorConfig& config)
    : id(objRev.id().objectId())
    , revSnapshotFct(std::move(revSnapshotFct_))
{
    REQUIRE(objRev.data().attributes,
        "Object " << objRev.id() << " has no attributes");
    categoryId = extractCategoryId(*objRev.data().attributes);
    REQUIRE(!categoryId.empty(),
        "Object " << objRev.id() << " has no category attribute");
    attributes = *objRev.data().attributes;

    if (objRev.data().geometry) {
        geom = Geom(*objRev.data().geometry);
        const auto* envelope = geom->getEnvelopeInternal(); // compute envelope for thread safety
        REQUIRE(std::isfinite(envelope->getWidth()) && std::isfinite(envelope->getHeight()),
            "Object " << objRev.id() << " has infinite envelope");
    }
    tdsType = categoryTdsType(config, categoryId);
}

LongtaskObject::Impl::Impl(
        TId id_,
        revision::Attributes attributes_,
        Geom geom_,
        RevSnapshotFactory revSnapshotFct_,
        const EditorConfig& config)
    : id(id_)
    , categoryId(extractCategoryId(attributes_))
    , attributes(std::move(attributes_))
    , geom(std::move(geom_))
    , tdsType(categoryTdsType(config, categoryId))
    , revSnapshotFct(std::move(revSnapshotFct_))
{
    REQUIRE(!categoryId.empty(),
        "Object " << id << " has no category attribute");
    if (!geom.isNull()) {
        const auto* envelope = geom->getEnvelopeInternal(); // compute envelope for thread safety
        REQUIRE(std::isfinite(envelope->getWidth()) && std::isfinite(envelope->getHeight()),
            "Object " << id << " has infinite envelope");
    }
}

LongtaskObject::LongtaskObject(Impl&& impl)
    : impl_(new Impl(std::move(impl)))
{ }

LongtaskObject::LongtaskObject(LongtaskObject&&) noexcept = default;
LongtaskObject& LongtaskObject::operator=(LongtaskObject&&) noexcept = default;
LongtaskObject::~LongtaskObject() = default;

TId LongtaskObject::id() const
{ return impl_->id; }

const std::string& LongtaskObject::categoryId() const
{ return impl_->categoryId; }

AttrValue LongtaskObject::attr(const std::string& name) const
{
    return getAttrValue(impl_->attributes, name, impl_->categoryId);
}

const Geom& LongtaskObject::geom() const
{
    return impl_->geom;
}

Relations LongtaskObject::loadMasterRelations()
{
    auto relations = common::retryDuration([&] {
        auto snapshot = impl_->revSnapshotFct.get();
        return snapshot->loadMasterRelations(id());
    });
    return extractRelations(relations);
}

Relations LongtaskObject::loadSlaveRelations()
{
    auto relations = common::retryDuration([&] {
        auto snapshot = impl_->revSnapshotFct.get();
        return snapshot->loadSlaveRelations(id());
    });
    return extractRelations(relations);
}

bool LongtaskObject::isFaceElement() const
{
    return impl_->tdsType == LongtaskObject::Impl::TDSType::FaceElement;
}

bool LongtaskObject::isFaceJunction() const
{
    return impl_->tdsType == LongtaskObject::Impl::TDSType::FaceJunction;
}

} // namespace diffalert
} // namespace wiki
} // namespace maps
