// 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/fee/fde_txtedtbuf.h"

#include <algorithm>

#include "xfa/fee/ifde_txtedtbuf.h"
#include "xfa/fee/ifde_txtedtengine.h"

#define FDE_DEFCHUNKCOUNT 2
#define FDE_TXTEDT_FORMATBLOCK_BGN 0xFFF9
#define FDE_TXTEDT_FORMATBLOCK_END 0xFFFB
#define FDE_TXTEDT_ZEROWIDTHSPACE 0x200B

CFDE_TxtEdtBufIter::CFDE_TxtEdtBufIter(CFDE_TxtEdtBuf* pBuf, FX_WCHAR wcAlias)
    : m_pBuf(pBuf),
      m_nCurChunk(0),
      m_nCurIndex(0),
      m_nIndex(0),
      m_Alias(wcAlias) {
  ASSERT(m_pBuf);
}
CFDE_TxtEdtBufIter::~CFDE_TxtEdtBufIter() {}
void CFDE_TxtEdtBufIter::Release() {
  delete this;
}
FX_BOOL CFDE_TxtEdtBufIter::Next(FX_BOOL bPrev) {
  if (bPrev) {
    if (m_nIndex == 0) {
      return FALSE;
    }
    ASSERT(m_nCurChunk < m_pBuf->m_Chunks.GetSize());
    CFDE_TxtEdtBuf::FDE_LPCHUNKHEADER lpChunk = NULL;
    if (m_nCurIndex > 0) {
      m_nCurIndex--;
    } else {
      while (m_nCurChunk > 0) {
        --m_nCurChunk;
        lpChunk = m_pBuf->m_Chunks[m_nCurChunk];
        if (lpChunk->nUsed > 0) {
          m_nCurIndex = lpChunk->nUsed - 1;
          break;
        }
      }
    }
    ASSERT(m_nCurChunk >= 0);
    m_nIndex--;
    return TRUE;
  } else {
    if (m_nIndex >= (m_pBuf->m_nTotal - 1)) {
      return FALSE;
    }
    ASSERT(m_nCurChunk < m_pBuf->m_Chunks.GetSize());
    CFDE_TxtEdtBuf::FDE_LPCHUNKHEADER lpChunk = m_pBuf->m_Chunks[m_nCurChunk];
    if (lpChunk->nUsed != (m_nCurIndex + 1)) {
      m_nCurIndex++;
    } else {
      int32_t nEnd = m_pBuf->m_Chunks.GetSize() - 1;
      while (m_nCurChunk < nEnd) {
        m_nCurChunk++;
        CFDE_TxtEdtBuf::FDE_LPCHUNKHEADER lpChunkTemp =
            m_pBuf->m_Chunks[m_nCurChunk];
        if (lpChunkTemp->nUsed > 0) {
          m_nCurIndex = 0;
          break;
        }
      }
    }
    m_nIndex++;
    return TRUE;
  }
}
void CFDE_TxtEdtBufIter::SetAt(int32_t nIndex) {
  ASSERT(nIndex >= 0 && nIndex < m_pBuf->m_nTotal);
  CFDE_TxtEdtBuf::FDE_CHUNKPLACE cp;
  m_pBuf->Index2CP(nIndex, cp);
  m_nIndex = nIndex;
  m_nCurChunk = cp.nChunkIndex;
  m_nCurIndex = cp.nCharIndex;
}
int32_t CFDE_TxtEdtBufIter::GetAt() const {
  return m_nIndex;
}
FX_WCHAR CFDE_TxtEdtBufIter::GetChar() {
  ASSERT(m_nIndex >= 0 && m_nIndex < m_pBuf->m_nTotal);
  if (m_Alias == 0 || m_nIndex == (m_pBuf->m_nTotal - 1)) {
    return m_pBuf->m_Chunks[m_nCurChunk]->wChars[m_nCurIndex];
  }
  return m_Alias;
}
FX_BOOL CFDE_TxtEdtBufIter::IsEOF(FX_BOOL bTail) const {
  return bTail ? m_nIndex == (m_pBuf->GetTextLength() - 2) : m_nIndex == 0;
}
IFX_CharIter* CFDE_TxtEdtBufIter::Clone() {
  CFDE_TxtEdtBufIter* pIter = new CFDE_TxtEdtBufIter(m_pBuf);
  pIter->m_nCurChunk = m_nCurChunk;
  pIter->m_nCurIndex = m_nCurIndex;
  pIter->m_nIndex = m_nIndex;
  pIter->m_Alias = m_Alias;
  return pIter;
}
CFDE_TxtEdtBuf::CFDE_TxtEdtBuf(int32_t nDefChunkSize)
    : m_nChunkSize(nDefChunkSize),
      m_nTotal(0),
      m_bChanged(FALSE),
      m_pAllocator(NULL) {
  ASSERT(m_nChunkSize);
  ResetChunkBuffer(FDE_DEFCHUNKCOUNT, m_nChunkSize);
}
void CFDE_TxtEdtBuf::Release() {
  delete this;
}
CFDE_TxtEdtBuf::~CFDE_TxtEdtBuf() {
  Clear(TRUE);
  m_pAllocator->Release();
  m_Chunks.RemoveAll();
}
FX_BOOL CFDE_TxtEdtBuf::SetChunkSize(int32_t nChunkSize) {
  ASSERT(nChunkSize);
  ResetChunkBuffer(FDE_DEFCHUNKCOUNT, nChunkSize);
  return TRUE;
}
int32_t CFDE_TxtEdtBuf::GetChunkSize() const {
  return m_nChunkSize;
}
int32_t CFDE_TxtEdtBuf::GetTextLength() const {
  return m_nTotal;
}
void CFDE_TxtEdtBuf::SetText(const CFX_WideString& wsText) {
  ASSERT(!wsText.IsEmpty());
  Clear(FALSE);
  int32_t nTextLength = wsText.GetLength();
  int32_t nNeedCount =
      ((nTextLength - 1) / m_nChunkSize + 1) - m_Chunks.GetSize();
  int32_t i = 0;
  for (i = 0; i < nNeedCount; i++) {
    FDE_LPCHUNKHEADER lpChunk = (FDE_LPCHUNKHEADER)m_pAllocator->Alloc(
        sizeof(FDE_CHUNKHEADER) + (m_nChunkSize - 1) * sizeof(FX_WCHAR));
    lpChunk->nUsed = 0;
    m_Chunks.Add(lpChunk);
  }
  int32_t nTotalCount = m_Chunks.GetSize();
  const FX_WCHAR* lpSrcBuf = wsText.c_str();
  int32_t nLeave = nTextLength;
  int32_t nCopyedLength = m_nChunkSize;
  for (i = 0; i < nTotalCount && nLeave > 0; i++) {
    if (nLeave < nCopyedLength) {
      nCopyedLength = nLeave;
    }
    FDE_LPCHUNKHEADER lpChunk = m_Chunks[i];
    FXSYS_memcpy(lpChunk->wChars, lpSrcBuf, nCopyedLength * sizeof(FX_WCHAR));
    nLeave -= nCopyedLength;
    lpSrcBuf += nCopyedLength;
    lpChunk->nUsed = nCopyedLength;
  }
  m_nTotal = nTextLength;
  m_bChanged = TRUE;
}
void CFDE_TxtEdtBuf::GetText(CFX_WideString& wsText) const {
  GetRange(wsText, 0, m_nTotal);
}
FX_WCHAR CFDE_TxtEdtBuf::GetCharByIndex(int32_t nIndex) const {
  ASSERT(nIndex >= 0 && nIndex < GetTextLength());
  FDE_LPCHUNKHEADER pChunkHeader = NULL;
  int32_t nTotal = 0;
  int32_t nCount = m_Chunks.GetSize();
  int32_t i = 0;
  for (i = 0; i < nCount; i++) {
    pChunkHeader = m_Chunks[i];
    nTotal += pChunkHeader->nUsed;
    if (nTotal > nIndex) {
      break;
    }
  }
  ASSERT(pChunkHeader);
  return pChunkHeader->wChars[pChunkHeader->nUsed - (nTotal - nIndex)];
}
void CFDE_TxtEdtBuf::GetRange(CFX_WideString& wsText,
                              int32_t nBegin,
                              int32_t nLength) const {
  FDE_CHUNKPLACE cp;
  Index2CP(nBegin, cp);
  int32_t nLeave = nLength;
  int32_t nCount = m_Chunks.GetSize();
  FX_WCHAR* lpDstBuf = wsText.GetBuffer(nLength);
  int32_t nChunkIndex = cp.nChunkIndex;
  FDE_LPCHUNKHEADER lpChunkHeader = m_Chunks[nChunkIndex];
  int32_t nCopyLength = lpChunkHeader->nUsed - cp.nCharIndex;
  FX_WCHAR* lpSrcBuf = lpChunkHeader->wChars + cp.nCharIndex;
  while (nLeave > 0) {
    if (nLeave <= nCopyLength) {
      nCopyLength = nLeave;
    }
    FXSYS_memcpy(lpDstBuf, lpSrcBuf, nCopyLength * sizeof(FX_WCHAR));
    nChunkIndex++;
    if (nChunkIndex >= nCount) {
      break;
    }
    lpChunkHeader = m_Chunks[nChunkIndex];
    lpSrcBuf = lpChunkHeader->wChars;
    nLeave -= nCopyLength;
    lpDstBuf += nCopyLength;
    nCopyLength = lpChunkHeader->nUsed;
  }
  wsText.ReleaseBuffer();
}
void CFDE_TxtEdtBuf::Insert(int32_t nPos,
                            const FX_WCHAR* lpText,
                            int32_t nLength) {
  ASSERT(nPos >= 0 && nPos <= m_nTotal);
  FDE_CHUNKPLACE cp;
  Index2CP(nPos, cp);
  int32_t nLengthTemp = nLength;
  if (cp.nCharIndex != 0) {
    FDE_LPCHUNKHEADER lpNewChunk = (FDE_LPCHUNKHEADER)m_pAllocator->Alloc(
        sizeof(FDE_CHUNKHEADER) + (m_nChunkSize - 1) * sizeof(FX_WCHAR));
    FDE_LPCHUNKHEADER lpChunk = m_Chunks[cp.nChunkIndex];
    int32_t nCopy = lpChunk->nUsed - cp.nCharIndex;
    FXSYS_memcpy(lpNewChunk->wChars, lpChunk->wChars + cp.nCharIndex,
                 nCopy * sizeof(FX_WCHAR));
    lpChunk->nUsed -= nCopy;
    cp.nChunkIndex++;
    m_Chunks.InsertAt(cp.nChunkIndex, lpNewChunk);
    lpNewChunk->nUsed = nCopy;
    cp.nCharIndex = 0;
  }
  if (cp.nChunkIndex != 0) {
    FDE_LPCHUNKHEADER lpChunk = m_Chunks[cp.nChunkIndex - 1];
    if (lpChunk->nUsed != m_nChunkSize) {
      cp.nChunkIndex--;
      int32_t nFree = m_nChunkSize - lpChunk->nUsed;
      int32_t nCopy = std::min(nLengthTemp, nFree);
      FXSYS_memcpy(lpChunk->wChars + lpChunk->nUsed, lpText,
                   nCopy * sizeof(FX_WCHAR));
      lpText += nCopy;
      nLengthTemp -= nCopy;
      lpChunk->nUsed += nCopy;
      cp.nChunkIndex++;
    }
  }
  while (nLengthTemp > 0) {
    FDE_LPCHUNKHEADER lpChunk = (FDE_LPCHUNKHEADER)m_pAllocator->Alloc(
        sizeof(FDE_CHUNKHEADER) + (m_nChunkSize - 1) * sizeof(FX_WCHAR));
    ASSERT(lpChunk);
    int32_t nCopy = std::min(nLengthTemp, m_nChunkSize);
    FXSYS_memcpy(lpChunk->wChars, lpText, nCopy * sizeof(FX_WCHAR));
    lpText += nCopy;
    nLengthTemp -= nCopy;
    lpChunk->nUsed = nCopy;
    m_Chunks.InsertAt(cp.nChunkIndex, lpChunk);
    cp.nChunkIndex++;
  }
  m_nTotal += nLength;
  m_bChanged = TRUE;
}
void CFDE_TxtEdtBuf::Delete(int32_t nIndex, int32_t nLength) {
  ASSERT(nLength > 0 && nIndex >= 0 && nIndex + nLength <= m_nTotal);
  FDE_CHUNKPLACE cpEnd;
  Index2CP(nIndex + nLength - 1, cpEnd);
  m_nTotal -= nLength;
  FDE_LPCHUNKHEADER lpChunk = m_Chunks[cpEnd.nChunkIndex];
  int32_t nFirstPart = cpEnd.nCharIndex + 1;
  int32_t nMovePart = lpChunk->nUsed - nFirstPart;
  if (nMovePart != 0) {
    int32_t nDelete = std::min(nFirstPart, nLength);
    FXSYS_memmove(lpChunk->wChars + nFirstPart - nDelete,
                  lpChunk->wChars + nFirstPart, nMovePart * sizeof(FX_WCHAR));
    lpChunk->nUsed -= nDelete;
    nLength -= nDelete;
    cpEnd.nChunkIndex--;
  }
  while (nLength > 0) {
    lpChunk = m_Chunks[cpEnd.nChunkIndex];
    int32_t nDeleted = std::min(lpChunk->nUsed, nLength);
    lpChunk->nUsed -= nDeleted;
    if (lpChunk->nUsed == 0) {
      m_pAllocator->Free(lpChunk);
      m_Chunks.RemoveAt(cpEnd.nChunkIndex);
      lpChunk = NULL;
    }
    nLength -= nDeleted;
    cpEnd.nChunkIndex--;
  }
  m_bChanged = TRUE;
}
void CFDE_TxtEdtBuf::Clear(FX_BOOL bRelease) {
  int32_t i = 0;
  int32_t nCount = m_Chunks.GetSize();
  if (bRelease) {
    while (i < nCount) {
      m_pAllocator->Free(m_Chunks[i++]);
    }
    m_Chunks.RemoveAll();
  } else {
    while (i < nCount) {
      m_Chunks[i++]->nUsed = 0;
    }
  }
  m_nTotal = 0;
  m_bChanged = TRUE;
}
FX_BOOL CFDE_TxtEdtBuf::Optimize(IFX_Pause* pPause) {
  if (m_bChanged == FALSE) {
    return TRUE;
  }
  if (m_nTotal == 0) {
    return TRUE;
  }
  int32_t nCount = m_Chunks.GetSize();
  if (nCount == 0) {
    return TRUE;
  }
  int32_t i = 0;
  for (; i < nCount; i++) {
    FDE_LPCHUNKHEADER lpChunk = m_Chunks[i];
    if (lpChunk->nUsed == 0) {
      m_pAllocator->Free(lpChunk);
      m_Chunks.RemoveAt(i);
      --i;
      --nCount;
    }
  }
  if (pPause != NULL && pPause->NeedToPauseNow()) {
    return FALSE;
  }
  FDE_LPCHUNKHEADER lpPreChunk = m_Chunks[0];
  FDE_LPCHUNKHEADER lpCurChunk = NULL;
  for (i = 1; i < nCount; i++) {
    lpCurChunk = m_Chunks[i];
    if (lpPreChunk->nUsed + lpCurChunk->nUsed <= m_nChunkSize) {
      FXSYS_memcpy(lpPreChunk->wChars + lpPreChunk->nUsed, lpCurChunk->wChars,
                   lpCurChunk->nUsed * sizeof(FX_WCHAR));
      lpPreChunk->nUsed += lpCurChunk->nUsed;
      m_pAllocator->Free(lpCurChunk);
      m_Chunks.RemoveAt(i);
      --i;
      --nCount;
    } else {
      lpPreChunk = lpCurChunk;
    }
    if (pPause != NULL && pPause->NeedToPauseNow()) {
      return FALSE;
    }
  }
  m_bChanged = FALSE;
  return TRUE;
}
void CFDE_TxtEdtBuf::ResetChunkBuffer(int32_t nDefChunkCount,
                                      int32_t nChunkSize) {
  ASSERT(nChunkSize);
  ASSERT(nDefChunkCount);
  if (m_pAllocator) {
    m_pAllocator->Release();
    m_pAllocator = NULL;
  }
  m_Chunks.RemoveAll();
  m_nChunkSize = nChunkSize;
  int32_t nChunkLength =
      sizeof(FDE_CHUNKHEADER) + (m_nChunkSize - 1) * sizeof(FX_WCHAR);
  m_pAllocator =
      FX_CreateAllocator(FX_ALLOCTYPE_Fixed, nDefChunkCount, nChunkLength);
  ASSERT(m_pAllocator);
  FDE_LPCHUNKHEADER lpChunkHeader =
      (FDE_LPCHUNKHEADER)m_pAllocator->Alloc(nChunkLength);
  ASSERT(lpChunkHeader);
  lpChunkHeader->nUsed = 0;
  m_Chunks.Add(lpChunkHeader);
  m_nTotal = 0;
}
int32_t CFDE_TxtEdtBuf::CP2Index(const FDE_CHUNKPLACE& cp) const {
  int32_t nTotal = cp.nCharIndex;
  int32_t i = 0;
  for (i = 0; i < cp.nChunkIndex; i++) {
    nTotal += m_Chunks[i]->nUsed;
  }
  return nTotal;
}
void CFDE_TxtEdtBuf::Index2CP(int32_t nIndex, FDE_CHUNKPLACE& cp) const {
  ASSERT(nIndex <= GetTextLength());
  if (nIndex == m_nTotal) {
    cp.nChunkIndex = m_Chunks.GetSize() - 1;
    cp.nCharIndex = m_Chunks[cp.nChunkIndex]->nUsed;
    return;
  }
  int32_t i = 0;
  int32_t nTotal = 0;
  int32_t nCount = m_Chunks.GetSize();
  for (; i < nCount; i++) {
    nTotal += m_Chunks[i]->nUsed;
    if (nTotal > nIndex) {
      break;
    }
  }
  cp.nChunkIndex = i;
  cp.nCharIndex = m_Chunks[i]->nUsed - (nTotal - nIndex);
}