// Copyright 2010-2021, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#ifndef MOZC_STORAGE_LRU_CACHE_H_
#define MOZC_STORAGE_LRU_CACHE_H_

#include <cstring>
#include <map>
#include <string>

#include "base/logging.h"
#include "base/port.h"

namespace mozc {
namespace storage {

// Note: this class keeps some resources inside of the Key/Value, even if
// such a entry is erased. Be careful to use for such classes.
// TODO(yukawa): Make this class final once we stop supporting GCC 4.6.
template <typename Key, typename Value>
class LRUCache {
 public:
  // Constructs a new LRUCache that can hold at most max_elements
  explicit LRUCache(size_t max_elements);

  // Cleans up all allocated resources.
  ~LRUCache();

  // Every Element is either on the free list or the lru list.  The
  // free list is singly-linked and only uses the next pointer, while
  // the LRU list is doubly-linked and uses both next and prev.
  struct Element {
    Element* next;
    Element* prev;
    Key key;
    Value value;
  };

  // Adds the specified key/value pair into the cache, putting it at the head
  // of the LRU list.
  void Insert(const Key& key, const Value& value);

  // Adds the specified key and return the Element added to the cache.
  // Caller needs to set the value
  Element* Insert(const Key& key);

  // Returns the cached value associated with the key, or NULL if the cache
  // does not contain an entry for that key.  The caller does not assume
  // ownership of the returned value.  The reference returned by Lookup() could
  // be invalidated by a call to Insert(), so the caller must take care to not
  // access the value if Insert() could have been called after Lookup().
  const Value* Lookup(const Key& key);

  // return non-const Value
  Value* MutableLookup(const Key& key);

  // Lookup/MutableLookup don't change the LRU order.
  const Value* LookupWithoutInsert(const Key& key) const;
  Value* MutableLookupWithoutInsert(const Key& key) const;

  // Removes the cache entry specified by key.  Returns true if the entry was
  // in the cache, otherwise returns false.
  bool Erase(const Key& key);

  // Removes all entries from the cache.  Note that this does not release the
  // memory associates with the blocks, but just pushes all the elements onto
  // the free list.
  void Clear();

  // Returns the number of entries currently in the cache.
  size_t Size() const;

  bool HasKey(const Key& key) const;

  // Returns the head of LRU list
  const Element* Head() const { return lru_head_; }
  Element* MutableHead() const { return lru_head_; }

  // Returns the tail of LRU list
  const Element* Tail() const { return lru_tail_; }

 private:
  // Allocates a new block containing next_block_size_ elements, updates
  // next_block_size_ appropriately, and pushes the elements in the new block
  // onto the free list.
  void AddBlock();

  // Pushes an element onto the head of the free list.
  void PushFreeList(Element* element);

  // Pops an element from the head of the free list.
  Element* PopFreeList();

  // Returns a free element, popping from the free list if possible, or
  // allocating a new element if the free list is empty.  If there are already
  // max_elements_ in use this will return NULL.
  Element* NextFreeElement();

  // Returns the Element* associated with key, or NULL if no element with this
  // key is found.
  Element* LookupInternal(const Key& key) const;

  // Removes the specified element from the LRU list.
  void RemoveFromLRU(Element* element);

  // Adds the specified element to the head of the LRU list.
  void PushLRUHead(Element* element);

  // Evict is similar to Erase, except that it adds the eviction callback and
  // element to a list of eviction calls, and takes an element so that another
  // lookup is not necessary.
  bool Evict(Element* element);

  typedef std::map<Key, Element*> Table;

  Table* table_;
  Element* free_list_;   // singly linked list of Element
  Element* lru_head_;    // head of doubly linked list of Element
  Element* lru_tail_;    // tail of doubly linked list of Element
  Element* blocks_[10];  // blocks of Element, with each block being twice
  //   as big as the previous block.  This allows the
  //   cache to use a small amount of memory when it
  //   contains few items, but still have low malloc
  //   overhead per insert.  The number of blocks is
  //   arbitrary, but 10 blocks allows the largest
  //   block to be 2^10 times as large as the smallest
  //   block, which seems like more than enough.
  size_t block_count_;      // how many entries in blocks_ have been used
  size_t block_capacity_;   // how many Elements can be stored in current blocks
  size_t next_block_size_;  // size of the next block to allocate
  size_t max_elements_;     // maximum elements to hold

  DISALLOW_COPY_AND_ASSIGN(LRUCache);
};

template <typename Key, typename Value>
void LRUCache<Key, Value>::AddBlock() {
  const size_t max_blocks = sizeof(blocks_) / sizeof(blocks_[0]);
  if (block_count_ < max_blocks && block_capacity_ < max_elements_) {
    blocks_[block_count_] = new Element[next_block_size_];
    block_capacity_ += next_block_size_;
    // Add new elements to free list
    for (size_t i = 0; i < next_block_size_; ++i) {
      Element* e = &((blocks_[block_count_])[i]);
      e->prev = nullptr;  // free list is not doubly linked
      e->next = free_list_;
      free_list_ = e;
    }
    block_count_++;
    size_t blocks_remaining = max_blocks - block_count_;
    if (blocks_remaining > 0) {
      next_block_size_ = (next_block_size_ << 1);
      size_t elements_remaining = max_elements_ - block_capacity_;
      if (next_block_size_ > (elements_remaining / blocks_remaining)) {
        next_block_size_ = elements_remaining / blocks_remaining;
      }
      if (block_capacity_ + next_block_size_ > max_elements_) {
        next_block_size_ = max_elements_ - block_capacity_;
      }
    }
  }
}

template <typename Key, typename Value>
void LRUCache<Key, Value>::PushFreeList(Element* element) {
  element->prev = nullptr;
  element->next = free_list_;
  free_list_ = element;
}

template <typename Key, typename Value>
typename LRUCache<Key, Value>::Element* LRUCache<Key, Value>::PopFreeList() {
  Element* r = free_list_;
  if (r != nullptr) {
    CHECK(r->prev == nullptr);
    free_list_ = r->next;
    if (free_list_ != nullptr) {
      free_list_->prev = nullptr;
    }
    r->next = nullptr;
  }
  return r;
}

template <typename Key, typename Value>
typename LRUCache<Key, Value>::Element*
LRUCache<Key, Value>::NextFreeElement() {
  Element* r = PopFreeList();
  if (r == nullptr) {
    AddBlock();
    r = PopFreeList();
  }
  return r;
}

template <typename Key, typename Value>
typename LRUCache<Key, Value>::Element* LRUCache<Key, Value>::LookupInternal(
    const Key& key) const {
  typename Table::iterator iter = table_->find(key);
  if (iter != table_->end()) {
    return iter->second;
  }
  return nullptr;
}

template <typename Key, typename Value>
void LRUCache<Key, Value>::RemoveFromLRU(Element* element) {
  if (lru_head_ == element) {
    lru_head_ = element->next;
  }
  if (lru_tail_ == element) {
    lru_tail_ = element->prev;
  }
  if (element->prev != nullptr) {
    element->prev->next = element->next;
  }
  if (element->next != nullptr) {
    element->next->prev = element->prev;
  }
  element->prev = nullptr;
  element->next = nullptr;
}

template <typename Key, typename Value>
void LRUCache<Key, Value>::PushLRUHead(Element* element) {
  if (lru_head_ == element) {
    // element is already at head, so do nothing.
    return;
  }
  RemoveFromLRU(element);
  element->next = lru_head_;
  lru_head_ = element;
  if (element->next != nullptr) {
    element->next->prev = element;
  }
  if (lru_tail_ == nullptr) {
    lru_tail_ = element;
  }
}

template <typename Key, typename Value>
bool LRUCache<Key, Value>::Evict(Element* e) {
  if (e != nullptr) {
    int erased = table_->erase(e->key);
    CHECK_EQ(erased, 1);
    RemoveFromLRU(e);
    PushFreeList(e);
    return true;
  }
  return false;
}

template <typename Key, typename Value>
LRUCache<Key, Value>::LRUCache(size_t max_elements)
    : free_list_(nullptr),
      lru_head_(nullptr),
      lru_tail_(nullptr),
      block_count_(0),
      block_capacity_(0),
      max_elements_(max_elements) {
  ::memset(blocks_, 0, sizeof(blocks_));
  table_ = new Table;
  CHECK(table_);
  if (max_elements_ <= 128) {
    next_block_size_ = max_elements_;
  } else {
    next_block_size_ = 64;
    size_t num_blocks = sizeof(blocks_) / sizeof(blocks_[0]);
    // The default starting block size is 64, which is 2^6.  Every block is
    // twice as big as the previous (see AddBlock()), so the size of the last
    // block would be 2^(6 + num_blocks) if the first block was of size 64.  If
    // max_elements is large enough that 64 is too low of a starting size,
    // figure that out here.
    while ((next_block_size_ << num_blocks) < max_elements) {
      next_block_size_ = (next_block_size_ << 1);
    }
  }
}

template <typename Key, typename Value>
LRUCache<Key, Value>::~LRUCache() {
  // To free all the memory that I have allocated I need to delete table_ and
  // any used entries in blocks_.
  delete table_;
  for (size_t i = 0; i < block_count_; ++i) {
    delete[] blocks_[i];
  }
}

template <typename Key, typename Value>
void LRUCache<Key, Value>::Insert(const Key& key, const Value& value) {
  Element* e = Insert(key);
  if (e != nullptr) {
    e->value = value;
  }
}

template <typename Key, typename Value>
typename LRUCache<Key, Value>::Element* LRUCache<Key, Value>::Insert(
    const Key& key) {
  bool erased = false;
  Element* e = LookupInternal(key);
  if (e != nullptr) {
    erased = Evict(e);
    CHECK(erased);
  }

  e = NextFreeElement();
  if (e == nullptr) {
    // no free elements, I have to replace an existing element
    e = lru_tail_;
    erased = Evict(e);
    CHECK(erased);
    e = NextFreeElement();
    CHECK(e != nullptr);
  }
  e->key = key;
  (*table_)[key] = e;
  PushLRUHead(e);

  return e;
}

template <typename Key, typename Value>
Value* LRUCache<Key, Value>::MutableLookup(const Key& key) {
  Element* e = LookupInternal(key);
  if (e != nullptr) {
    PushLRUHead(e);
    return &(e->value);
  }
  return nullptr;
}

template <typename Key, typename Value>
const Value* LRUCache<Key, Value>::Lookup(const Key& key) {
  return MutableLookup(key);
}

template <typename Key, typename Value>
Value* LRUCache<Key, Value>::MutableLookupWithoutInsert(const Key& key) const {
  Element* e = LookupInternal(key);
  if (e != nullptr) {
    return &(e->value);
  }
  return nullptr;
}

template <typename Key, typename Value>
const Value* LRUCache<Key, Value>::LookupWithoutInsert(const Key& key) const {
  return MutableLookupWithoutInsert(key);
}

template <typename Key, typename Value>
bool LRUCache<Key, Value>::Erase(const Key& key) {
  Element* e = LookupInternal(key);
  return Evict(e);
}

template <typename Key, typename Value>
void LRUCache<Key, Value>::Clear() {
  table_->clear();
  Element* e = lru_head_;
  while (e != nullptr) {
    Element* next = e->next;
    PushFreeList(e);
    e = next;
  }
  lru_head_ = lru_tail_ = nullptr;
}

template <typename Key, typename Value>
bool LRUCache<Key, Value>::HasKey(const Key& key) const {
  return (table_->find(key) != table_->end());
}

template <typename Key, typename Value>
size_t LRUCache<Key, Value>::Size() const {
  return table_->size();
}

}  // namespace storage
}  // namespace mozc
#endif  // MOZC_STORAGE_LRU_CACHE_H_
