// Copyright 2014 PDFium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com

#include "xfa/fgas/crt/fgas_utils.h"

#include <algorithm>

#include "core/fxcrt/fx_basic.h"

class FX_BASEARRAYDATA {
 public:
  FX_BASEARRAYDATA(int32_t growsize, int32_t blocksize);
  ~FX_BASEARRAYDATA();

  int32_t iGrowSize;
  int32_t iBlockSize;
  int32_t iTotalCount;
  int32_t iBlockCount;
  uint8_t* pBuffer;
};

FX_BASEARRAYDATA::FX_BASEARRAYDATA(int32_t growsize, int32_t blocksize)
    : iGrowSize(growsize),
      iBlockSize(blocksize),
      iTotalCount(0),
      iBlockCount(0),
      pBuffer(nullptr) {}

FX_BASEARRAYDATA::~FX_BASEARRAYDATA() {
  FX_Free(pBuffer);
}

CFX_BaseArray::CFX_BaseArray(int32_t iGrowSize, int32_t iBlockSize) {
  ASSERT(iGrowSize > 0 && iBlockSize > 0);
  m_pData = new FX_BASEARRAYDATA(iGrowSize, iBlockSize);
}
CFX_BaseArray::~CFX_BaseArray() {
  RemoveAll(false);
  delete m_pData;
}
int32_t CFX_BaseArray::GetSize() const {
  return m_pData->iBlockCount;
}
int32_t CFX_BaseArray::GetBlockSize() const {
  return m_pData->iBlockSize;
}
uint8_t* CFX_BaseArray::AddSpaceTo(int32_t index) {
  ASSERT(index > -1);
  uint8_t*& pBuffer = m_pData->pBuffer;
  int32_t& iTotalCount = m_pData->iTotalCount;
  int32_t iBlockSize = m_pData->iBlockSize;
  if (index >= iTotalCount) {
    int32_t iGrowSize = m_pData->iGrowSize;
    iTotalCount = (index / iGrowSize + 1) * iGrowSize;
    int32_t iNewSize = iTotalCount * iBlockSize;
    if (!pBuffer) {
      pBuffer = FX_Alloc(uint8_t, iNewSize);
    } else {
      pBuffer = FX_Realloc(uint8_t, pBuffer, iNewSize);
    }
  }
  int32_t& iBlockCount = m_pData->iBlockCount;
  if (index >= iBlockCount) {
    iBlockCount = index + 1;
  }
  return pBuffer + index * iBlockSize;
}
uint8_t* CFX_BaseArray::GetAt(int32_t index) const {
  ASSERT(index > -1 && index < m_pData->iBlockCount);
  return m_pData->pBuffer + index * m_pData->iBlockSize;
}
uint8_t* CFX_BaseArray::GetBuffer() const {
  return m_pData->pBuffer;
}
int32_t CFX_BaseArray::Append(const CFX_BaseArray& src,
                              int32_t iStart,
                              int32_t iCount) {
  int32_t iBlockSize = m_pData->iBlockSize;
  ASSERT(iBlockSize == src.m_pData->iBlockSize);
  int32_t& iBlockCount = m_pData->iBlockCount;
  int32_t iAdded = src.GetSize();
  ASSERT(iStart > -1 && iStart < iAdded);
  if (iCount < 0) {
    iCount = iAdded;
  }
  if (iStart + iCount > iAdded) {
    iCount = iAdded - iStart;
  }
  if (iCount < 1) {
    return 0;
  }
  uint8_t* pDst = m_pData->pBuffer + iBlockCount * iBlockSize;
  AddSpaceTo(iBlockCount + iCount - 1);
  FXSYS_memcpy(pDst, src.m_pData->pBuffer + iStart * iBlockSize,
               iCount * iBlockSize);
  return iCount;
}
int32_t CFX_BaseArray::Copy(const CFX_BaseArray& src,
                            int32_t iStart,
                            int32_t iCount) {
  int32_t iBlockSize = m_pData->iBlockSize;
  ASSERT(iBlockSize == src.m_pData->iBlockSize);
  int32_t iCopied = src.GetSize();
  ASSERT(iStart > -1 && iStart < iCopied);
  if (iCount < 0) {
    iCount = iCopied;
  }
  if (iStart + iCount > iCopied) {
    iCount = iCopied - iStart;
  }
  if (iCount < 1) {
    return 0;
  }
  RemoveAll(true);
  AddSpaceTo(iCount - 1);
  FXSYS_memcpy(m_pData->pBuffer, src.m_pData->pBuffer + iStart * iBlockSize,
               iCount * iBlockSize);
  return iCount;
}
int32_t CFX_BaseArray::RemoveLast(int32_t iCount) {
  int32_t& iBlockCount = m_pData->iBlockCount;
  if (iCount < 0 || iCount > iBlockCount) {
    iCount = iBlockCount;
    iBlockCount = 0;
  } else {
    iBlockCount -= iCount;
  }
  return iCount;
}
void CFX_BaseArray::RemoveAll(bool bLeaveMemory) {
  if (!bLeaveMemory) {
    uint8_t*& pBuffer = m_pData->pBuffer;
    if (pBuffer) {
      FX_Free(pBuffer);
      pBuffer = nullptr;
    }
    m_pData->iTotalCount = 0;
  }
  m_pData->iBlockCount = 0;
}

CFX_BaseMassArrayImp::CFX_BaseMassArrayImp(int32_t iChunkSize,
                                           int32_t iBlockSize)
    : m_iChunkSize(iChunkSize),
      m_iBlockSize(iBlockSize),
      m_iChunkCount(0),
      m_iBlockCount(0),
      m_pData(new CFX_ArrayTemplate<void*>()) {
  ASSERT(m_iChunkSize > 0 && m_iBlockSize > 0);
  m_pData->SetSize(16);
}
CFX_BaseMassArrayImp::~CFX_BaseMassArrayImp() {
  RemoveAll(false);
  delete m_pData;
}
uint8_t* CFX_BaseMassArrayImp::AddSpaceTo(int32_t index) {
  ASSERT(index > -1);
  uint8_t* pChunk;
  if (index < m_iBlockCount) {
    pChunk = (uint8_t*)m_pData->GetAt(index / m_iChunkSize);
  } else {
    int32_t iMemSize = m_iChunkSize * m_iBlockSize;
    while (true) {
      if (index < m_iChunkCount * m_iChunkSize) {
        pChunk = (uint8_t*)m_pData->GetAt(index / m_iChunkSize);
        break;
      } else {
        pChunk = FX_Alloc(uint8_t, iMemSize);
        if (m_iChunkCount < m_pData->GetSize()) {
          m_pData->SetAt(m_iChunkCount, pChunk);
        } else {
          m_pData->Add(pChunk);
        }
        m_iChunkCount++;
      }
    }
  }
  ASSERT(pChunk);
  m_iBlockCount = index + 1;
  return pChunk + (index % m_iChunkSize) * m_iBlockSize;
}
uint8_t* CFX_BaseMassArrayImp::GetAt(int32_t index) const {
  ASSERT(index > -1 && index < m_iBlockCount);
  uint8_t* pChunk = (uint8_t*)m_pData->GetAt(index / m_iChunkSize);
  ASSERT(pChunk);
  return pChunk + (index % m_iChunkSize) * m_iBlockSize;
}
int32_t CFX_BaseMassArrayImp::Append(const CFX_BaseMassArrayImp& src,
                                     int32_t iStart,
                                     int32_t iCount) {
  ASSERT(m_iBlockSize == src.m_iBlockSize);
  int32_t iAdded = src.m_iBlockCount;
  ASSERT(iStart > -1 && iStart < iAdded);
  if (iCount < 0) {
    iCount = iAdded;
  }
  if (iStart + iCount > iAdded) {
    iCount = iAdded - iStart;
  }
  if (iCount < 1) {
    return m_iBlockCount;
  }
  int32_t iBlockCount = m_iBlockCount;
  int32_t iTotal = m_iBlockCount + iCount;
  AddSpaceTo(iTotal - 1);
  Append(iBlockCount, src, iStart, iCount);
  return m_iBlockCount;
}
int32_t CFX_BaseMassArrayImp::Copy(const CFX_BaseMassArrayImp& src,
                                   int32_t iStart,
                                   int32_t iCount) {
  ASSERT(m_iBlockSize == src.m_iBlockSize);
  int32_t iCopied = src.m_iBlockCount;
  ASSERT(iStart > -1);
  if (iStart >= iCopied) {
    return 0;
  }
  RemoveAll(true);
  if (iCount < 0) {
    iCount = iCopied;
  }
  if (iStart + iCount > iCopied) {
    iCount = iCopied - iStart;
  }
  if (iCount < 1) {
    return 0;
  }
  if (m_iBlockCount < iCount) {
    AddSpaceTo(iCount - 1);
  }
  Append(0, src, iStart, iCount);
  return m_iBlockCount;
}

void CFX_BaseMassArrayImp::Append(int32_t iDstStart,
                                  const CFX_BaseMassArrayImp& src,
                                  int32_t iSrcStart,
                                  int32_t iSrcCount) {
  ASSERT(iDstStart > -1);
  ASSERT(m_iBlockSize == src.m_iBlockSize);
  ASSERT(src.m_iBlockCount > 0);
  ASSERT(m_iBlockCount >= iDstStart + iSrcCount);
  ASSERT(iSrcStart > -1);
  ASSERT(iSrcStart < src.m_iBlockCount);
  ASSERT(iSrcCount > 0);
  ASSERT(iSrcStart + iSrcCount <= src.m_iBlockCount);

  int32_t iDstChunkIndex = iDstStart / m_iChunkSize;
  int32_t iSrcChunkIndex = iSrcStart / src.m_iChunkSize;
  uint8_t* pDstChunk = (uint8_t*)GetAt(iDstStart);
  uint8_t* pSrcChunk = (uint8_t*)src.GetAt(iSrcStart);
  int32_t iDstChunkSize = m_iChunkSize - (iDstStart % m_iChunkSize);
  int32_t iSrcChunkSize = src.m_iChunkSize - (iSrcStart % src.m_iChunkSize);
  int32_t iCopySize =
      std::min(iSrcCount, std::min(iSrcChunkSize, iDstChunkSize));
  int32_t iCopyBytes = iCopySize * m_iBlockSize;
  while (iSrcCount > 0) {
    ASSERT(pDstChunk && pSrcChunk);
    FXSYS_memcpy(pDstChunk, pSrcChunk, iCopyBytes);
    iSrcCount -= iCopySize;
    iSrcChunkSize -= iCopySize;
    if (iSrcChunkSize < 1) {
      iSrcChunkSize = src.m_iChunkSize;
      iSrcChunkIndex++;
      pSrcChunk = (uint8_t*)src.m_pData->GetAt(iSrcChunkIndex);
    } else {
      pSrcChunk += iCopyBytes;
    }
    iDstChunkSize -= iCopySize;
    if (iDstChunkSize < 1) {
      iDstChunkSize = m_iChunkSize;
      iDstChunkIndex++;
      pDstChunk = (uint8_t*)m_pData->GetAt(iDstChunkIndex);
    } else {
      pDstChunk += iCopyBytes;
    }
    iCopySize = std::min(iSrcCount, std::min(iSrcChunkSize, iDstChunkSize));
    iCopyBytes = iCopySize * m_iBlockSize;
  }
}
int32_t CFX_BaseMassArrayImp::RemoveLast(int32_t iCount) {
  if (iCount < 0 || iCount >= m_iBlockCount) {
    m_iBlockCount = 0;
  } else {
    m_iBlockCount -= iCount;
  }
  return m_iBlockCount;
}
void CFX_BaseMassArrayImp::RemoveAll(bool bLeaveMemory) {
  if (bLeaveMemory) {
    m_iBlockCount = 0;
    return;
  }
  for (int32_t i = 0; i < m_iChunkCount; i++)
    FX_Free(m_pData->GetAt(i));

  m_pData->RemoveAll();
  m_iChunkCount = 0;
  m_iBlockCount = 0;
}

struct FX_BASEDISCRETEARRAYDATA {
  int32_t iBlockSize;
  int32_t iChunkSize;
  int32_t iChunkCount;
  CFX_ArrayTemplate<uint8_t*> ChunkBuffer;
};

CFX_BaseDiscreteArray::CFX_BaseDiscreteArray(int32_t iChunkSize,
                                             int32_t iBlockSize) {
  ASSERT(iChunkSize > 0 && iBlockSize > 0);
  FX_BASEDISCRETEARRAYDATA* pData = new FX_BASEDISCRETEARRAYDATA;
  m_pData = pData;
  pData->ChunkBuffer.SetSize(16);
  pData->iChunkCount = 0;
  pData->iChunkSize = iChunkSize;
  pData->iBlockSize = iBlockSize;
}
CFX_BaseDiscreteArray::~CFX_BaseDiscreteArray() {
  RemoveAll();
  delete static_cast<FX_BASEDISCRETEARRAYDATA*>(m_pData);
}
uint8_t* CFX_BaseDiscreteArray::AddSpaceTo(int32_t index) {
  ASSERT(index > -1);
  FX_BASEDISCRETEARRAYDATA* pData = (FX_BASEDISCRETEARRAYDATA*)m_pData;
  int32_t& iChunkCount = pData->iChunkCount;
  int32_t iChunkSize = pData->iChunkSize;
  uint8_t* pChunk = nullptr;
  int32_t iChunk = index / iChunkSize;
  if (iChunk < iChunkCount) {
    pChunk = pData->ChunkBuffer.GetAt(iChunk);
  }
  if (!pChunk) {
    pChunk = FX_Alloc2D(uint8_t, iChunkSize, pData->iBlockSize);
    FXSYS_memset(pChunk, 0, iChunkSize * pData->iBlockSize);
    pData->ChunkBuffer.SetAtGrow(iChunk, pChunk);
    if (iChunkCount <= iChunk) {
      iChunkCount = iChunk + 1;
    }
  }
  return pChunk + (index % iChunkSize) * pData->iBlockSize;
}
uint8_t* CFX_BaseDiscreteArray::GetAt(int32_t index) const {
  ASSERT(index >= 0);
  FX_BASEDISCRETEARRAYDATA* pData = (FX_BASEDISCRETEARRAYDATA*)m_pData;
  int32_t iChunkSize = pData->iChunkSize;
  int32_t iChunk = index / iChunkSize;
  if (iChunk >= pData->iChunkCount)
    return nullptr;

  uint8_t* pChunk = pData->ChunkBuffer.GetAt(iChunk);
  if (!pChunk)
    return nullptr;

  return pChunk + (index % iChunkSize) * pData->iBlockSize;
}
void CFX_BaseDiscreteArray::RemoveAll() {
  FX_BASEDISCRETEARRAYDATA* pData = (FX_BASEDISCRETEARRAYDATA*)m_pData;
  CFX_ArrayTemplate<uint8_t*>& ChunkBuffer = pData->ChunkBuffer;
  int32_t& iChunkCount = pData->iChunkCount;
  for (int32_t i = 0; i < iChunkCount; i++)
    FX_Free(ChunkBuffer.GetAt(i));

  ChunkBuffer.RemoveAll();
  iChunkCount = 0;
}
CFX_BaseStack::CFX_BaseStack(int32_t iChunkSize, int32_t iBlockSize) {
  m_pData = new CFX_BaseMassArrayImp(iChunkSize, iBlockSize);
}
CFX_BaseStack::~CFX_BaseStack() {
  delete (CFX_BaseMassArrayImp*)m_pData;
}
uint8_t* CFX_BaseStack::Push() {
  return m_pData->AddSpace();
}
void CFX_BaseStack::Pop() {
  int32_t& iBlockCount = m_pData->m_iBlockCount;
  if (iBlockCount < 1) {
    return;
  }
  iBlockCount--;
}
uint8_t* CFX_BaseStack::GetTopElement() const {
  int32_t iSize = m_pData->m_iBlockCount;
  if (iSize < 1) {
    return nullptr;
  }
  return m_pData->GetAt(iSize - 1);
}
int32_t CFX_BaseStack::GetSize() const {
  return m_pData->m_iBlockCount;
}
uint8_t* CFX_BaseStack::GetAt(int32_t index) const {
  return m_pData->GetAt(index);
}
void CFX_BaseStack::RemoveAll(bool bLeaveMemory) {
  m_pData->RemoveAll(bLeaveMemory);
}