// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkHeap.h"
#include "vtkCommonMiscModule.h" // For export macro
#include "vtkObjectFactory.h"
#include <cstddef>

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkHeap);

static size_t vtkGetLongAlignment()
{
  struct vtkTestAlignLong
  {
    char pad;
    long x;
  };

  return offsetof(vtkTestAlignLong, x);
}

class VTKCOMMONMISC_EXPORT vtkHeapBlock
{
public:
  char* Data;
  vtkHeapBlock* Next;
  size_t Size; // Variable size guards against block size changing from SetBlockSize()
               // or large requests greater than the standard block size.

  vtkHeapBlock(size_t size)
    : Next(nullptr)
    , Size(size)
  {
    this->Data = new char[size];
  }
  ~vtkHeapBlock() { delete[] this->Data; }
};

vtkHeap::vtkHeap()
{
  this->BlockSize = 256000;
  this->NumberOfBlocks = 0;
  this->NumberOfAllocations = 0;
  this->Alignment = vtkGetLongAlignment();
  this->First = nullptr;
  this->Last = nullptr;
  this->Current = nullptr;
  this->Position = 0;
}

vtkHeap::~vtkHeap()
{
  this->CleanAll();
}

void vtkHeap::SetBlockSize(size_t _arg)
{
  vtkDebugMacro(<< this->GetClassName() << " (" << this << "): setting BlockSize to "
                << static_cast<int>(_arg));
  if (this->BlockSize != _arg)
  {
    this->BlockSize = _arg;
    this->Modified();
  }
}

void* vtkHeap::AllocateMemory(size_t n)
{
  if (n % this->Alignment) // 4-byte word alignment
  {
    n += this->Alignment - (n % this->Alignment);
  }

  size_t blockSize = (n > this->BlockSize ? n : this->BlockSize);
  this->NumberOfAllocations++;

  if (!this->Current || (this->Position + n) >= this->Current->Size)
  {
    this->Add(blockSize);
  }

  char* ptr = this->Current->Data + this->Position;
  this->Position += n;

  return ptr;
}

// If a Reset() was invoked, then we reuse memory (i.e., the list of blocks)
// or allocate it as necessary. Otherwise a block is allocated and placed into
// the list of blocks.
void vtkHeap::Add(size_t blockSize)
{
  this->Position = 0; // reset to the beginning of the block

  if (this->Current && this->Current != this->Last &&
    this->Current->Next->Size >= blockSize) // reuse
  {
    this->Current = this->Current->Next;
  }

  else // allocate a new block
  {
    this->NumberOfBlocks++;
    vtkHeapBlock* block = new vtkHeapBlock(blockSize);

    if (!this->Last)
    {
      this->First = block;
      this->Current = block;
      this->Last = block;
      return;
    }

    this->Last->Next = block;
    this->Last = block;
    this->Current = block;
  }
}

void vtkHeap::CleanAll()
{
  this->Current = this->First;
  if (!this->Current)
  {
    return;
  }
  while (this->DeleteAndNext())
  {
  }
  this->First = this->Current = this->Last = nullptr;
  this->Position = 0;
}

vtkHeapBlock* vtkHeap::DeleteAndNext()
{
  if (this->Current)
  {
    vtkHeapBlock* tmp = this->Current;
    this->Current = this->Current->Next;
    delete tmp;
    return this->Current;
  }
  else
  {
    return nullptr;
  }
}

void vtkHeap::Reset()
{
  this->Current = this->First;
  this->Position = 0;
}

char* vtkHeap::StringDup(const char* str)
{
  char* newStr = static_cast<char*>(this->AllocateMemory(strlen(str) + 1));
  strcpy(newStr, str);
  return newStr;
}

void vtkHeap::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);

  os << indent << "Block Size: " << static_cast<int>(this->BlockSize) << "\n";
  os << indent << "Number of Blocks: " << this->NumberOfBlocks << "\n";
  os << indent << "Number of Allocations: " << this->NumberOfAllocations << "\n";
  os << indent << "Current bytes allocated: "
     << ((this->NumberOfBlocks - 1) * static_cast<int>(this->BlockSize) +
          static_cast<int>(this->Position))
     << "\n";
}
VTK_ABI_NAMESPACE_END
