// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// This file contains a template for a Most Recently Used cache that allows
// constant-time access to items using a key, but easy identification of the
// least-recently-used items for removal.  Each key can only be associated with
// one payload item at a time.
//
// The key object will be stored twice, so it should support efficient copying.
//
// NOTE: While all operations are O(1), this code is written for
// legibility rather than optimality. If future profiling identifies this as
// a bottleneck, there is room for smaller values of 1 in the O(1). :]

#ifndef BASE_CONTAINERS_MRU_CACHE_H_
#define BASE_CONTAINERS_MRU_CACHE_H_

#include <list>
#include <map>
#include <utility>

#include "base/basictypes.h"
#include "base/containers/hash_tables.h"
#include "base/logging.h"

namespace base {

// MRUCacheBase ----------------------------------------------------------------

// This template is used to standardize map type containers that can be used
// by MRUCacheBase. This level of indirection is necessary because of the way
// that template template params and default template params interact.
template <class KeyType, class ValueType>
struct MRUCacheStandardMap {
  typedef std::map<KeyType, ValueType> Type;
};

// Base class for the MRU cache specializations defined below.
// The deletor will get called on all payloads that are being removed or
// replaced.
template <class KeyType, class PayloadType, class DeletorType,
          template <typename, typename> class MapType = MRUCacheStandardMap>
class MRUCacheBase {
 public:
  // The payload of the list. This maintains a copy of the key so we can
  // efficiently delete things given an element of the list.
  typedef std::pair<KeyType, PayloadType> value_type;

 private:
  typedef std::list<value_type> PayloadList;
  typedef typename MapType<KeyType,
                           typename PayloadList::iterator>::Type KeyIndex;

 public:
  typedef typename PayloadList::size_type size_type;

  typedef typename PayloadList::iterator iterator;
  typedef typename PayloadList::const_iterator const_iterator;
  typedef typename PayloadList::reverse_iterator reverse_iterator;
  typedef typename PayloadList::const_reverse_iterator const_reverse_iterator;

  enum { NO_AUTO_EVICT = 0 };

  // The max_size is the size at which the cache will prune its members to when
  // a new item is inserted. If the caller wants to manager this itself (for
  // example, maybe it has special work to do when something is evicted), it
  // can pass NO_AUTO_EVICT to not restrict the cache size.
  explicit MRUCacheBase(size_type max_size) : max_size_(max_size) {
  }

  MRUCacheBase(size_type max_size, const DeletorType& deletor)
      : max_size_(max_size), deletor_(deletor) {
  }

  virtual ~MRUCacheBase() {
    iterator i = begin();
    while (i != end())
      i = Erase(i);
  }

  size_type max_size() const { return max_size_; }

  // Inserts a payload item with the given key. If an existing item has
  // the same key, it is removed prior to insertion. An iterator indicating the
  // inserted item will be returned (this will always be the front of the list).
  //
  // The payload will be copied. In the case of an OwningMRUCache, this function
  // will take ownership of the pointer.
  iterator Put(const KeyType& key, const PayloadType& payload) {
    // Remove any existing payload with that key.
    typename KeyIndex::iterator index_iter = index_.find(key);
    if (index_iter != index_.end()) {
      // Erase the reference to it. This will call the deletor on the removed
      // element. The index reference will be replaced in the code below.
      Erase(index_iter->second);
    } else if (max_size_ != NO_AUTO_EVICT) {
      // New item is being inserted which might make it larger than the maximum
      // size: kick the oldest thing out if necessary.
      ShrinkToSize(max_size_ - 1);
    }

    ordering_.push_front(value_type(key, payload));
    index_.insert(std::make_pair(key, ordering_.begin()));
    return ordering_.begin();
  }

  // Retrieves the contents of the given key, or end() if not found. This method
  // has the side effect of moving the requested item to the front of the
  // recency list.
  //
  // TODO(brettw) We may want a const version of this function in the future.
  iterator Get(const KeyType& key) {
    typename KeyIndex::iterator index_iter = index_.find(key);
    if (index_iter == index_.end())
      return end();
    typename PayloadList::iterator iter = index_iter->second;

    // Move the touched item to the front of the recency ordering.
    ordering_.splice(ordering_.begin(), ordering_, iter);
    return ordering_.begin();
  }

  // Retrieves the payload associated with a given key and returns it via
  // result without affecting the ordering (unlike Get).
  iterator Peek(const KeyType& key) {
    typename KeyIndex::const_iterator index_iter = index_.find(key);
    if (index_iter == index_.end())
      return end();
    return index_iter->second;
  }

  const_iterator Peek(const KeyType& key) const {
    typename KeyIndex::const_iterator index_iter = index_.find(key);
    if (index_iter == index_.end())
      return end();
    return index_iter->second;
  }

  // Erases the item referenced by the given iterator. An iterator to the item
  // following it will be returned. The iterator must be valid.
  iterator Erase(iterator pos) {
    deletor_(pos->second);
    index_.erase(pos->first);
    return ordering_.erase(pos);
  }

  // MRUCache entries are often processed in reverse order, so we add this
  // convenience function (not typically defined by STL containers).
  reverse_iterator Erase(reverse_iterator pos) {
    // We have to actually give it the incremented iterator to delete, since
    // the forward iterator that base() returns is actually one past the item
    // being iterated over.
    return reverse_iterator(Erase((++pos).base()));
  }

  // Shrinks the cache so it only holds |new_size| items. If |new_size| is
  // bigger or equal to the current number of items, this will do nothing.
  void ShrinkToSize(size_type new_size) {
    for (size_type i = size(); i > new_size; i--)
      Erase(rbegin());
  }

  // Deletes everything from the cache.
  void Clear() {
    for (typename PayloadList::iterator i(ordering_.begin());
         i != ordering_.end(); ++i)
      deletor_(i->second);
    index_.clear();
    ordering_.clear();
  }

  // Returns the number of elements in the cache.
  size_type size() const {
    // We don't use ordering_.size() for the return value because
    // (as a linked list) it can be O(n).
    DCHECK(index_.size() == ordering_.size());
    return index_.size();
  }

  // Allows iteration over the list. Forward iteration starts with the most
  // recent item and works backwards.
  //
  // Note that since these iterators are actually iterators over a list, you
  // can keep them as you insert or delete things (as long as you don't delete
  // the one you are pointing to) and they will still be valid.
  iterator begin() { return ordering_.begin(); }
  const_iterator begin() const { return ordering_.begin(); }
  iterator end() { return ordering_.end(); }
  const_iterator end() const { return ordering_.end(); }

  reverse_iterator rbegin() { return ordering_.rbegin(); }
  const_reverse_iterator rbegin() const { return ordering_.rbegin(); }
  reverse_iterator rend() { return ordering_.rend(); }
  const_reverse_iterator rend() const { return ordering_.rend(); }

  bool empty() const { return ordering_.empty(); }

 private:
  PayloadList ordering_;
  KeyIndex index_;

  size_type max_size_;

  DeletorType deletor_;

  DISALLOW_COPY_AND_ASSIGN(MRUCacheBase);
};

// MRUCache --------------------------------------------------------------------

// A functor that does nothing. Used by the MRUCache.
template<class PayloadType>
class MRUCacheNullDeletor {
 public:
  void operator()(PayloadType& payload) {
  }
};

// A container that does not do anything to free its data. Use this when storing
// value types (as opposed to pointers) in the list.
template <class KeyType, class PayloadType>
class MRUCache : public MRUCacheBase<KeyType,
                                     PayloadType,
                                     MRUCacheNullDeletor<PayloadType> > {
 private:
  typedef MRUCacheBase<KeyType, PayloadType,
      MRUCacheNullDeletor<PayloadType> > ParentType;

 public:
  // See MRUCacheBase, noting the possibility of using NO_AUTO_EVICT.
  explicit MRUCache(typename ParentType::size_type max_size)
      : ParentType(max_size) {
  }
  virtual ~MRUCache() {
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(MRUCache);
};

// OwningMRUCache --------------------------------------------------------------

template<class PayloadType>
class MRUCachePointerDeletor {
 public:
  void operator()(PayloadType& payload) {
    delete payload;
  }
};

// A cache that owns the payload type, which must be a non-const pointer type.
// The pointers will be deleted when they are removed, replaced, or when the
// cache is destroyed.
template <class KeyType, class PayloadType>
class OwningMRUCache
    : public MRUCacheBase<KeyType,
                          PayloadType,
                          MRUCachePointerDeletor<PayloadType> > {
 private:
  typedef MRUCacheBase<KeyType, PayloadType,
      MRUCachePointerDeletor<PayloadType> > ParentType;

 public:
  // See MRUCacheBase, noting the possibility of using NO_AUTO_EVICT.
  explicit OwningMRUCache(typename ParentType::size_type max_size)
      : ParentType(max_size) {
  }
  virtual ~OwningMRUCache() {
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(OwningMRUCache);
};

// HashingMRUCache ------------------------------------------------------------

template <class KeyType, class ValueType>
struct MRUCacheHashMap {
  typedef base::hash_map<KeyType, ValueType> Type;
};

// This class is similar to MRUCache, except that it uses base::hash_map as
// the map type instead of std::map. Note that your KeyType must be hashable
// to use this cache.
template <class KeyType, class PayloadType>
class HashingMRUCache : public MRUCacheBase<KeyType,
                                            PayloadType,
                                            MRUCacheNullDeletor<PayloadType>,
                                            MRUCacheHashMap> {
 private:
  typedef MRUCacheBase<KeyType, PayloadType,
                       MRUCacheNullDeletor<PayloadType>,
                       MRUCacheHashMap> ParentType;

 public:
  // See MRUCacheBase, noting the possibility of using NO_AUTO_EVICT.
  explicit HashingMRUCache(typename ParentType::size_type max_size)
      : ParentType(max_size) {
  }
  virtual ~HashingMRUCache() {
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(HashingMRUCache);
};

}  // namespace base

#endif  // BASE_CONTAINERS_MRU_CACHE_H_
