// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkExodusIICache.h"

#include "vtkDataArray.h"
#include "vtkObjectFactory.h"

// Define VTK_EXO_DBG_CACHE to print cache adds, drops, and replacements.
//#undef VTK_EXO_DBG_CACHE

#define VTK_EXO_PRT_KEY(ckey)                                                                      \
  "(" << (ckey).Time << ", " << (ckey).ObjectType << ", " << (ckey).ObjectId << ", "               \
      << (ckey).ArrayId << ")"
#define VTK_EXO_PRT_ARR(cval)                                                                      \
  " [" << (cval) << "," << ((cval) ? (cval)->GetActualMemorySize() / 1024. : 0.) << "/"            \
       << this->Size << "/" << this->Capacity << "]"
#define VTK_EXO_PRT_ARR2(cval)                                                                     \
  " [" << (cval) << ", " << ((cval) ? (cval)->GetActualMemorySize() / 1024. : 0.) << "]"

#if 0
VTK_ABI_NAMESPACE_BEGIN
static void printCache( vtkExodusIICacheSet& cache, vtkExodusIICacheLRU& lru )
{
  cout << "Cache\n";
  vtkExodusIICacheRef cit;
  for ( cit = cache.begin(); cit != cache.end(); ++cit )
  {
    cout << VTK_EXO_PRT_KEY( cit->first ) << VTK_EXO_PRT_ARR2( cit->second->GetValue() ) << "\n";
  }
  cout << "LRU\n";
  vtkExodusIICacheLRURef lit;
  for ( lit = lru.begin(); lit != lru.end(); ++lit )
  {
    cout << VTK_EXO_PRT_KEY( (*lit)->first ) << "\n";
  }
}
VTK_ABI_NAMESPACE_END
#endif // 0

// ============================================================================
VTK_ABI_NAMESPACE_BEGIN
vtkExodusIICacheEntry::vtkExodusIICacheEntry()
{
  this->Value = nullptr;
}

vtkExodusIICacheEntry::vtkExodusIICacheEntry(vtkDataArray* arr)
{
  this->Value = arr;
  if (arr)
    this->Value->Register(nullptr);
}
vtkExodusIICacheEntry::~vtkExodusIICacheEntry()
{
  if (this->Value)
    this->Value->Delete();
}

vtkExodusIICacheEntry::vtkExodusIICacheEntry(const vtkExodusIICacheEntry& other)
{
  this->Value = other.Value;
  if (this->Value)
    this->Value->Register(nullptr);
}

#if 0
void printLRUBack( vtkExodusIICacheRef& cit )
{
  cout << "Key is " << VTK_EXO_PRT_KEY( cit->first ) << "\n";
}
#endif // 0

// ============================================================================

vtkStandardNewMacro(vtkExodusIICache);

vtkExodusIICache::vtkExodusIICache()
{
  this->Size = 0.;
  this->Capacity = 2.;
}

vtkExodusIICache::~vtkExodusIICache()
{
  this->ReduceToSize(0.);
}

void vtkExodusIICache::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "Capacity: " << this->Capacity << " MiB\n";
  os << indent << "Size: " << this->Size << " MiB\n";
  os << indent << "Cache: " << &this->Cache << " (" << this->Cache.size() << ")\n";
  os << indent << "LRU: " << &this->LRU << "\n";
}

void vtkExodusIICache::Clear()
{
  // printCache( this->Cache, this->LRU );
  this->ReduceToSize(0.);
}

void vtkExodusIICache::SetCacheCapacity(double sizeInMiB)
{
  if (sizeInMiB == this->Capacity)
    return;

  if (this->Size > sizeInMiB)
  {
    this->ReduceToSize(sizeInMiB);
  }

  this->Capacity = sizeInMiB < 0 ? 0 : sizeInMiB;
}

int vtkExodusIICache::ReduceToSize(double newSize)
{
  int deletedSomething = 0;
  while (this->Size > newSize && !this->LRU.empty())
  {
    vtkExodusIICacheRef cit(this->LRU.back());
    vtkDataArray* arr = cit->second->Value;
    if (arr)
    {
      deletedSomething = 1;
      double arrSz = (double)arr->GetActualMemorySize() / 1024.;
      this->Size -= arrSz;
#ifdef VTK_EXO_DBG_CACHE
      cout << "Dropping " << VTK_EXO_PRT_KEY(cit->first) << VTK_EXO_PRT_ARR(arr) << "\n";
#endif // VTK_EXO_DBG_CACHE
      if (this->Size <= 0)
      {
        if (this->Cache.empty())
          this->Size = 0.;
        else
          this->RecomputeSize(); // oops, FP roundoff
      }
    }
    else
    {
#ifdef VTK_EXO_DBG_CACHE
      cout << "Dropping " << VTK_EXO_PRT_KEY(cit->first) << VTK_EXO_PRT_ARR(arr) << "\n";
#endif // VTK_EXO_DBG_CACHE
    }

    delete cit->second;
    this->Cache.erase(cit);
    this->LRU.pop_back();
  }

  if (this->Cache.empty())
  {
    this->Size = 0;
  }

  return deletedSomething;
}

void vtkExodusIICache::Insert(vtkExodusIICacheKey& key, vtkDataArray* value)
{
  double vsize = value ? value->GetActualMemorySize() / 1024. : 0.;

  vtkExodusIICacheRef it = this->Cache.find(key);
  if (it != this->Cache.end())
  {
    if (it->second->Value == value)
      return;

    // Remove existing array and put in our new one.
    this->Size -= vsize;
    if (this->Size <= 0)
    {
      this->RecomputeSize();
    }
    this->ReduceToSize(this->Capacity - vsize);
    it->second->Value->Delete();
    it->second->Value = value;
    it->second->Value->Register(
      nullptr); // Since we re-use the cache entry, the constructor's Register won't get called.
    this->Size += vsize;
#ifdef VTK_EXO_DBG_CACHE
    cout << "Replacing " << VTK_EXO_PRT_KEY(it->first) << VTK_EXO_PRT_ARR(value) << "\n";
#endif // VTK_EXO_DBG_CACHE
    this->LRU.erase(it->second->LRUEntry);
    it->second->LRUEntry = this->LRU.insert(this->LRU.begin(), it);
  }
  else
  {
    this->ReduceToSize(this->Capacity - vsize);
    std::pair<const vtkExodusIICacheKey, vtkExodusIICacheEntry*> entry(
      key, new vtkExodusIICacheEntry(value));
    std::pair<vtkExodusIICacheSet::iterator, bool> iret = this->Cache.insert(entry);
    this->Size += vsize;
#ifdef VTK_EXO_DBG_CACHE
    cout << "Adding " << VTK_EXO_PRT_KEY(key) << VTK_EXO_PRT_ARR(value) << "\n";
#endif // VTK_EXO_DBG_CACHE
    iret.first->second->LRUEntry = this->LRU.insert(this->LRU.begin(), iret.first);
  }
  // printCache( this->Cache, this->LRU );
}

vtkDataArray*& vtkExodusIICache::Find(const vtkExodusIICacheKey& key)
{
  static vtkDataArray* dummy = nullptr;

  vtkExodusIICacheRef it = this->Cache.find(key);
  if (it != this->Cache.end())
  {
    this->LRU.erase(it->second->LRUEntry);
    it->second->LRUEntry = this->LRU.insert(this->LRU.begin(), it);
    return it->second->Value;
  }

  dummy = nullptr;
  return dummy;
}

int vtkExodusIICache::Invalidate(const vtkExodusIICacheKey& key)
{
  vtkExodusIICacheRef it = this->Cache.find(key);
  if (it != this->Cache.end())
  {
#ifdef VTK_EXO_DBG_CACHE
    cout << "Dropping " << VTK_EXO_PRT_KEY(it->first) << VTK_EXO_PRT_ARR(it->second->Value) << "\n";
#endif // VTK_EXO_DBG_CACHE
    this->LRU.erase(it->second->LRUEntry);
    if (it->second->Value)
    {
      this->Size -= it->second->Value->GetActualMemorySize() / 1024.;
    }
    delete it->second;
    this->Cache.erase(it);

    if (this->Size <= 0)
    {
      if (this->Cache.empty())
        this->Size = 0.;
      else
        this->RecomputeSize(); // oops, FP roundoff
    }

    return 1;
  }
  return 0;
}

int vtkExodusIICache::Invalidate(const vtkExodusIICacheKey& key, const vtkExodusIICacheKey& pattern)
{
  vtkExodusIICacheRef it;
  int nDropped = 0;
  it = this->Cache.begin();
  while (it != this->Cache.end())
  {
    if (!it->first.match(key, pattern))
    {
      ++it;
      continue;
    }

#ifdef VTK_EXO_DBG_CACHE
    cout << "Dropping " << VTK_EXO_PRT_KEY(it->first) << VTK_EXO_PRT_ARR(it->second->Value) << "\n";
#endif // VTK_EXO_DBG_CACHE
    this->LRU.erase(it->second->LRUEntry);
    if (it->second->Value)
    {
      this->Size -= it->second->Value->GetActualMemorySize() / 1024.;
    }
    vtkExodusIICacheRef tmpIt = it++;
    delete tmpIt->second;
    this->Cache.erase(tmpIt);

    if (this->Size <= 0)
    {
      if (this->Cache.empty())
        this->Size = 0.;
      else
        this->RecomputeSize(); // oops, FP roundoff
    }

    ++nDropped;
  }
  return nDropped;
}

void vtkExodusIICache::RecomputeSize()
{
  this->Size = 0.;
  vtkExodusIICacheRef it;
  for (it = this->Cache.begin(); it != this->Cache.end(); ++it)
  {
    if (it->second->Value)
    {
      this->Size += (double)it->second->Value->GetActualMemorySize() / 1024.;
    }
  }
}
VTK_ABI_NAMESPACE_END
