/******************************************************************************/
/*  Copyright (c) 2010-2011, Tim Day <timday@timday.com>                      */
/*                                                                            */
/*  Permission to use, copy, modify, and/or distribute this software for any  */
/*  purpose with or without fee is hereby granted, provided that the above    */
/*  copyright notice and this permission notice appear in all copies.         */
/*                                                                            */
/*  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES  */
/*  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF          */
/*  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR   */
/*  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES    */
/*  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN     */
/*  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF   */
/*  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.            */
/******************************************************************************/

// The original source code is from:
// https://bitbucket.org/timday/lru_cache/src/497822a492a8/include/lru_cache_using_std.h

#ifndef I18N_ADDRESSINPUT_UTIL_LRU_CACHE_USING_STD_H_
#define I18N_ADDRESSINPUT_UTIL_LRU_CACHE_USING_STD_H_

#include <cassert>
#include <cstddef>
#include <list>
#include <map>
#include <utility>

// Class providing fixed-size (by number of records)
// LRU-replacement cache of a function with signature
// V f(K).
// The default comparator/hash/allocator will be used.
template <
  typename K,
  typename V
  > class lru_cache_using_std
{
public:

  typedef K key_type;
  typedef V value_type;

  // Key access history, most recent at back
  typedef std::list<key_type> key_tracker_type;

  // Key to value and key history iterator
  typedef std::map<
    key_type,
    std::pair<
      value_type,
      typename key_tracker_type::iterator
      >
  > key_to_value_type;

  // Constuctor specifies the cached function and
  // the maximum number of records to be stored
  lru_cache_using_std(
    value_type (*f)(const key_type&),
    size_t c
  )
    :_fn(f)
    ,_capacity(c)
  {
    assert(_capacity!=0);
  }

  // Obtain value of the cached function for k
  value_type operator()(const key_type& k) {

    // Attempt to find existing record
    const typename key_to_value_type::iterator it
      =_key_to_value.find(k);

    if (it==_key_to_value.end()) {

      // We don't have it:

      // Evaluate function and create new record
      const value_type v=_fn(k);
      insert(k,v);

      // Return the freshly computed value
      return v;

    } else {

      // We do have it:

      // Update access record by moving
      // accessed key to back of list
      _key_tracker.splice(
	_key_tracker.end(),
	_key_tracker,
	(*it).second.second
      );

      // Return the retrieved value
      return (*it).second.first;
    }
  }

  // Obtain the cached keys, most recently used element
  // at head, least recently used at tail.
  // This method is provided purely to support testing.
  template <typename IT> void get_keys(IT dst) const {
    typename key_tracker_type::const_reverse_iterator src
	=_key_tracker.rbegin();
    while (src!=_key_tracker.rend()) {
      *dst++ = *src++;
    }
  }

private:

  // Record a fresh key-value pair in the cache
  void insert(const key_type& k,const value_type& v) {

    // Method is only called on cache misses
    assert(_key_to_value.find(k)==_key_to_value.end());

    // Make space if necessary
    if (_key_to_value.size()==_capacity)
      evict();

    // Record k as most-recently-used key
    typename key_tracker_type::iterator it
      =_key_tracker.insert(_key_tracker.end(),k);

    // Create the key-value entry,
    // linked to the usage record.
    _key_to_value.insert(
      std::make_pair(
	k,
	std::make_pair(v,it)
      )
    );
    // No need to check return,
    // given previous assert.
  }

  // Purge the least-recently-used element in the cache
  void evict() {

    // Assert method is never called when cache is empty
    assert(!_key_tracker.empty());

    // Identify least recently used key
    const typename key_to_value_type::iterator it
      =_key_to_value.find(_key_tracker.front());
    assert(it!=_key_to_value.end());

    // Erase both elements to completely purge record
    _key_to_value.erase(it);
    _key_tracker.pop_front();
  }

  // The function to be cached
  value_type (*_fn)(const key_type&);

  // Maximum number of key-value pairs to be retained
  const size_t _capacity;

  // Key access history
  key_tracker_type _key_tracker;

  // Key-to-value lookup
  key_to_value_type _key_to_value;
};

#endif  // I18N_ADDRESSINPUT_UTIL_LRU_CACHE_USING_STD_H_
