#pragma once

#include <yandex/maps/wiki/common/lrucache.h>
#include "common.h"
#include <maps/libs/common/include/exception.h>
#include <maps/libs/xml/include/xml.h>
#include <memory>
#include <functional>
#include <mutex>
#include <vector>

namespace maps {
namespace wiki {
namespace renderer {

template <typename Key, typename Value>
class CachedLoader
{
public:
    typedef std::function<Value(Key)> Loader;

    CachedLoader(size_t maxSize, size_t cacheTime, size_t mutexesSize)
        : lruCache_(maxSize, std::chrono::seconds(cacheTime))
        , mutexes_(mutexesSize)
    {
        REQUIRE(mutexesSize, "Invalid size of mutexes");
    }

    Value load(const Key& key, const Loader& loader)
    {
        auto cachedValue = lruCache_.get(key);
        if (cachedValue) {
            return *cachedValue;
        }

        auto index = std::hash<Key>()(key) % mutexes_.size();
        std::lock_guard<std::mutex> lock(mutexes_.at(index));

        cachedValue = lruCache_.get(key);
        if (cachedValue) {
            return *cachedValue;
        }

        auto value = loader(key);
        lruCache_.put(key, value);
        return value;
    }

private:
    common::LRUCacheThreadSafe<Key, Value> lruCache_;
    std::vector<std::mutex> mutexes_;
};


template <typename Key, typename Value>
class CachedLoaderHolder
{
public:
    typedef typename CachedLoader<Key, Value>::Loader Loader;

    explicit CachedLoaderHolder(const maps::xml3::Node& node)
    {
        auto cacheSize = node.node(CACHE_SIZE, true).value<size_t>(CACHE_SIZE_DEFAULT);
        auto cacheTime = node.node(CACHE_TIME, true).value<size_t>(CACHE_TIME_DEFAULT);

        if (cacheSize && cacheTime) {
            auto mutexesSize =
                node.node(MUTEXES_SIZE, true).value<size_t>(MUTEXES_SIZE_DEFAULT);
            cachedLoader_.reset(
                new CachedLoader<Key, Value>(cacheSize, cacheTime, mutexesSize));
        }
    }

    Value load(const Key& key, const Loader& loader)
    {
        return cachedLoader_ ? cachedLoader_->load(key, loader) : loader(key);
    }

private:
    std::unique_ptr<CachedLoader<Key, Value>> cachedLoader_;
};

}}} //namespaces
