#pragma once

#include "inmemory_objects_collection.h"
#include <maps/wikimap/mapspro/libs/validator/common/exception.h>
#include <maps/wikimap/mapspro/libs/validator/common/magic_strings.h>
#include <maps/wikimap/mapspro/libs/validator/common/utils.h>
#include <maps/wikimap/mapspro/libs/validator/loader/object_constructors-inl.h>

namespace maps {
namespace wiki {
namespace validator {

template<class Category>
InMemoryObjectsCollection<Category>::InMemoryObjectsCollection()
    : ObjectsCollectionBase(Category::id())
    , constructorFailsCount_(0)
{ }

template<class Category>
std::vector<TId> InMemoryObjectsCollection<Category>::objectIds() const
{
    std::vector<TId> ids;
    ids.reserve(objects_.size());
    for (const auto& object : objects_) {
        ids.push_back(object.id());
    }
    return ids;
}

template<class Category>
boost::optional<RevisionID>
InMemoryObjectsCollection<Category>::revisionId(TId oid) const
{
    auto oidCidPair = objectIdToCommitId_.find(oid);
    if (oidCidPair != objectIdToCommitId_.end()) {
        return RevisionID(oidCidPair->first, oidCidPair->second);
    } else {
        return boost::none;
    }
}

template<class Category>
void InMemoryObjectsCollection<Category>::addRevisions(
        std::vector<ObjectDatum> objectData,
        ObjectIdToCommitId objectCommitIds,
        ObjectIdSet deletedRelatedObjectIds,
        const AreaOfInterest& aoi,
        const configs::editor::Category* configCategory,
        const ImportantRegions& importantRegions,
        std::list<Message>* baseCheckMessages)
{
    for (const auto& pair: objectCommitIds) {
        if (!deletedRelatedObjectIds.count(pair.first)) {
            objectIdToCommitId_.insert(pair);
        }
    }

    TObjectConstructor constructor {
        baseCheckMessages,
        &objects_,
        std::move(objectCommitIds),
        std::move(deletedRelatedObjectIds),
        aoi,
        configCategory
    };

    for (auto& datum : objectData) {
        const RevisionID revisionId = datum.revision.id();

        try {
            constructor.construct(std::move(datum));
        } catch (InvalidRelationsException& ex) {
            RevisionIds revisionIds {revisionId};
            for (TId otherOid : ex.otherOids()) {
                revisionIds.emplace_back(
                    otherOid, objectIdToCommitId_.at(otherOid)
                );
            }

            baseCheckMessages->emplace_back(
                    ex.severity(),
                    BASE_CHECK_ID,
                    ex.description(),
                    importantRegions.determineRegionType(ex.geom()),
                    geomWkb(ex.geom()),
                    std::move(revisionIds)
            );
        } catch (ObjectLoadingException& ex) {
            const RevisionID errorRevisionId {
                ex.oid(), objectIdToCommitId_.at(ex.oid())
            };
            baseCheckMessages->emplace_back(
                    Severity::Fatal,
                    BASE_CHECK_ID,
                    ex.description(),
                    importantRegions.determineRegionType(ex.geom()),
                    geomWkb(ex.geom()),
                    RevisionIds{errorRevisionId}
            );
        } catch (...) {
            ++constructorFailsCount_;
            logException(
                log8::Level::WARNING,
                "while loading object " + std::to_string(revisionId.objectId())
            );
        }
    }
}

template<class Category>
size_t InMemoryObjectsCollection<Category>::finalize()
{
    REQUIRE(
            constructorFailsCount_ == 0,
            "Loading of " << constructorFailsCount_ << " objects of category "
            << Category::id() << " failed");

    objects_.shrink_to_fit();

    indexSuite_.reset(new TIndexSuite(objects_));
    loaded_.store(true);
    return objects_.size();
}

template<class Category>
void InMemoryObjectsCollection<Category>::unload()
{
    loaded_.store(false);
    indexSuite_.reset();

    // clear() does not deallocate memory
    ObjectIdToCommitId().swap(objectIdToCommitId_);
    TObjects().swap(objects_);
}

} // namespace validator
} // namespace wiki
} // namespace maps
