#include "relation_infos.h"

#include "relations_manager.h"
#include "objects/object.h"
#include "objects/relation_object.h"
#include "configs/categories.h"
#include "configs/config.h"

#include <yandex/maps/wiki/configs/editor/categories.h>

namespace maps {
namespace wiki {

RelationInfo::RelationInfo(
        RelationType relationType,
        GeoObject* relative,
        RelationObject* relation)
    : type_(relationType)
    , relative_(relative)
    , relation_(relation)
{
    REQUIRE(relative_ && relation_,
        "Invalid link: relative and/or relation not set");
}

TOid
RelationInfo::id() const
{
    return relative_->id();
}

const std::string&
RelationInfo::roleId() const
{
    return relation_->role();
}

TOid
RelationInfo::relationObjectId() const
{
    return relation_->id();
}

TRevisionId
RelationInfo::revisionId() const
{
    return relative_->revision();
}

const std::string&
RelationInfo::categoryId() const
{
    return relative_->categoryId();
}

std::string
RelationInfo::screenLabel() const
{
    return relative_->screenLabel();
}

size_t
RelationInfo::seqNum() const
{
    return relation_->seqNum();
}

const Geom&
RelationInfo::geom() const
{
    return relative_->geom();
}

void
RelationInfo::setSeqNum(size_t seqNum)
{
    relation_->setSeqNum(seqNum);
}

RelationInfo::operator const std::string& () const
{
    return relation_->role();
}

RelationInfo::operator TOid() const
{
    return relative_->id();
}

/// RelationInfoKey

RelationInfoKey::RelationInfoKey(const RelationInfo& link)
{
    id_ = link.relative()->id();
    roleId_ = link.relation()->role();
    relativeCategoryId_ = link.relative()->categoryId();
}

/// Iterator

RelationInfosIterator
RelationInfosIterator::begin(const RelationByRolesRefContainer& links)
{
    return links.empty()
        ? RelationInfosIterator(links)
        : RelationInfosIterator(links, links.begin(), links.begin()->links->relations.begin());
}

RelationInfosIterator
RelationInfosIterator::end(const RelationByRolesRefContainer& links)
{
    return links.empty()
        ? RelationInfosIterator(links)
        : RelationInfosIterator(links, links.end(), (--links.end())->links->relations.end());
}

RelationInfosIterator::RelationInfosIterator(
        const RelationByRolesRefContainer& links)
    : links_(links)
    , outerIt_(links_.end())
{
    ASSERT(links_.empty());
}

RelationInfosIterator::RelationInfosIterator(
        const RelationByRolesRefContainer& links,
        OuterIterator outerIt,
        InternalIterator internalIt)
    : links_(links)
    , outerIt_(outerIt)
    , internalIt_(internalIt)
{
    ASSERT(!links_.empty());
    for (const auto& roleContainer : links_) {
        ASSERT(!roleContainer.links->relations.empty());
    }
}

void
RelationInfosIterator::increment()
{
    REQUIRE(!links_.empty() && internalIt_, "Incrementing iterator of empty container");
    REQUIRE(outerIt_ != links_.end() && *internalIt_ != outerIt_->links->relations.end(), "Already end");
    if (++(*internalIt_) == outerIt_->links->relations.end()) {
        if (++outerIt_ != links_.end()) {
            *internalIt_ = outerIt_->links->relations.begin();
        }
    }
}

void
RelationInfosIterator::decrement()
{
    REQUIRE(!links_.empty() && internalIt_, "Decrementing iterator of empty container");
    REQUIRE(outerIt_ != links_.begin() || *internalIt_ != outerIt_->links->relations.begin(), "Already begin");
    if (*internalIt_ == outerIt_->links->relations.begin()) {
        *internalIt_ = (--outerIt_)->links->relations.end();
    }
    --(*internalIt_);
}

bool
RelationInfosIterator::equal(const RelationInfosIterator& other) const
{
    return outerIt_ == other.outerIt_ &&
        ((!internalIt_ && !other.internalIt_) ||
        (internalIt_ && other.internalIt_ && *internalIt_ == *other.internalIt_));
}

RelationInfosIterator::reference
RelationInfosIterator::dereference() const
{
    REQUIRE(internalIt_, "Dereferencing iterator to empty range");
    return *(*internalIt_);
}

/// Range

void
RelationInfosRange::removeEmptyRoleContainers()
{
    RelationByRolesRefContainer newContainer;
    for (const auto& roleContainer : links_) {
        if (!roleContainer.links->relations.empty()) {
            newContainer.push_back(roleContainer);
        }
    }
    links_.swap(newContainer);
}

MixedRolesInfosContainer rangeToInfos(const RelationInfosRange& range)
{
    MixedRolesInfosContainer links;
    for (const auto& link : range) {
        links.push_back(link);
    }
    return links;
}

/// RelationInfos

RelationInfos::RelationInfos(RelationInfosHolder& holder)
    : holder_(holder)
{}

bool
RelationInfos::areAllRolesLoaded() const
{
    return holder_.areAllRolesLoaded();
}

bool
RelationInfos::isRoleLoaded(const std::string& roleId) const
{
    return holder_.isRoleLoaded(roleId);
}

bool
RelationInfos::areRolesLoaded(const StringSet& roleIds) const
{
    return holder_.areRolesLoaded(roleIds);
}

namespace {

boost::optional<RelationInfos::Range>
collectRangeWithLimit(RelationByRolesRefContainer&& relations, RelativesLimit limit)
{
    if (limit.type == RelativesLimit::Type::All) {
        size_t totalCount = 0;
        for (const auto& roleContainer : relations) {
            if (!roleContainer.links->loaded) {
                return boost::none;
            }
            totalCount += roleContainer.links->relations.size();
            if (totalCount > limit.value) {
                return boost::none;
            }
        }
        return RelationInfos::Range(relations, limit, {});
    }
    // limit.type == PerRole
    RelationByRolesRefContainer nonEmptyRelations;
    StringSet rolesWithExceededLimit;
    for (auto& roleRefContainer : relations) {
        if (!roleRefContainer.links->loaded ||
            roleRefContainer.links->relations.size() > limit.value)
        {
            rolesWithExceededLimit.insert(roleRefContainer.links->roleId);
        } else if (!roleRefContainer.links->relations.empty()) {
            nonEmptyRelations.push_back(roleRefContainer);
        }
    }
    return RelationInfos::Range(nonEmptyRelations, limit, rolesWithExceededLimit);
}

RelationInfos::Range
collectRange(RelationByRolesRefContainer&& relations)
{
    RelationByRolesRefContainer res;
    for (const auto& roleRefContainer : relations) {
        if (!roleRefContainer.links->relations.empty()) {
            res.push_back(roleRefContainer);
        }
    }
    return RelationInfos::Range(res);
}

} // namespace

RelationInfos::Range
RelationInfos::range() const
{
    holder_.loadRelations();
    return collectRange(holder_.relations());
}

boost::optional<RelationInfos::Range>
RelationInfos::range(RelativesLimit limit) const
{
    REQUIRE(limit.type == RelativesLimit::PerRole,
        "Cumulative limit for all slaves range is not supported");
    holder_.loadRelations(limit);
    return collectRangeWithLimit(holder_.relations(), limit);
}

RelationInfos::Range
RelationInfos::range(const std::string& roleId) const
{
    holder_.loadRelations(roleId);
    return collectRange(holder_.relations(StringSet{roleId}));
}

boost::optional<RelationInfos::Range>
RelationInfos::range(const std::string& roleId, RelativesLimit limit) const
{
    holder_.loadRelations(roleId, limit);
    return collectRangeWithLimit(holder_.relations(StringSet{roleId}), limit);
}

RelationInfos::Range
RelationInfos::range(const StringSet& roleIds) const
{
    holder_.loadRelations(roleIds);
    return collectRange(holder_.relations(roleIds));
}

boost::optional<RelationInfos::Range>
RelationInfos::range(const StringSet& roleIds, RelativesLimit limit) const
{
    holder_.loadRelations(roleIds, limit);
    return collectRangeWithLimit(holder_.relations(roleIds), limit);
}

RelationInfos::Diff
RelationInfos::diff() const
{
    return Diff(holder_.added(), holder_.deleted());
}

RelationInfos::Diff
RelationInfos::diff(const std::string& roleId, const std::string& relativeCategoryId) const
{
    Diff res;
    for (const auto& added : holder_.added()) {
        if (added.roleId() == roleId &&
            (relativeCategoryId.empty() ||
                relativeCategoryId == added.categoryId()))
        {
            res.added.push_back(added);
        }
    }
    for (const auto& deleted : holder_.deleted()) {
        if (deleted.roleId() == roleId &&
            (relativeCategoryId.empty() ||
                relativeCategoryId == deleted.categoryId()))
        {
            res.deleted.push_back(deleted);
        }
    }
    return res;
}

RelationInfos::Diff
RelationInfos::diff(const StringSet& roleIds) const
{
    Diff res;
    for (const auto& added : holder_.added()) {
        if (roleIds.find(added.roleId()) != roleIds.end()) {
            res.added.push_back(added);
        }
    }
    for (const auto& deleted : holder_.deleted()) {
        if (roleIds.find(deleted.roleId()) != roleIds.end()) {
            res.deleted.push_back(deleted);
        }
    }
    return res;
}

/// RelationInfosHolder

RelationInfosHolder::RelationInfosHolder(
        RelationType type,
        TOid id,
        RelationsManager& relationsManager)
    : id_(id)
    , type_(type)
    , relationsManager_(relationsManager)
{}

RelationInfosHolder::RelationInfosHolder(const RelationInfosHolder& oth)
    : id_(oth.id_)
    , type_(oth.type_)
    , added_(oth.added_)
    , deleted_(oth.deleted_)
    , relationsManager_(oth.relationsManager_)
{
    for (const auto& roleRelationPair : oth.relatives_) {
        relatives_.insert(std::make_pair(
            roleRelationPair.first,
            std::unique_ptr<RoleInfosContainer>(
                new RoleInfosContainer(*roleRelationPair.second))));
    }
}

void
RelationInfosHolder::setCategory(const std::string& categoryId)
{
    categoryId_ = categoryId;
    cfg()->editor()->categories()[categoryId_];
}

bool
RelationInfosHolder::areAllRolesLoaded() const
{
    auto relativesRoles = cfg()->editor()->categories()[categoryId_].roleIds(
        type_, roles::filters::All);
    auto loadedRoles = this->loadedRoles();
    return loadedRoles == relativesRoles;
}

bool
RelationInfosHolder::isRoleLoaded(const std::string& roleId) const
{
    auto loadedRoles = this->loadedRoles();
    return loadedRoles.find(roleId) != loadedRoles.end();
}

bool
RelationInfosHolder::areRolesLoaded(const StringSet& roleIds) const
{
    auto loadedRoles = this->loadedRoles();
    return std::includes(
        loadedRoles.begin(), loadedRoles.end(),
        roleIds.begin(), roleIds.end());
}

StringSet
RelationInfosHolder::loadedRoles() const
{
    StringSet roleIds;
    for (const auto& roleRelatives : relatives_) {
        if (roleRelatives.second->loaded) {
            roleIds.insert(roleRelatives.first);
        }
    }
    return roleIds;
}

StringSet
RelationInfosHolder::rolesToLoad(const StringSet& requestedRoleIds) const
{
    StringSet roleIds;
    for (const auto& roleId : requestedRoleIds) {
        auto it = relatives_.find(roleId);
        if (it == relatives_.end() || !it->second->loaded) {
            roleIds.insert(roleId);
        }
    }
    return roleIds;
}

void
RelationInfosHolder::add(TOid relativeId, const std::string& roleId)
{
    TOid masterId = type_ == RelationType::Master ? relativeId : id_;
    TOid slaveId = type_ == RelationType::Master ? id_ : relativeId;
    relationsManager_.createRelation(masterId, slaveId, roleId);
}

void
RelationInfosHolder::remove(TOid relativeId, const std::string& roleId)
{
    TOid masterId = type_ == RelationType::Master ? relativeId : id_;
    TOid slaveId = type_ == RelationType::Master ? id_ : relativeId;
    relationsManager_.deleteRelation(masterId, slaveId, roleId);
}

RoleInfosContainer*
RelationInfosHolder::roleRelations(const std::string& roleId)
{
    auto it = relatives_.find(roleId);
    if (it == relatives_.end()) {
        DEBUG() << BOOST_CURRENT_FUNCTION <<
            " Object id: " << id_ << " , creating relatives container for role: " << roleId;
        it = relatives_.insert(std::make_pair(
            roleId,
            std::unique_ptr<RoleInfosContainer>(new RoleInfosContainer(roleId)))).first;
    }
    return it->second.get();
}

const std::vector<const RelationObject*>&
RelationInfosHolder::cachedRelations() const
{
    return relationsManager_.cachedRelations(type_, id_);
}

RelationByRolesRefContainer
RelationInfosHolder::relations()
{
    RelationByRolesRefContainer res;
    auto relativesRoles = cfg()->editor()->categories()[categoryId_].roleIds(
        type_, roles::filters::All);
    for (const auto& roleId : relativesRoles) {
        res.push_back(RoleInfosRefContainer(roleRelations(roleId)));
    }
    return res;
}

RelationByRolesRefContainer
RelationInfosHolder::relations(const StringSet& roleIds)
{
    RelationByRolesRefContainer res;
    for (const auto& roleId : roleIds) {
        res.push_back(RoleInfosRefContainer(roleRelations(roleId)));
    }
    return res;
}

void
RelationInfosHolder::loadRelations()
{
    if (!areAllRolesLoaded()) {
        DEBUG() << BOOST_CURRENT_FUNCTION << " Object " << id_ << ", not all roles loaded";
        relationsManager_.loadRelations(type_, TOIds{id_});
    }
}

void
RelationInfosHolder::loadRelations(RelativesLimit limit)
{
    if (!areAllRolesLoaded()) {
        DEBUG() << BOOST_CURRENT_FUNCTION << " Object " << id_ << ", not all roles loaded";
        relationsManager_.loadRelations(type_, id_, limit);
    }
}

void
RelationInfosHolder::loadRelations(const std::string& roleId)
{
    if (!isRoleLoaded(roleId)) {
        DEBUG() << BOOST_CURRENT_FUNCTION <<
            " Object " << id_ << ", role " << roleId << " not loaded";
        relationsManager_.loadRelations(type_, TOIds{id_}, StringSet{roleId});
    }
}

void
RelationInfosHolder::loadRelations(const std::string& roleId, RelativesLimit limit)
{
    if (!isRoleLoaded(roleId)) {
        DEBUG() << BOOST_CURRENT_FUNCTION <<
            " Object " << id_ << ", role " << roleId << " not loaded";
        relationsManager_.loadRelations(type_, id_, StringSet{roleId}, limit);
    }
}

void
RelationInfosHolder::loadRelations(const StringSet& roleIds)
{
    if (areAllRolesLoaded()) {
        return;
    }
    StringSet rolesToLoad = this->rolesToLoad(roleIds);
    if (!rolesToLoad.empty()) {
        relationsManager_.loadRelations(type_, TOIds{id_}, rolesToLoad);
    }
}

void
RelationInfosHolder::loadRelations(const StringSet& roleIds, RelativesLimit limit)
{
    if (areAllRolesLoaded()) {
        return;
    }
    StringSet rolesToLoad = this->rolesToLoad(roleIds);
    if (!rolesToLoad.empty()) {
        relationsManager_.loadRelations(type_, id_, rolesToLoad, limit);
    }
}

} // namespace wiki
} // namespace maps
