#include <sstream>

#include "tsystem.h"
#include "tcachedlevel.h"

#include "tcodec.h"

#include "texception.h"
//#include "tcachedlevel.h"
//#include "tcodec.h"

#include "tconvert.h"

#ifdef LINUX
#include "texception.h"
//#include "tsystem.h"
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#endif

#ifdef MACOSX
#include "sys/mman.h"
#include "sys/errno.h"
#endif
//------------------------------------------------------------------------------

#define DWORDLONG_LO_DWORD(dwl64) ((DWORD)(dwl64))
#define DWORDLONG_HI_DWORD(dwl64) ((DWORD)(dwl64 >> 32))

//==============================================================================
//==============================================================================
//==============================================================================
//              TDiskCachePersist
//------------------------------------------------------------------------------

class ImpPD {
public:
  ImpPD(const TFilePath &fn)
      : m_fname(fn)
      , m_chunkSize(0)
      , m_currentFileSize(0)
      , m_fileMapAddress(0)
      , m_mapOffset(0) {
    TFileStatus fileStatus(fn);
    if (fileStatus.doesExist())
      m_currentFileSize = fileStatus.getSize();
    else
      m_currentFileSize = ImpPD::m_defaultFileSize;
  };
  virtual ~ImpPD() {}
  virtual void openFile(const TFilePath &, TINT64 fileSize) = 0;
  virtual void setCurrentView(int pos, int &newLowPos, int &newHiPos) = 0;
  TFilePath m_fname;

  TINT64 m_chunkSize;
  TINT64 m_currentFileSize;
  void *m_fileMapAddress;
  TINT64 m_mapOffset;

  // quantita' espresse in byte
  static TINT64 m_defaultFileSize;
  TUINT32 m_viewSize;
  TINT64 m_reallocSize;
};

TINT64 ImpPD::m_defaultFileSize(100 * 1024 * 1024);

//------------------------------------------------------------------------------

class TDiskCachePersist::Imp {
public:
  Imp(const TFilePath &fp);
  ~Imp();

  bool put(int frame, UCHAR *data, TUINT32 dataSize);
  UCHAR *get(int pos, TUINT32 *size);
  void openFile(const TFilePath &fp, TINT64 fileSize);
  void setCurrentView(int frame) {
    if (!m_force && ((m_lowFrame <= frame) && (frame < m_hiFrame)))
      return;  // la vista corrente gia' copre il frame
    m_force = false;
    m_impPD->setCurrentView(frame, m_lowFrame, m_hiFrame);
  }

  ImpPD *m_impPD;

  int m_lowFrame, m_hiFrame;
  TThread::Mutex m_mutex;

  bool m_force;
};

#ifdef WIN32
class ImpPDW : public ImpPD {
private:
  string getLastErrorMessage() {
    LPVOID lpMsgBuf;

    DWORD err = GetLastError();
    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
            FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL, err,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),  // Default language
        (LPTSTR)&lpMsgBuf, 0, NULL);

    string msg((LPCTSTR)lpMsgBuf);

    // Free the buffer.
    LocalFree(lpMsgBuf);
    return msg;
  }

public:
  ImpPDW(const TFilePath &fp);
  ~ImpPDW();
  void openFile(const TFilePath &fname, TINT64 fileSize);
  void setCurrentView(int pos, int &newLowPos, int &newHiPos);

private:
  HANDLE m_hFile;
  HANDLE m_hMap;

  SYSTEM_INFO m_systemInfo;
};

#else

class ImpPDX : public ImpPD {
private:
  class TCachedLevelException : public TException {
    static string msgFromErrorCode(int errorCode) {
      switch (errorCode) {
      case EBADF:
        return " fd is not a valid file descriptor  (and  MAP_ANONY­MOUS was "
               "not set).";
        break;
      /*
case EACCES_MAP_PRIVATE:
return "Map private was requested, but fd is not open for reading. Or MAP_SHARED
was requested and PROT_WRITE is set, but fd is not open in read/write O_RDWR)
mode.";
break;
*/
      case EINVAL:
        return "We don't like start or length  or  offset.   (E.g., they  are  "
               "too  large, or not aligned on a PAGESIZE boundary.)";
        break;

      case ETXTBSY:
        return "MAP_DENYWRITE was set but the object  specified  by fd is open "
               "for writing.";
        break;

      case EAGAIN:
        return "The  file  has  been locked, or too much memory has been "
               "locked.";
        break;

      case ENOMEM:
        return "No memory is available.";
        break;

      default:
        char *sysErr = strerror(errorCode);
        ostringstream os;
        os << errorCode << '\0';
        return string(sysErr) + "(" + os.str() + ")";
        break;
      }
      return "";
    }

  public:
    TCachedLevelException(int errorCode)
        : TException(msgFromErrorCode(errorCode)) {}
    ~TCachedLevelException() {}
  };

public:
  ImpPDX(const TFilePath &fp);
  ~ImpPDX();
  void openFile(const TFilePath &fname, TINT64 fileSize);
  void setCurrentView(int pos, int &newLowPos, int &newHiPos);

private:
  int m_fd;
  size_t m_pageSize;
};

#endif

//  HIGH
//------------------------------------------------------------------------------

TDiskCachePersist::TDiskCachePersist(TRasterCodec *codec, const TFilePath &fp)
    : TCachePersist(codec), m_imp(new Imp(fp)) {}

//------------------------------------------------------------------------------

TDiskCachePersist::~TDiskCachePersist() { delete m_imp; }

//------------------------------------------------------------------------------

void TDiskCachePersist::setFrameSize(int lx, int ly, int bpp) {
  m_imp->m_impPD->m_chunkSize = lx * ly * (bpp >> 3) + m_codec->getHeaderSize();
  m_imp->m_force              = true;
}

//------------------------------------------------------------------------------

TRasterP TDiskCachePersist::doGetRaster(int frame) {
  TRasterP rasP;
  TUINT32 size;
  UCHAR *src = m_imp->get(frame, &size);
  m_codec->decompress(src, size, rasP);
  delete[] src;
  return rasP;
}

//------------------------------------------------------------------------------

bool TDiskCachePersist::doGetRaster(int frame, TRaster32P &ras) const {
  assert(false);
  return false;
}

//------------------------------------------------------------------------------

bool TDiskCachePersist::doPutRaster(int frame, const TRasterP &ras) {
  UCHAR *outData     = 0;
  TINT32 outDataSize = 0;
  m_codec->compress(ras, 1, &outData, outDataSize);
  bool cached = m_imp->put(frame, outData, outDataSize);
  delete[] outData;
  return cached;
}

//------------------------------------------------------------------------------

UCHAR *TDiskCachePersist::getRawData(int frame, TINT32 &size, int &lx,
                                     int &ly) {
  TUINT32 inDataSize;
  UCHAR *src = m_imp->get(frame, &inDataSize);
  return m_codec->removeHeader(src, inDataSize, size, lx, ly);
}

//------------------------------------------------------------------------------
// MEDIUM
TDiskCachePersist::Imp::Imp(const TFilePath &fp) : m_impPD(0) {
#ifdef WIN32
  m_impPD = new ImpPDW(fp);
#else
  m_impPD = new ImpPDX(fp);
#endif
  m_impPD->m_currentFileSize = TFileStatus(fp).doesExist()
                                   ? TFileStatus(fp).getSize()
                                   : 0;  // per gli arrotondamenti...
}

//------------------------------------------------------------------------------

TDiskCachePersist::Imp::~Imp() { delete m_impPD; }
//------------------------------------------------------------------------------
// LOW

#ifdef WIN32
ImpPDW::ImpPDW(const TFilePath &fp) : ImpPD(fp), m_hFile(0), m_hMap(0) {
  GetSystemInfo(&m_systemInfo);

  m_viewSize    = 100 * 1024 * 1024;
  m_reallocSize = 250 * 1024 * 1024;

  TINT64 allocUnitCount = m_reallocSize / m_systemInfo.dwAllocationGranularity;

  // rendo m_reallocSize multiplo di m_systemInfo.dwAllocationGranularity
  if ((m_reallocSize % m_systemInfo.dwAllocationGranularity) != 0)
    ++allocUnitCount;

  m_reallocSize = allocUnitCount * m_systemInfo.dwAllocationGranularity;

  TINT64 fileSize = m_defaultFileSize;

  TFileStatus fileStatus(fp);
  if (fileStatus.doesExist()) fileSize = fileStatus.getSize();

  try {
    openFile(fp, fileSize);
  } catch (TException &e) {
    m_currentFileSize = 0;
    throw e;
  }

  m_currentFileSize = fileSize;
}

//------------------------------------------------------------------------------

ImpPDW::~ImpPDW() {
  if (m_fileMapAddress) UnmapViewOfFile(m_fileMapAddress);
  CloseHandle(m_hMap);
  CloseHandle(m_hFile);
}

//------------------------------------------------------------------------------

void ImpPDW::openFile(const TFilePath &fname, TINT64 fileSize) {
  DWORD dwDesiredAccess = GENERIC_READ | GENERIC_WRITE;
  DWORD dwShareMode     = 0;  // dwShareMode == 0 --> accesso esclusivo

  // lpSecurityAttributes == NULL --> l'handle non puo' essere
  // ereditato da processi figli
  LPSECURITY_ATTRIBUTES lpSecurityAttributes = NULL;

  DWORD dwCreationDisposition = OPEN_ALWAYS;  // CREATE_ALWAYS;
  DWORD dwFlagsAndAttributes =
      FILE_FLAG_SEQUENTIAL_SCAN;  // FILE_ATTRIBUTE_NORMAL;//

  HANDLE hTemplateFile = NULL;

  m_hFile = CreateFileW(fname.getWideString().c_str(),  // file name
                        dwDesiredAccess,                // access mode
                        dwShareMode,                    // share mode
                        NULL,                           // SD
                        dwCreationDisposition,          // how to create
                        dwFlagsAndAttributes,           // file attributes
                        hTemplateFile  // handle to template file
                        );

  if (m_hFile == INVALID_HANDLE_VALUE) {
    string errMsg = getLastErrorMessage();
    throw TException(wstring(L"Unable to open cache file: ") +
                     fname.getWideString() + L"\n" + toWideString(errMsg));
  }

  DWORD flProtect         = PAGE_READWRITE;
  DWORD dwMaximumSizeHigh = DWORDLONG_HI_DWORD(fileSize);
  DWORD dwMaximumSizeLow  = DWORDLONG_LO_DWORD(fileSize);
  LPCTSTR lpName          = NULL;  // l'oggetto non ha nome

  m_hMap = CreateFileMapping(m_hFile,            // handle to file
                             NULL,               // security
                             flProtect,          // protection
                             dwMaximumSizeHigh,  // high-order DWORD of size
                             dwMaximumSizeLow,   // low-order DWORD of size
                             lpName              // object name
                             );

  if (m_hMap == NULL) {
    string errMsg = getLastErrorMessage();
    CloseHandle(m_hFile);
    m_hFile = 0;
    throw TException("Unable to create file mapping. " + errMsg);
  }
}

//------------------------------------------------------------------------------

void ImpPDW::setCurrentView(int frame, int &newLowFrame, int &newHiFrame) {
  if (m_fileMapAddress) UnmapViewOfFile(m_fileMapAddress);

  newLowFrame = frame;

  newHiFrame = newLowFrame + TINT32(m_viewSize / m_chunkSize);

  DWORD allocGranularity = m_systemInfo.dwAllocationGranularity;
  TINT64 viewOffset =
      (TINT64(newLowFrame * m_chunkSize) / allocGranularity) * allocGranularity;
  m_mapOffset = newLowFrame * m_chunkSize - viewOffset;

  TINT64 fileSize = newHiFrame * m_chunkSize;

  if ((fileSize > m_currentFileSize) || !m_hMap)  // devo riallocare!
  {
    CloseHandle(m_hMap);
    m_hMap = 0;
    CloseHandle(m_hFile);
    m_hFile = 0;

    TINT64 allocUnitCount = fileSize / m_reallocSize;
    // rendo fileSize multiplo di m_reallocSize
    if ((fileSize % m_reallocSize) != 0) ++allocUnitCount;

    fileSize = allocUnitCount * m_reallocSize;

    openFile(m_fname, fileSize);
    m_currentFileSize = fileSize;
  }

  DWORD dwDesiredAccess = FILE_MAP_WRITE;
  m_fileMapAddress      = MapViewOfFile(
      m_hMap,           // handle to file-mapping object
      dwDesiredAccess,  // access mode: Write permission
      DWORDLONG_HI_DWORD(
          viewOffset),  // high-order DWORD of offset: Max. object size.
      DWORDLONG_LO_DWORD(
          viewOffset),  // low-order DWORD of offset: Size of hFile.
      m_viewSize);      // number of bytes to map

  if (m_fileMapAddress == NULL) {
    string errMsg = getLastErrorMessage();
    CloseHandle(m_hMap);
    m_hMap = 0;
    CloseHandle(m_hFile);
    m_hFile = 0;

    throw TException("Unable to memory map cache file. " + errMsg);
  }
}
#else

ImpPDX::ImpPDX(const TFilePath &fp) : ImpPD(fp), m_fd(-1) {
  // std::cout << "cache file " << toString(m_fname.getFullPath()) << std::endl;
  m_pageSize = getpagesize();
  openFile(m_fname, 0);
  assert(m_fd >= 0);
}

//------------------------------------------------------------------------------

ImpPDX::~ImpPDX() {
  if (m_fileMapAddress) munmap(m_fileMapAddress, m_viewSize);
  close(m_fd);
  m_fd = 0;
}

//------------------------------------------------------------------------------

void ImpPDX::openFile(const TFilePath &fname, TINT64 fileSize) {
  assert(0);
  /*
string fn(toString(fname.getWideString()));
std::cout << "open " << fn << std::endl;
m_fd = open(fn.c_str(), O_RDWR|O_CREAT, 00666);
assert(m_fd >=0);
*/
}

void ImpPDX::setCurrentView(int pos, int &newLowPos, int &newHiPos) {
  newLowPos = pos;
  newHiPos  = newLowPos + (m_viewSize / m_chunkSize);

  assert(m_fd >= 0);
  if (m_fileMapAddress)  // previous view...
    if (munmap(m_fileMapAddress, m_viewSize) != 0)
      throw TCachedLevelException(errno);
  void *start       = 0;
  int flags         = MAP_SHARED;
  size_t viewOffset = ((newLowPos * m_chunkSize) / m_pageSize) * m_pageSize;
  m_mapOffset       = newLowPos * m_chunkSize - viewOffset;

  assert(!"controllare le dimensioni");
  unsigned long lastByte =
      (unsigned long)(((newHiPos * m_chunkSize) / (double)m_pageSize + 0.5) *
                      m_pageSize);

  if (lastByte > m_currentFileSize)  // devo riallocare!
  {
    unsigned long bu =
        (unsigned long)((lastByte / (double)m_reallocSize + 0.5) *
                        m_reallocSize);
    bu = (unsigned long)((bu / (double)m_pageSize + 0.5) * m_pageSize);
    // m_maxFileSize = tmax(m_maxFileSize + m_reallocFileSize, lastByte);
    m_currentFileSize += bu;

    std::cout << "new cache size " << m_currentFileSize << std::endl;
    if (lseek(m_fd, m_currentFileSize, SEEK_SET) == -1)
      throw TCachedLevelException(errno);
    if (write(m_fd, "", 1) == -1) throw TCachedLevelException(errno);
    if (ftruncate(m_fd, m_currentFileSize) == -1)
      throw TCachedLevelException(errno);
  }

  m_fileMapAddress =
      mmap(start, m_viewSize, PROT_READ | PROT_WRITE, flags, m_fd, viewOffset);
  if (m_fileMapAddress == (void *)-1) throw TCachedLevelException(errno);
}

#endif

//------------------------------------------------------------------------------
#ifndef WIN32
#define ULONGLONG unsigned long long
#endif
bool TDiskCachePersist::Imp::put(int frame, UCHAR *data, TUINT32 dataSize) {
  if (dataSize != m_impPD->m_chunkSize) return false;

  TThread::ScopedLock sl(m_mutex);

  setCurrentView(frame);
  ULONGLONG offset = (frame - m_lowFrame) * m_impPD->m_chunkSize;
  UCHAR *dst       = (UCHAR *)m_impPD->m_fileMapAddress + offset;
  memcpy(dst + m_impPD->m_mapOffset, data, dataSize);
  return true;
}

//------------------------------------------------------------------------------

UCHAR *TDiskCachePersist::Imp::get(int pos, TUINT32 *size) {
  UCHAR *ret = new UCHAR[TINT32(m_impPD->m_chunkSize)];

  TThread::ScopedLock sl(m_mutex);
  setCurrentView(pos);
  ULONGLONG offset = (pos - m_lowFrame) * m_impPD->m_chunkSize;
  UCHAR *src =
      (UCHAR *)m_impPD->m_fileMapAddress + offset + m_impPD->m_mapOffset;
  memcpy(ret, src, TINT32(m_impPD->m_chunkSize));
  *size = TUINT32(m_impPD->m_chunkSize);
  return ret;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
// TRasterCache
//------------------------------------------------------------------------------

class TRasterCache::Data {
public:
  Data(TCachePersist *cp)
      : m_cp(cp)
      , m_size(0, 0)
      , m_prefetchEnabled(false)
      , m_prefetchedFrame(-1)
      , m_frameToPrefetch(-1)
      , m_preLoader(1, true) {}
  ~Data() {}

  class FrameData {
  public:
    bool m_valid;
    //  int m_size; // dimensione in byte del raster codificato
    ~FrameData() {}
  };

  bool isFrameCached(int frame) const;

  TDimension m_size;  // dimensioni dei raster in cache
  int m_bpp;

  typedef map<int, FrameData> Status;
  Status m_status;
  TCachePersist *m_cp;
  TThread::Mutex m_accessMutex;

  TThread::Executor m_preLoader;
  bool m_prefetchEnabled;
  int m_prefetchedFrame;
  int m_frameToPrefetch;
  TRasterP m_prefetchedRas;
};

bool TRasterCache::Data::isFrameCached(int frame) const {
  // volutamente senza ScopedLock
  Data::Status::const_iterator it = m_status.find(frame);
  if (it == m_status.end()) return false;
  Data::FrameData fd = it->second;
  return fd.m_valid;
}

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

namespace {

class Load : public TThread::Runnable {
public:
  Load(int frameToPrefetch, TCachePersist *cp, int &prefetchedFrame,
       TRasterP &prefetchedRas)
      : m_frame(frameToPrefetch)
      , m_cp(cp)
      , m_prefetchedFrame(prefetchedFrame)
      , m_prefetchedRas(prefetchedRas) {}

  void run();

private:
  int m_frame;
  TCachePersist *m_cp;
  int &m_prefetchedFrame;
  TRasterP &m_prefetchedRas;
};

void Load::run() {
  m_prefetchedRas   = m_cp->doGetRaster(m_frame);
  m_prefetchedFrame = m_frame;
}
};

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

TRasterCache::TRasterCache(TCachePersist *cp) : m_data(new Data(cp)) {}

//------------------------------------------------------------------------------

TRasterCache::~TRasterCache() { delete m_data; }

//------------------------------------------------------------------------------
void TRasterCache::setMode(const TDimension &size, int bpp) {
  TThread::ScopedLock sl(m_data->m_accessMutex);

  m_data->m_size = size;  // dimensioni dei raster in cache
  m_data->m_bpp  = bpp;

  m_data->m_cp->setFrameSize(size.lx, size.ly, bpp);
  invalidate();
}

//------------------------------------------------------------------------------

void TRasterCache::getMode(TDimension &size, int &bpp) const {
  size = m_data->m_size;  // dimensioni dei raster in cache
  bpp  = m_data->m_bpp;
}

//------------------------------------------------------------------------------

TRasterP TRasterCache::getRaster(int frame) const {
  TThread::ScopedLock sl(m_data->m_accessMutex);

  if (m_data->m_prefetchEnabled) {
    /*
if (frame == m_data->m_frameToPrefetch)
m_data->m_preLoader.wait();
else
*/
    {
      m_data->m_preLoader.clear();
      // m_data->m_preLoader.cancel();
    }

    TRasterP ras;
    if (frame == m_data->m_prefetchedFrame)
      ras = m_data->m_prefetchedRas;
    else
      ras = m_data->m_cp->doGetRaster(frame);

    if (isFrameCached(frame + 1)) {
      // il frame successivo a quello richiesto e' nella cache
      // -> avvia il prefetch di tale raster
      m_data->m_frameToPrefetch = frame + 1;
      m_data->m_preLoader.addTask(
          new Load(m_data->m_frameToPrefetch, m_data->m_cp,
                   m_data->m_prefetchedFrame, m_data->m_prefetchedRas));
    }

    return ras;
  } else {
    return m_data->m_cp->doGetRaster(frame);
  }
}

//------------------------------------------------------------------------------

bool TRasterCache::getRaster(int frame, TRaster32P &ras) const {
  TThread::ScopedLock sl(m_data->m_accessMutex);
  if (m_data->isFrameCached(frame)) {
    bool rc = m_data->m_cp->doGetRaster(frame, ras);
    assert(rc);
    return true;
  } else
    return false;
}

//------------------------------------------------------------------------------

void TRasterCache::putRaster(int frame, const TRasterP &ras) {
  TThread::ScopedLock sl(m_data->m_accessMutex);
  Data::Status::iterator it = m_data->m_status.find(frame);
  bool cached               = false;
  try {
    cached = m_data->m_cp->doPutRaster(frame, ras);
  } catch (TException &e) {
    if (it != m_data->m_status.end()) {
      Data::FrameData fd;
      fd.m_valid              = false;
      m_data->m_status[frame] = fd;
    }
    throw e;
  }
  if (cached) {
    Data::FrameData fd;
    fd.m_valid              = true;
    m_data->m_status[frame] = fd;
  }
}

//------------------------------------------------------------------------------

UCHAR *TRasterCache::getRawData(int frame, TINT32 &size, int &lx,
                                int &ly) const {
  return m_data->m_cp->getRawData(frame, size, lx, ly);
}

//------------------------------------------------------------------------------

bool TRasterCache::isFrameCached(int frame) const {
  TThread::ScopedLock sl(m_data->m_accessMutex);
  return m_data->isFrameCached(frame);
}

//------------------------------------------------------------------------------

void TRasterCache::invalidate() {
  TThread::ScopedLock sl(m_data->m_accessMutex);
  m_data->m_status.clear();
  m_data->m_cp->onInvalidate();
}

//------------------------------------------------------------------------------

void TRasterCache::invalidate(int startFrame, int endFrame) {
  assert(startFrame <= endFrame);
  TThread::ScopedLock sl(m_data->m_accessMutex);

  Data::Status::iterator low = m_data->m_status.lower_bound(startFrame);
  Data::Status::iterator hi  = m_data->m_status.upper_bound(endFrame);

#ifdef _DEBUG
  int count = m_data->m_status.size();

  if (low != m_data->m_status.end() && hi != m_data->m_status.end()) {
    int ll = low->first;
    int hh = hi->first;
    assert(ll <= hh);
  }
#endif

  if (low != m_data->m_status.end()) {
    m_data->m_status.erase(low, hi);
    m_data->m_cp->onInvalidate(startFrame, endFrame);
  }
}

//------------------------------------------------------------------------------

void TRasterCache::enablePrefetch(bool newState) {
  m_data->m_prefetchEnabled = newState;
}

//------------------------------------------------------------------------------

bool TRasterCache::isPrefetchEnabled() const {
  return m_data->m_prefetchEnabled;
}

//------------------------------------------------------------------------------

TUINT64 TRasterCache::getUsedSpace() { return m_data->m_cp->getUsedSpace(); }

//==============================================================================
//==============================================================================
//==============================================================================
//              TRamCachePersist
//------------------------------------------------------------------------------
class TRamCachePersist::Imp {
  friend class TRamCachePersist;

public:
  Imp() : m_cacheSize(0), m_chunks() {}
  ~Imp() {
    for (CompressedChunks::iterator it = m_chunks.begin(); it != m_chunks.end();
         ++it) {
      CompressedChunk *cc = it->second;
      m_cacheSize -= cc->m_size;
      delete cc;
    }
    assert(m_cacheSize == 0);  // se m_cacheSize > 0 mi sono perso qualche chunk
    // se m_cacheSize < 0 ho liberato 2 volte qualche chunk
    m_chunks.clear();
  }

  class CompressedChunk {
  public:
    CompressedChunk(UCHAR *buffer, int size) : m_buffer(buffer), m_size(size) {}
    ~CompressedChunk() { delete[] m_buffer; }
    UCHAR *m_buffer;
    int m_size;
  };

  typedef map<int, CompressedChunk *> CompressedChunks;
  CompressedChunks m_chunks;
  TUINT64 m_cacheSize;
};

TRamCachePersist::TRamCachePersist(TRasterCodec *codec)
    : TCachePersist(codec), m_imp(new Imp) {}

//------------------------------------------------------------------------------

TRamCachePersist::~TRamCachePersist() { delete m_imp; }

//------------------------------------------------------------------------------

TRasterP TRamCachePersist::doGetRaster(int frame)
// void TRamCachePersist::doGetRaster(int frame, const TRasterP &ras)
{
  Imp::CompressedChunks::const_iterator it = m_imp->m_chunks.find(frame);
  if (it == m_imp->m_chunks.end()) return TRasterP();
  Imp::CompressedChunk *cc = it->second;
  assert(cc);
  TRasterP rasP;
  m_codec->decompress(cc->m_buffer, cc->m_size, rasP);
  return rasP;
}

//------------------------------------------------------------------------------

bool TRamCachePersist::doGetRaster(int frame, TRaster32P &ras) const {
  Imp::CompressedChunks::const_iterator it = m_imp->m_chunks.find(frame);
  if (it == m_imp->m_chunks.end()) return false;
  Imp::CompressedChunk *cc = it->second;
  assert(cc);
  TRasterP rasP(ras);
  m_codec->decompress(cc->m_buffer, cc->m_size, rasP);
  return true;
}

//------------------------------------------------------------------------------

bool TRamCachePersist::doPutRaster(int frame, const TRasterP &ras) {
  Imp::CompressedChunks::iterator it = m_imp->m_chunks.find(frame);
  if (it != m_imp->m_chunks.end()) {
    m_imp->m_cacheSize -= it->second->m_size;
    delete it->second;
    m_imp->m_chunks.erase(it);
  }

  UCHAR *outData     = 0;
  TINT32 outDataSize = 0;
  m_codec->compress(ras, 1, &outData, outDataSize);
  m_imp->m_cacheSize += outDataSize;
  Imp::CompressedChunk *cc = new Imp::CompressedChunk(outData, outDataSize);
  m_imp->m_chunks.insert(Imp::CompressedChunks::value_type(frame, cc));
  return true;
}

//------------------------------------------------------------------------------

void TRamCachePersist::onInvalidate() {
  for (Imp::CompressedChunks::iterator it = m_imp->m_chunks.begin();
       it != m_imp->m_chunks.end(); ++it) {
    Imp::CompressedChunk *cc = it->second;
    m_imp->m_cacheSize -= cc->m_size;
    delete cc;
  }

  m_imp->m_chunks.clear();
}

//------------------------------------------------------------------------------

void TRamCachePersist::onInvalidate(int startFrame,
                                    int endFrame) {  // ottimizzabile
  assert(startFrame <= endFrame);

  for (int frame = startFrame; frame <= endFrame; ++frame) {
    Imp::CompressedChunks::iterator it = m_imp->m_chunks.find(frame);
    if (it != m_imp->m_chunks.end()) {
      m_imp->m_cacheSize -= it->second->m_size;
      delete it->second;
      m_imp->m_chunks.erase(it);
    }
  }
}

//------------------------------------------------------------------------------

UCHAR *TRamCachePersist::getRawData(int frame, TINT32 &size, int &lx, int &ly) {
  Imp::CompressedChunks::const_iterator it = m_imp->m_chunks.find(frame);
  if (it == m_imp->m_chunks.end()) return 0;
  Imp::CompressedChunk *cc = it->second;
  assert(cc);
  return m_codec->removeHeader(cc->m_buffer, cc->m_size, size, lx, ly);
}

//------------------------------------------------------------------------------

TUINT64 TRamCachePersist::getUsedSpace() { return m_imp->m_cacheSize; }

//------------------------------------------------------------------------------

void TDiskCachePersist::onInvalidate() {
  // m_imp->m_chunkSize = 0;
}

//------------------------------------------------------------------------------

void TDiskCachePersist::onInvalidate(int startFrame, int endFrame) {
  // m_imp->m_chunkSize = 0;
}

//------------------------------------------------------------------------------

TUINT64 TDiskCachePersist::getUsedSpace() {
  assert(0);
  return 0;
}

//==============================================================================
//==============================================================================
//==============================================================================
//
//  TDiskCachePersist2
//
//------------------------------------------------------------------------------
#ifdef WIN32
namespace {

class ZFile {
public:
  ZFile(const TFilePath &fp, bool directIO, bool asyncIO);
  ~ZFile();

  void open();

  int read(BYTE buf[], int size, TINT64 qwOffset) const;
  int write(BYTE buf[], int size, TINT64 qwOffset) const;

  void waitForAsyncIOCompletion() const;

  TFilePath getFilePath() const { return m_filepath; }

  int getBytesPerSector() const { return m_bytesPerSector; }

  static void CALLBACK FileIOCompletionRoutine(DWORD errCode,
                                               DWORD byteTransferred,
                                               LPOVERLAPPED overlapped);

private:
  TFilePath m_filepath;
  bool m_directIO;
  bool m_asyncIO;
  DWORD m_bytesPerSector;

  HANDLE m_fileHandle;
  HANDLE m_writeNotPending;
};

//-----------------------------------------------------------------------------

ZFile::ZFile(const TFilePath &fp, bool directIO, bool asyncIO)
    : m_filepath(fp)
    , m_directIO(directIO)
    , m_asyncIO(asyncIO)
    , m_fileHandle(0)
    , m_writeNotPending(0) {
  DWORD sectorsPerCluster;
  DWORD numberOfFreeClusters;
  DWORD totalNumberOfClusters;

  TFilePathSet disks = TSystem::getDisks();

  TFilePath disk = fp;
  while (std::find(disks.begin(), disks.end(), disk) == disks.end())
    disk = disk.getParentDir();

  BOOL ret = GetDiskFreeSpaceW(disk.getWideString().c_str(),  // root path
                               &sectorsPerCluster,     // sectors per cluster
                               &m_bytesPerSector,      // bytes per sector
                               &numberOfFreeClusters,  // free clusters
                               &totalNumberOfClusters  // total clusters
                               );

  if (m_asyncIO) m_writeNotPending = CreateEvent(NULL, TRUE, TRUE, NULL);
}

//-----------------------------------------------------------------------------

ZFile::~ZFile() {
  if (m_fileHandle) CloseHandle(m_fileHandle);

  if (m_writeNotPending) CloseHandle(m_writeNotPending);
}

//-----------------------------------------------------------------------------

void ZFile::open() {
  DWORD flagsAndAttributes = 0;
  flagsAndAttributes       = m_directIO ? FILE_FLAG_NO_BUFFERING : 0UL;
  flagsAndAttributes |= m_asyncIO ? FILE_FLAG_OVERLAPPED : 0UL;

  // Open the file for write access.
  m_fileHandle =
      CreateFileW(m_filepath.getWideString().c_str(),
                  GENERIC_READ | GENERIC_WRITE,  // Read/Write access
                  0,                             // no sharing allowed
                  NULL,                          // no security
                  OPEN_ALWAYS,  // open it or create new if it doesn't exist
                  flagsAndAttributes,
                  NULL);  // ignored

  if (m_fileHandle == INVALID_HANDLE_VALUE) {
    m_fileHandle = 0;

    char errorMessage[2048];

    DWORD error = GetLastError();
    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0UL, error,
                  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), errorMessage, 2048,
                  NULL);

    throw TException(errorMessage);
  }
}

//-----------------------------------------------------------------------------

int ZFile::read(BYTE buf[], int size, TINT64 qwOffset) const {
  assert(size % m_bytesPerSector == 0);
  assert(qwOffset % m_bytesPerSector == 0);

  char msg[2048] = "";
  unsigned long bytesToRead;  // Padded number of bytes to read.
  unsigned long bytesRead;    // count of bytes actually read

  OVERLAPPED overLapped;
  memset(&overLapped, 0, sizeof(overLapped));

#define DWORDLONG_LO_DWORD(dwl64) ((DWORD)(dwl64))
#define DWORDLONG_HI_DWORD(dwl64) ((DWORD)(dwl64 >> 32))

  // set the overlapped stucture with the offsets
  overLapped.Offset     = DWORDLONG_LO_DWORD(qwOffset);
  overLapped.OffsetHigh = DWORDLONG_HI_DWORD(qwOffset);

  if (m_asyncIO) {
    overLapped.hEvent = CreateEvent(NULL,   // SD
                                    TRUE,   // manual reset
                                    FALSE,  // initial state is not signaled
                                    NULL);  // object name
  } else
    overLapped.hEvent = NULL;

  bytesToRead = size;

  // Read a bunch of bytes and store in buf
  int result = ReadFile(m_fileHandle,  // file handle
                        (void *)buf,   // buffer to store data
                        bytesToRead,   // num bytes to read
                        &bytesRead,    // bytes read
                        &overLapped);  // stucture for file offsets

  if (!result) {
    DWORD error = GetLastError();
    if (m_asyncIO && ERROR_IO_PENDING == error) {
      if (!GetOverlappedResult(m_fileHandle, &overLapped, &bytesRead, TRUE)) {
        char errorMessage[2048];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0UL, error,
                      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), errorMessage,
                      2048, NULL);

        throw TException(errorMessage);
      }
    } else {
      char errorMessage[2048];
      FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0UL, error,
                    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), errorMessage,
                    2048, NULL);

      throw TException(errorMessage);
    }
  }

  return bytesRead;
}

//-----------------------------------------------------------------------------

int ZFile::write(BYTE buf[], int size, TINT64 qwOffset) const {
  assert(size % m_bytesPerSector == 0);
  assert(qwOffset % m_bytesPerSector == 0);

  char msg[2048] = "";
  unsigned long bytesToWrite;      // Padded number of bytes to write.
  unsigned long bytesWritten = 0;  // count of bytes actually writtten

  int result;

  if (m_asyncIO) {
    OVERLAPPED *overLapped = new OVERLAPPED;
    memset(overLapped, 0, sizeof(OVERLAPPED));

    // set the overlapped stucture with the offsets
    overLapped->Offset     = DWORDLONG_LO_DWORD(qwOffset);
    overLapped->OffsetHigh = DWORDLONG_HI_DWORD(qwOffset);
    overLapped->hEvent     = NULL;

    bytesToWrite = size;

    result = WriteFileEx(m_fileHandle,  // file handle
                         (void *)buf,   // data buffer
                         bytesToWrite,  // num bytes to write
                         overLapped,    // stucture for file offsets
                         &ZFile::FileIOCompletionRoutine);

    ResetEvent(m_writeNotPending);
  } else {
    OVERLAPPED overLapped;
    memset(&overLapped, 0, sizeof(overLapped));

    // set the overlapped stucture with the offsets
    overLapped.Offset     = DWORDLONG_LO_DWORD(qwOffset);
    overLapped.OffsetHigh = DWORDLONG_HI_DWORD(qwOffset);
    overLapped.hEvent     = NULL;

    bytesToWrite = size;

    result = WriteFile(m_fileHandle,   // file handle
                       (void *)buf,    // data buffer
                       bytesToWrite,   // num bytes to read
                       &bytesWritten,  // bytes read
                       &overLapped);   // stucture for file offsets
  }

  if (!result) {
    char errorMessage[2048];
    DWORD error = GetLastError();
    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0UL, error,
                  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), errorMessage, 2048,
                  NULL);

    throw TException(errorMessage);
  }

  return bytesWritten;
}

//------------------------------------------------------------------------------

void ZFile::waitForAsyncIOCompletion() const {
  if (m_asyncIO) {
    WaitForSingleObjectEx(m_writeNotPending, INFINITE, TRUE);
    SetEvent(m_writeNotPending);
  }
}

//------------------------------------------------------------------------------

void CALLBACK ZFile::FileIOCompletionRoutine(DWORD errCode,
                                             DWORD byteTransferred,
                                             LPOVERLAPPED overlapped) {
  delete overlapped;
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------

class BufferQueue {
public:
  class Item {
  public:
    Item(int frame, UCHAR *buffer, int bufferSize, int chunkSize)
        : m_frame(frame)
        , m_buffer(buffer)
        , m_bufferSize(bufferSize)
        , m_chunkSize(chunkSize) {}

    int m_frame;
    UCHAR *m_buffer;
    int m_bufferSize;
    TINT64 m_chunkSize;
  };

  BufferQueue(int capacity, int allocUnit)
      : m_capacity(capacity)
      , m_allocUnit(allocUnit)
      , m_notEmpty()
      , m_notFull()
      , m_mutex()
      , m_bufferCount(0)
      , m_nextPutItem(0)
      , m_nextGetItem(0) {
    for (int i = 0; i < m_capacity; ++i)
      m_items.push_back(Item(-1, (UCHAR *)0, 0, 0));
  }

  ~BufferQueue() {
    for (int i = 0; i < m_capacity; ++i) delete[] m_items[i].m_buffer;
  }

  void put(int frame, UCHAR *buffer, int bufferSize, int chunkSize) {
    TThread::ScopedLock sl(m_mutex);
    while (m_bufferCount == m_capacity) m_notFull.wait(sl);

    if (m_items[m_nextPutItem].m_chunkSize != chunkSize) {
      delete[] m_items[m_nextPutItem].m_buffer;
      m_items[m_nextPutItem].m_buffer    = new UCHAR[chunkSize];
      m_items[m_nextPutItem].m_chunkSize = chunkSize;
    }

    memcpy(m_items[m_nextPutItem].m_buffer, buffer, bufferSize);
    m_items[m_nextPutItem].m_frame = frame;

    m_nextPutItem = (m_nextPutItem + 1) % m_capacity;
    ++m_bufferCount;
    m_notEmpty.notifyOne();
  }

  BufferQueue::Item get() {
    TThread::ScopedLock sl(m_mutex);

    while (m_bufferCount == 0) m_notEmpty.wait(sl);

    m_notFull.notifyOne();

    BufferQueue::Item item = m_items[m_nextGetItem];

    m_nextGetItem = (m_nextGetItem + 1) % m_capacity;
    --m_bufferCount;
    return item;
  }

  void saveOne(ZFile *file) {
    TThread::ScopedLock sl(m_mutex);

    while (m_bufferCount == 0) m_notEmpty.wait(sl);

    m_notFull.notifyOne();

    BufferQueue::Item item = m_items[m_nextGetItem];

    m_nextGetItem = (m_nextGetItem + 1) % m_capacity;
    --m_bufferCount;

    TINT64 pos         = item.m_frame * item.m_chunkSize;
    TINT64 sectorCount = pos / m_allocUnit;

    if ((pos % m_allocUnit) != 0) ++sectorCount;

    pos = sectorCount * m_allocUnit;
    file->write(item.m_buffer, (TINT32)item.m_chunkSize, pos);
  }

  int size() {
    TThread::ScopedLock sl(m_mutex);
    return m_bufferCount;
  }

private:
  int m_capacity;
  int m_allocUnit;

  TThread::Condition m_notEmpty;
  TThread::Condition m_notFull;
  TThread::Mutex m_mutex;

  vector<Item> m_items;

  int m_bufferCount;
  int m_nextPutItem;
  int m_nextGetItem;
};

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------

class WriteBufferTask : public TThread::Runnable {
public:
  WriteBufferTask(ZFile *file, BufferQueue *bufferQueue)
      : m_file(file), m_bufferQueue(bufferQueue) {}

  void run();

  ZFile *m_file;
  BufferQueue *m_bufferQueue;
};

//------------------------------------------------------------------------------

void WriteBufferTask::run() {
  while (true) {
    TThread::milestone();

    try {
      m_bufferQueue->saveOne(m_file);
      m_file->waitForAsyncIOCompletion();
    } catch (TException & /*e*/) {
    } catch (...) {
    }
  }
}

}  // anonymous namespace

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------

class TDiskCachePersist2::Imp {
public:
  Imp(const TFilePath &fp, bool asyncWrite)
      : m_chunkSize(0)
      , m_readBuffer(0)
      , m_file(new ZFile(fp, true, asyncWrite))
      , m_asyncWrite(asyncWrite)
      , m_executor(0)
      , m_bufferQueue(0) {
    m_file->open();
    m_allocUnit = m_file->getBytesPerSector();

    if (m_asyncWrite) {
      m_executor    = new TThread::Executor();
      m_bufferQueue = new BufferQueue(4, m_allocUnit);
      m_executor->addTask(new WriteBufferTask(m_file, m_bufferQueue));
    }
  }

  ~Imp() {
    delete m_file;
    delete[] m_readBuffer;

    if (m_executor) {
      m_executor->cancel();
      delete m_executor;
    }

    delete m_bufferQueue;
  }

  bool put(int frame, UCHAR *data, TUINT32 dataSize);
  UCHAR *get(int pos, TUINT32 *size);

  TThread::Mutex m_mutex;
  TINT64 m_chunkSize;

  ZFile *m_file;
  int m_allocUnit;
  UCHAR *m_readBuffer;
  int m_lx;
  int m_ly;
  int m_bpp;

  bool m_asyncWrite;
  TThread::Executor *m_executor;
  BufferQueue *m_bufferQueue;
};

//------------------------------------------------------------------------------

bool TDiskCachePersist2::Imp::put(int frame, UCHAR *data, TUINT32 dataSize) {
  if (dataSize != m_chunkSize) return false;

  TINT64 pos         = frame * m_chunkSize;
  TINT64 sectorCount = pos / m_allocUnit;

  if ((pos % m_allocUnit) != 0) ++sectorCount;

  pos = sectorCount * m_allocUnit;

  m_file->write(data, dataSize, pos);
  return true;
}

//------------------------------------------------------------------------------

UCHAR *TDiskCachePersist2::Imp::get(int frame, TUINT32 *size) {
  UCHAR *ret = new UCHAR[TINT32(m_chunkSize)];

  TThread::ScopedLock sl(m_mutex);

  TINT64 pos         = frame * m_chunkSize;
  TINT64 sectorCount = pos / m_allocUnit;

  if ((pos % m_allocUnit) != 0) ++sectorCount;

  pos = sectorCount * m_allocUnit;

  m_file->read(ret, TINT32(m_chunkSize), pos);
  *size = TUINT32(m_chunkSize);
  return ret;
}

//------------------------------------------------------------------------------

TDiskCachePersist2::TDiskCachePersist2(TRasterCodec *codec,
                                       const TFilePath &fullpath)
    : TCachePersist(codec), m_imp(new Imp(fullpath, false /*true*/)) {}

//------------------------------------------------------------------------------

TDiskCachePersist2::~TDiskCachePersist2() { delete m_imp; }

//------------------------------------------------------------------------------

void TDiskCachePersist2::setFrameSize(int lx, int ly, int bpp) {
  m_imp->m_lx  = lx;
  m_imp->m_ly  = ly;
  m_imp->m_bpp = bpp;

  // inizializza m_imp->m_chunkSize in modo che sia un multiplo di
  // m_imp->m_allocUnit
  if (m_codec)
    m_imp->m_chunkSize = m_codec->getMaxCompressionSize(lx * ly * (bpp >> 3));
  else
    m_imp->m_chunkSize = lx * ly * (bpp >> 3);

  TINT64 allocUnitCount = m_imp->m_chunkSize / m_imp->m_allocUnit;
  if ((m_imp->m_chunkSize % m_imp->m_allocUnit) != 0) ++allocUnitCount;

  m_imp->m_chunkSize = allocUnitCount * m_imp->m_allocUnit;
  delete[] m_imp->m_readBuffer;
  m_imp->m_readBuffer = 0;
}

//------------------------------------------------------------------------------

TRasterP TDiskCachePersist2::doGetRaster(int frame) {
  TRasterP outRas;
  TUINT32 size;

  if (!m_imp->m_readBuffer)
    m_imp->m_readBuffer = new UCHAR[TINT32(m_imp->m_chunkSize)];

  TINT64 pos         = frame * m_imp->m_chunkSize;
  TINT64 sectorCount = pos / m_imp->m_allocUnit;

  if ((pos % m_imp->m_allocUnit) != 0) ++sectorCount;

  pos = sectorCount * m_imp->m_allocUnit;

  m_imp->m_file->read(m_imp->m_readBuffer, TINT32(m_imp->m_chunkSize), pos);
  size = TUINT32(m_imp->m_chunkSize);

  if (m_codec)
    m_codec->decompress(m_imp->m_readBuffer, size, outRas);
  else {
    switch (m_imp->m_bpp) {
    case 32: {
      TRaster32P ras(m_imp->m_lx, m_imp->m_ly);
      outRas = ras;
    } break;
    case 64: {
      TRaster64P ras(m_imp->m_lx, m_imp->m_ly);
      outRas = ras;
    } break;
    default:
      throw TException("unsupported pixel format");
      break;
    }

    unsigned int rasSize = outRas->getRowSize() * outRas->getLy();
    assert(size >= rasSize);
    outRas->lock();
    memcpy(outRas->getRawData(), m_imp->m_readBuffer, rasSize);
    outRas->unlock();
  }
  return outRas;
}

//------------------------------------------------------------------------------

bool TDiskCachePersist2::doGetRaster(int frame, TRaster32P &ras) const {
  if (!m_imp->m_readBuffer)
    m_imp->m_readBuffer = new UCHAR[TINT32(m_imp->m_chunkSize)];

  TINT64 pos         = frame * m_imp->m_chunkSize;
  TINT64 sectorCount = pos / m_imp->m_allocUnit;

  if ((pos % m_imp->m_allocUnit) != 0) ++sectorCount;

  pos = sectorCount * m_imp->m_allocUnit;

  TRasterP rasP = ras;
  if (m_codec) {
    m_imp->m_file->read(m_imp->m_readBuffer, TINT32(m_imp->m_chunkSize), pos);
    m_codec->decompress(m_imp->m_readBuffer, TINT32(m_imp->m_chunkSize), rasP);
    assert(rasP->getSize() == ras->getSize());
    ras->copy(rasP);
  } else {
    assert(ras->getLx() == ras->getWrap());
    int rasSize = ras->getRowSize() * ras->getLy();
    ras->lock();
    if (rasSize == m_imp->m_chunkSize)
      m_imp->m_file->read(ras->getRawData(), TINT32(m_imp->m_chunkSize), pos);
    else {
      assert(rasSize < m_imp->m_chunkSize);
      m_imp->m_file->read(m_imp->m_readBuffer, TINT32(m_imp->m_chunkSize), pos);
      memcpy(ras->getRawData(), m_imp->m_readBuffer, rasSize);
    }
    ras->unlock();
  }
  return true;
}

//------------------------------------------------------------------------------

bool TDiskCachePersist2::doPutRaster(int frame, const TRasterP &inRas) {
  UCHAR *outData     = 0;
  TINT32 outDataSize = 0;
  int actualDataSize = 0;

  bool deleteDataBuffer = false;
  if (m_codec) {
    m_codec->compress(inRas, m_imp->m_allocUnit, &outData, outDataSize);
    deleteDataBuffer = true;
    ;
    actualDataSize = outDataSize;
  } else {
    assert(inRas->getLx() == inRas->getWrap());
    int rasSize = inRas->getLx() * inRas->getLy() * inRas->getPixelSize();

    outDataSize = TINT32(m_imp->m_chunkSize);
    inRas->lock();
    if (rasSize == m_imp->m_chunkSize)
      outData = inRas->getRawData();
    else {
      if (!m_imp->m_readBuffer)
        m_imp->m_readBuffer = new UCHAR[TINT32(m_imp->m_chunkSize)];

      memcpy(m_imp->m_readBuffer, inRas->getRawData(), rasSize);
      outData = m_imp->m_readBuffer;
    }
    inRas->unlock();
    actualDataSize = rasSize;
  }

  assert((outDataSize % m_imp->m_allocUnit) == 0);

  bool cached = true;
  if (m_imp->m_asyncWrite)
    m_imp->m_bufferQueue->put(frame, outData, actualDataSize, outDataSize);
  else
    cached = m_imp->put(frame, outData, outDataSize);

  if (deleteDataBuffer) delete[] outData;
  return cached;
}

//------------------------------------------------------------------------------

UCHAR *TDiskCachePersist2::getRawData(int frame, TINT32 &size, int &lx,
                                      int &ly) {
  TUINT32 inDataSize;
  UCHAR *src = m_imp->get(frame, &inDataSize);
  return m_codec->removeHeader(src, inDataSize, size, lx, ly);
}

//------------------------------------------------------------------------------

void TDiskCachePersist2::onInvalidate() {}

//------------------------------------------------------------------------------

void TDiskCachePersist2::onInvalidate(int startFrame, int endFrame) {}

//------------------------------------------------------------------------------

TUINT64 TDiskCachePersist2::getUsedSpace() {
  TFileStatus fs(m_imp->m_file->getFilePath());
  return fs.getSize();
}

//------------------------------------------------------------------------------
#endif  // WIN32
