#pragma once

#include "../common.h"
#include "../topology_data.h"

#include <yandex/maps/wiki/common/batch.h>
#include <yandex/maps/wiki/threadutils/threadpool.h>
#include <yandex/maps/wiki/threadutils/executor.h>

#include <boost/thread/shared_mutex.hpp>

#include <mutex>
#include <unordered_map>
#include <vector>

namespace maps {
namespace wiki {
namespace topology_fixer {

class FaceLocker
{
public:
    using Locks = std::vector<std::unique_lock<std::mutex>>;

    explicit FaceLocker(const TopologyData& data)
    {
        for (auto faceId : data.faceIds()) {
            faceMutexes_.emplace(faceId, std::make_unique<std::mutex>());
        }
    }

    template<typename Container>
    Locks lockFaces(const Container& faceIds)
    {
        std::vector<FaceId> sortedFaceIds;
        sortedFaceIds.reserve(faceIds.size());
        sortedFaceIds.insert(sortedFaceIds.end(), faceIds.begin(), faceIds.end());
        std::sort(sortedFaceIds.begin(), sortedFaceIds.end());

        Locks locks;
        for (auto faceId : sortedFaceIds)
        {
            auto it = faceMutexes_.find(faceId);
            ASSERT(it != faceMutexes_.end());
            locks.emplace_back(*(it->second));
        }
        return locks;
    }

private:
    FaceLocker(const FaceLocker&) = delete;
    FaceLocker& operator= (const FaceLocker&) = delete;

    std::unordered_map<FaceId, std::unique_ptr<std::mutex>> faceMutexes_;
};


class SharedProcessedIdsCache
{
public:
    bool has(DBIdType id)
    {
        boost::shared_lock<boost::shared_mutex> readLock(mutex_);
        return processedIds_.count(id) > 0;
    }

    void insert(DBIdType id)
    {
        boost::unique_lock<boost::shared_mutex> writeLock(mutex_);
        processedIds_.insert(id);
    }

private:
    boost::shared_mutex mutex_;
    IdSet processedIds_;
};


template<typename Container, typename Func>
void executeInThreads(
    ThreadPool& pool,
    const Container& data,
    size_t bulkCount,
    Func func)
{
    Executor executor;

    common::applyBatchOp<Container>(
        data,
        bulkCount,
        [&](const Container& batch) {
            executor.addTask([&, batch](){
                func(batch);
            });
        });

    executor.executeAllInThreads(pool);
}

} // namespace topology_fixer
} // namespace wiki
} // namespace maps
