#ifndef vtkExodusIICache_h
#define vtkExodusIICache_h

// ============================================================================
// The following classes define an LRU cache for data arrays
// loaded by the Exodus reader. Here's how they work:
//
// The actual cache consists of two STL containers: a set of
// cache entries (vtkExodusIICacheEntry) and a list of
// cache references (vtkExodusIICacheRef). The entries in
// these containers are sorted for fast retrieval:
// 1. The cache entries are indexed by the timestep, the
//    object type (edge block, face set, ...), and the
//    object ID (if one exists). When you call Find() to
//    retrieve a cache entry, you provide a key containing
//    this information and the array is returned if it exists.
// 2. The list of cache references are stored in "least-recently-used"
//    order. The least recently referenced array is the first in
//    the list. Whenever you request an entry with Find(), it is
//    moved to the back of the list if it exists.
// This makes retrieving arrays O(n log n) and popping LRU
// entries O(1). Each cache entry stores an iterator into
// the list of references so that it can be located quickly for
// removal.

#include "vtkIOExodusModule.h" // For export macro
#include "vtkObject.h"

#include <map> // used for cache storage
#include <list> // use for LRU ordering

class VTKIOEXODUS_EXPORT vtkExodusIICacheKey
{
public:
  int Time;
  int ObjectType;
  int ObjectId;
  int ArrayId;
  vtkExodusIICacheKey()
  {
    Time = -1;
    ObjectType = -1;
    ObjectId = -1;
    ArrayId = -1;
  }
  vtkExodusIICacheKey( int time, int objType, int objId, int arrId )
  {
    Time = time;
    ObjectType = objType;
    ObjectId = objId;
    ArrayId = arrId;
  }
  vtkExodusIICacheKey( const vtkExodusIICacheKey& src )
  {
    Time = src.Time;
    ObjectType = src.ObjectType;
    ObjectId = src.ObjectId;
    ArrayId = src.ArrayId;
  }
  vtkExodusIICacheKey& operator = ( const vtkExodusIICacheKey& src )
  {
    Time = src.Time;
    ObjectType = src.ObjectType;
    ObjectId = src.ObjectId;
    ArrayId = src.ArrayId;
    return *this;
  }
  bool match( const vtkExodusIICacheKey&other, const vtkExodusIICacheKey& pattern ) const
  {
    if ( pattern.Time && this->Time != other.Time )
      return false;
    if ( pattern.ObjectType && this->ObjectType != other.ObjectType )
      return false;
    if ( pattern.ObjectId && this->ObjectId != other.ObjectId )
      return false;
    if ( pattern.ArrayId && this->ArrayId != other.ArrayId )
      return false;
    return true;
  }
  bool operator < ( const vtkExodusIICacheKey& other ) const
  {
    if ( this->Time < other.Time )
      return true;
    else if ( this->Time > other.Time )
      return false;
    if ( this->ObjectType < other.ObjectType )
      return true;
    else if ( this->ObjectType > other.ObjectType )
      return false;
    if ( this->ObjectId < other.ObjectId )
      return true;
    else if ( this->ObjectId > other.ObjectId )
      return false;
    if ( this->ArrayId < other.ArrayId )
      return true;
    return false;
  }
};

class vtkExodusIICacheEntry;
class vtkExodusIICache;
class vtkDataArray;

typedef std::map<vtkExodusIICacheKey,vtkExodusIICacheEntry*> vtkExodusIICacheSet;
typedef std::map<vtkExodusIICacheKey,vtkExodusIICacheEntry*>::iterator vtkExodusIICacheRef;
typedef std::list<vtkExodusIICacheRef> vtkExodusIICacheLRU;
typedef std::list<vtkExodusIICacheRef>::iterator vtkExodusIICacheLRURef;

class VTKIOEXODUS_EXPORT vtkExodusIICacheEntry
{
public:
  vtkExodusIICacheEntry();
  vtkExodusIICacheEntry( vtkDataArray* arr );
  vtkExodusIICacheEntry( const vtkExodusIICacheEntry& other );

  ~vtkExodusIICacheEntry();

  vtkDataArray* GetValue() { return this->Value; }

protected:
  vtkDataArray* Value;
  vtkExodusIICacheLRURef LRUEntry;

  friend class vtkExodusIICache;
};

class VTKIOEXODUS_EXPORT vtkExodusIICache : public vtkObject
{
public:
  static vtkExodusIICache* New();
  vtkTypeMacro(vtkExodusIICache,vtkObject);
  void PrintSelf( ostream& os, vtkIndent indent ) override;

  /// Empty the cache
  void Clear();

  /// Set the maximum allowable cache size. This will remove cache entries if the capacity is reduced below the current size.
  void SetCacheCapacity( double sizeInMiB );

  /** See how much cache space is left.
    * This is the difference between the capacity and the size of the cache.
    * The result is in MiB.
    */
  double GetSpaceLeft()
    { return this->Capacity - this->Size; }

  /** Remove cache entries until the size of the cache is at or below the given size.
    * Returns a nonzero value if deletions were required.
    */
  int ReduceToSize( double newSize );

  /// Insert an entry into the cache (this can remove other cache entries to make space).
  void Insert( vtkExodusIICacheKey& key, vtkDataArray* value );

  /** Determine whether a cache entry exists. If it does, return it -- otherwise return nullptr.
    * If a cache entry exists, it is marked as most recently used.
    */
  vtkDataArray*& Find( const vtkExodusIICacheKey& );

  /** Invalidate a cache entry (drop it from the cache) if the key exists.
    * This does nothing if the cache entry does not exist.
    * Returns 1 if the cache entry existed prior to this call and 0 otherwise.
    */
  int Invalidate( const vtkExodusIICacheKey& key );

  /** Invalidate all cache entries matching a specified pattern, dropping all matches from the cache.
    * Any nonzero entry in the \a pattern forces a comparison between the corresponding value of \a key.
    * Any cache entries satisfying all the comparisons will be dropped.
    * If pattern is entirely zero, this will empty the entire cache.
    * This is useful for invalidating all entries of a given object type.
    *
    * Returns the number of cache entries dropped.
    * It is not an error to specify an empty range -- 0 will be returned if one is given.
    */
  int Invalidate( const vtkExodusIICacheKey& key, const vtkExodusIICacheKey& pattern );

protected:
  /// Default constructor
  vtkExodusIICache();

  /// Destructor.
  ~vtkExodusIICache() override;


  /// Avoid (some) FP problems
  void RecomputeSize();

  /// The capacity of the cache (i.e., the maximum size of all arrays it contains) in MiB.
  double Capacity;

  /// The current size of the cache (i.e., the size of the all the arrays it currently contains) in MiB.
  double Size;

  /** A least-recently-used (LRU) cache to hold arrays.
    * During RequestData the cache may contain more than its maximum size since
    * the user may request more data than the cache can hold. However, the cache
    * is expunged whenever a new array is loaded. Never count on the cache holding
    * what you request for very long.
    */
  vtkExodusIICacheSet Cache;

  /// The actual LRU list (indices into the cache ordered least to most recently used).
  vtkExodusIICacheLRU LRU;

private:
  vtkExodusIICache( const vtkExodusIICache& ) = delete;
  void operator = ( const vtkExodusIICache& ) = delete;
};
#endif // vtkExodusIICache_h
