/* +------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)            |
   |                          https://www.mrpt.org/                         |
   |                                                                        |
   | Copyright (c) 2005-2024, Individual contributors, see AUTHORS file     |
   | See: https://www.mrpt.org/Authors - All rights reserved.               |
   | Released under BSD License. See: https://www.mrpt.org/License          |
   +------------------------------------------------------------------------+ */

#include "io-precomp.h"  // Precompiled headers
//
#include <mrpt/io/CFileInputStream.h>
#include <mrpt/io/CFileOutputStream.h>
#include <mrpt/io/CMemoryStream.h>

#include <algorithm>  // min,max
#include <cstring>    // memcpy

using namespace mrpt::io;
using std::max;
using std::min;

CMemoryStream::CMemoryStream(const void* data, const uint64_t nBytesInData)
{
  MRPT_START
  ASSERT_(data != nullptr);

  // Set data:
  resize(nBytesInData);
  memcpy(m_memory.get(), data, nBytesInData);

  m_bytesWritten = nBytesInData;

  MRPT_END
}

void CMemoryStream::assignMemoryNotOwn(const void* data, const uint64_t nBytesInData)
{
  this->clear();
  m_memory.set(data);
  m_size = nBytesInData;
  m_position = 0;
  m_bytesWritten = nBytesInData;
  m_read_only = true;
}

CMemoryStream::~CMemoryStream()
{
  if (!m_read_only)
  {
    // Free buffer:
    if (m_memory.get()) free(m_memory.get());
    m_memory = nullptr;
    m_size = 0;
    m_position = 0;
  }
}

void CMemoryStream::resize(uint64_t newSize)
{
  if (m_read_only)
    THROW_EXCEPTION(
        "[CMemoryStream::resize] Cannot change memory block size since it "
        "was set with 'assign'");

  if (!newSize)
  {  // Free buffer:
    if (m_memory.get()) free(m_memory.get());
    m_memory = nullptr;
    m_size = 0;
    m_position = 0;
  }
  else
  {  // Resize:
    m_memory.set(realloc(m_memory.get(), newSize));

    // Check for non-memory errors??
    if (newSize) ASSERT_(m_memory.get());

    m_size = newSize;
  }

  if (m_bytesWritten > m_size) m_bytesWritten = m_size;
}

size_t CMemoryStream::Read(void* Buffer, size_t Count)
{
  // enough bytes?
  long maxAvail = (((long)m_bytesWritten)) - ((long)m_position);
  size_t nToRead = std::min<size_t>(Count, maxAvail);

  // Copy the memory block:
  if (nToRead > 0) memcpy(Buffer, reinterpret_cast<char*>(m_memory.get()) + m_position, nToRead);

  // Update cursor position:
  m_position += nToRead;
  return nToRead;
}

size_t CMemoryStream::Write(const void* Buffer, size_t Count)
{
  ASSERT_(Buffer != nullptr);
  // enough space in current bufer?
  size_t requiredSize = m_position + Count;

  if (requiredSize >= m_size)
  {
    // Incrent the size of reserved memory:
    resize(requiredSize + m_alloc_block_size);
  }

  // Copy the memory block:
  memcpy(reinterpret_cast<char*>(m_memory.get()) + m_position, Buffer, Count);

  // New cursor position:
  m_position = requiredSize;

  m_bytesWritten = max(m_bytesWritten, m_position);

  return Count;
}

uint64_t CMemoryStream::Seek(int64_t Offset, CStream::TSeekOrigin Origin)
{
  switch (Origin)
  {
    case sFromBeginning:
      m_position = Offset;
      break;
    case sFromCurrent:
      m_position += Offset;
      break;
    case sFromEnd:
      m_position = m_bytesWritten - 1 + Origin;
      break;
  };

  if (m_position >= m_size) m_position = m_size - 1;

  return m_position;
}

uint64_t CMemoryStream::getTotalBytesCount() const { return m_bytesWritten; }
uint64_t CMemoryStream::getPosition() const { return m_position; }
void CMemoryStream::clear()
{
  if (!m_read_only)
  {
    resize(0);
  }
  else
  {
    m_memory = nullptr;
    m_size = 0;
    m_position = 0;
    m_bytesWritten = 0;
    m_read_only = false;
  }
}

void* CMemoryStream::getRawBufferData() { return m_memory.get(); }
const void* CMemoryStream::getRawBufferData() const { return m_memory.get(); }
bool CMemoryStream::saveBufferToFile(const std::string& file_name)
{
  try
  {
    CFileOutputStream fo(file_name);
    fo.Write(m_memory.get(), getTotalBytesCount());
    return true;
  }
  catch (...)
  {
    return false;
  }
}

/*---------------------------------------------------------------
          loadBufferFromFile
 ---------------------------------------------------------------*/
bool CMemoryStream::loadBufferFromFile(const std::string& file_name)
{
  try
  {
    CFileInputStream fi(file_name);
    uint64_t N = fi.getTotalBytesCount();

    // Read into the buffer:
    clear();
    resize(N + 100);
    uint64_t N_read = fi.Read(m_memory.get(), N);

    m_position = N_read;
    m_bytesWritten = max(m_bytesWritten, m_position);

    return N_read == N;
  }
  catch (...)
  {
    return false;
  }
}

// Used in mrpt_send_to_zmq(). `hint` points to a `TFreeFnDataForZMQ` struct, to
// be freed here.
void mrpt::io::internal::free_fn_for_zmq(void* /* data*/, void* hint)
{
  auto* fd = reinterpret_cast<mrpt::io::internal::TFreeFnDataForZMQ*>(hint);
  if (fd->do_free) delete fd->buf;
  delete fd;
}

std::string CMemoryStream::getStreamDescription() const
{
  using namespace std::string_literals;
  return "mrpt::io::CMemoryStream with size="s + std::to_string(this->m_size) +
         ", readPosition=" + std::to_string(m_position) + ", bytesWritten="s +
         std::to_string(m_bytesWritten);
}
