////////////////////////////////////////////////////////////////////////////////
// 
// Cache.hh 
//
//    produced: 24/10/25 jr
//
////////////////////////////////////////////////////////////////////////////////
#ifndef CACHE_HH
#define CACHE_HH

#include <unordered_set>
#include <shared_mutex>

#include "CommandlineOptions.hh"

namespace topcom {

  // the following template acts as a cache for objects of type T
  // where the commandline options are used to determine size;
  // if --memopt is given, then --localcache [k] determines
  // that the cache size is k;
  // if --memopt is not given, then the cache grows essentially without bound:

  template <class T>
  class Cache {
    typedef std::unordered_set<T, Hash<T> > cache_container_type;
    typedef typename cache_container_type::iterator cache_container_iterator_type;
    typedef typename cache_container_type::const_iterator cache_container_const_iterator_type;
  private:
    constexpr static size_type _MAX_NO_OF_OBJECTS = 100000UL;
  private:
    size_type            _maxsize;
    cache_container_type _data;

    // lock when cache is being changed:
    mutable std::shared_mutex _cache_mutex;
  public:
    
    // constructors:
    inline Cache() :
      _maxsize(_MAX_NO_OF_OBJECTS),
      _data() {}
    inline Cache(const Cache& cache) :
      _maxsize(cache._maxsize),
      _data(cache._data) {}
    inline Cache(Cache&& cache) :
      _maxsize(cache._maxsize),
      _data(std::move(cache._data)) {}

    // assignment:
    inline const Cache& operator=(const Cache& cache) {
      if (this == &cache) {
	return *this;
      }
      _maxsize = cache._maxsize;
      _data = cache._data;
      return *this;
    }
    inline const Cache& operator=(Cache&& cache) {
      if (this == &cache) {
	return *this;
      }
      _maxsize = cache._maxsize;
      _data = std::move(cache._data);
      return *this;
    }

    // destructor:
    inline ~Cache() {}

    // accessors:
    inline bool is_cached(const T& obj) const {
      std::shared_lock<std::shared_mutex> cache_read_guard(_cache_mutex);
      return (_data.find(obj) != _data.end());
    }
    inline size_type maxsize() const {
      return _maxsize;
    }    
    inline size_type size() const {
      return _data.size();
    }

    // modifiers:
    inline void clear() {
      std::unique_lock<std::shared_mutex> cache_write_guard(_cache_mutex);
      _data.clear();
    }
    inline void evict() {
      if (_data.empty()) {
	return;
      }
      else {

	// FIFO:
	std::unique_lock<std::shared_mutex> cache_write_guard(_cache_mutex);
	_data.erase(_data.begin());
      }
    }
    inline void add(const T& obj) {
      if (_data.size() >= _maxsize) {
	evict();
      }
      std::unique_lock<std::shared_mutex> cache_write_guard(_cache_mutex);
      _data.insert(obj);
    }
    inline void add(T&& obj) {
      if (_data.size() >= _maxsize) {
	evict();
      }
      std::unique_lock<std::shared_mutex> cache_write_guard(_cache_mutex);
      _data.insert(std::move(obj));
    }
    inline void add(const Cache& cache) {
      if (this == &cache) {
	return;
      }
      if (_data.size() + cache._data.size() <= _maxsize) {

	// add all in one go:
	std::unique_lock<std::shared_mutex> cache_write_guard(_cache_mutex);
	_data.insert(cache._data.begin(), cache._data.end());
      }
      else {

	// add one at a time:
	for (cache_container_const_iterator_type iter = cache._data.begin();
	     iter != cache._data.end();
	     ++iter) {
	  add(*iter);
	}
      }
    }
    inline void add(Cache&& cache) {
      if (this == &cache) {
	return;
      }
      if (_data.size() + cache._data.size() <= _maxsize) {

	// add all in one go:
	std::unique_lock<std::shared_mutex> cache_write_guard(_cache_mutex);
	std::move(cache._data.begin(), cache._data.end(), std::inserter(_data));
      }
      else {

	// add one at a time:
	for (cache_container_iterator_type iter = cache._data.begin();
	     iter != cache._data.end();
	     ++iter) {
	  add(std::move(*iter));
	}
      }
    }
      
  };
     
}; // namespace topcom

#endif

// eof Cache.hh
