// 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 "../../include/fpdfdoc/fpdf_doc.h"
const int nMaxRecursion = 32;
int CPDF_Dest::GetPageIndex(CPDF_Document* pDoc) {
  if (m_pObj == NULL || m_pObj->GetType() != PDFOBJ_ARRAY) {
    return 0;
  }
  CPDF_Object* pPage = ((CPDF_Array*)m_pObj)->GetElementValue(0);
  if (pPage == NULL) {
    return 0;
  }
  if (pPage->GetType() == PDFOBJ_NUMBER) {
    return pPage->GetInteger();
  }
  if (pPage->GetType() != PDFOBJ_DICTIONARY) {
    return 0;
  }
  return pDoc->GetPageIndex(pPage->GetObjNum());
}
FX_DWORD CPDF_Dest::GetPageObjNum() {
  if (m_pObj == NULL || m_pObj->GetType() != PDFOBJ_ARRAY) {
    return 0;
  }
  CPDF_Object* pPage = ((CPDF_Array*)m_pObj)->GetElementValue(0);
  if (pPage == NULL) {
    return 0;
  }
  if (pPage->GetType() == PDFOBJ_NUMBER) {
    return pPage->GetInteger();
  }
  if (pPage->GetType() == PDFOBJ_DICTIONARY) {
    return pPage->GetObjNum();
  }
  return 0;
}
const FX_CHAR* g_sZoomModes[] = {"XYZ",  "Fit",   "FitH",  "FitV", "FitR",
                                 "FitB", "FitBH", "FitBV", ""};
int CPDF_Dest::GetZoomMode() {
  if (m_pObj == NULL || m_pObj->GetType() != PDFOBJ_ARRAY) {
    return 0;
  }
  CFX_ByteString mode;
  CPDF_Object* pObj = ((CPDF_Array*)m_pObj)->GetElementValue(1);
  mode = pObj ? pObj->GetString() : CFX_ByteString();
  int i = 0;
  while (g_sZoomModes[i][0] != '\0') {
    if (mode == g_sZoomModes[i]) {
      return i + 1;
    }
    i++;
  }
  return 0;
}
FX_FLOAT CPDF_Dest::GetParam(int index) {
  if (m_pObj == NULL || m_pObj->GetType() != PDFOBJ_ARRAY) {
    return 0;
  }
  return ((CPDF_Array*)m_pObj)->GetNumber(2 + index);
}
CFX_ByteString CPDF_Dest::GetRemoteName() {
  if (m_pObj == NULL) {
    return CFX_ByteString();
  }
  return m_pObj->GetString();
}
CPDF_NameTree::CPDF_NameTree(CPDF_Document* pDoc,
                             const CFX_ByteStringC& category) {
  if (pDoc->GetRoot() && pDoc->GetRoot()->GetDict(FX_BSTRC("Names")))
    m_pRoot = pDoc->GetRoot()->GetDict(FX_BSTRC("Names"))->GetDict(category);
  else
    m_pRoot = NULL;
}
static CPDF_Object* SearchNameNode(CPDF_Dictionary* pNode,
                                   const CFX_ByteString& csName,
                                   int& nIndex,
                                   CPDF_Array** ppFind,
                                   int nLevel = 0) {
  if (nLevel > nMaxRecursion) {
    return NULL;
  }
  CPDF_Array* pLimits = pNode->GetArray(FX_BSTRC("Limits"));
  if (pLimits != NULL) {
    CFX_ByteString csLeft = pLimits->GetString(0);
    CFX_ByteString csRight = pLimits->GetString(1);
    if (csLeft.Compare(csRight) > 0) {
      CFX_ByteString csTmp = csRight;
      csRight = csLeft;
      csLeft = csTmp;
    }
    if (csName.Compare(csLeft) < 0 || csName.Compare(csRight) > 0) {
      return NULL;
    }
  }
  CPDF_Array* pNames = pNode->GetArray(FX_BSTRC("Names"));
  if (pNames) {
    FX_DWORD dwCount = pNames->GetCount() / 2;
    for (FX_DWORD i = 0; i < dwCount; i++) {
      CFX_ByteString csValue = pNames->GetString(i * 2);
      int32_t iCompare = csValue.Compare(csName);
      if (iCompare <= 0) {
        if (ppFind != NULL) {
          *ppFind = pNames;
        }
        if (iCompare < 0) {
          continue;
        }
      } else {
        break;
      }
      nIndex += i;
      return pNames->GetElementValue(i * 2 + 1);
    }
    nIndex += dwCount;
    return NULL;
  }
  CPDF_Array* pKids = pNode->GetArray(FX_BSTRC("Kids"));
  if (pKids == NULL) {
    return NULL;
  }
  for (FX_DWORD i = 0; i < pKids->GetCount(); i++) {
    CPDF_Dictionary* pKid = pKids->GetDict(i);
    if (pKid == NULL) {
      continue;
    }
    CPDF_Object* pFound =
        SearchNameNode(pKid, csName, nIndex, ppFind, nLevel + 1);
    if (pFound) {
      return pFound;
    }
  }
  return NULL;
}
static CPDF_Object* SearchNameNode(CPDF_Dictionary* pNode,
                                   int nIndex,
                                   int& nCurIndex,
                                   CFX_ByteString& csName,
                                   CPDF_Array** ppFind,
                                   int nLevel = 0) {
  if (nLevel > nMaxRecursion) {
    return NULL;
  }
  CPDF_Array* pNames = pNode->GetArray(FX_BSTRC("Names"));
  if (pNames) {
    int nCount = pNames->GetCount() / 2;
    if (nIndex >= nCurIndex + nCount) {
      nCurIndex += nCount;
      return NULL;
    }
    if (ppFind != NULL) {
      *ppFind = pNames;
    }
    csName = pNames->GetString((nIndex - nCurIndex) * 2);
    return pNames->GetElementValue((nIndex - nCurIndex) * 2 + 1);
  }
  CPDF_Array* pKids = pNode->GetArray(FX_BSTRC("Kids"));
  if (pKids == NULL) {
    return NULL;
  }
  for (FX_DWORD i = 0; i < pKids->GetCount(); i++) {
    CPDF_Dictionary* pKid = pKids->GetDict(i);
    if (pKid == NULL) {
      continue;
    }
    CPDF_Object* pFound =
        SearchNameNode(pKid, nIndex, nCurIndex, csName, ppFind, nLevel + 1);
    if (pFound) {
      return pFound;
    }
  }
  return NULL;
}
static int CountNames(CPDF_Dictionary* pNode, int nLevel = 0) {
  if (nLevel > nMaxRecursion) {
    return 0;
  }
  CPDF_Array* pNames = pNode->GetArray(FX_BSTRC("Names"));
  if (pNames) {
    return pNames->GetCount() / 2;
  }
  CPDF_Array* pKids = pNode->GetArray(FX_BSTRC("Kids"));
  if (pKids == NULL) {
    return 0;
  }
  int nCount = 0;
  for (FX_DWORD i = 0; i < pKids->GetCount(); i++) {
    CPDF_Dictionary* pKid = pKids->GetDict(i);
    if (pKid == NULL) {
      continue;
    }
    nCount += CountNames(pKid, nLevel + 1);
  }
  return nCount;
}
int CPDF_NameTree::GetCount() const {
  if (m_pRoot == NULL) {
    return 0;
  }
  return ::CountNames(m_pRoot);
}
int CPDF_NameTree::GetIndex(const CFX_ByteString& csName) const {
  if (m_pRoot == NULL) {
    return -1;
  }
  int nIndex = 0;
  if (SearchNameNode(m_pRoot, csName, nIndex, NULL) == NULL) {
    return -1;
  }
  return nIndex;
}
CPDF_Object* CPDF_NameTree::LookupValue(int nIndex,
                                        CFX_ByteString& csName) const {
  if (m_pRoot == NULL) {
    return NULL;
  }
  int nCurIndex = 0;
  return SearchNameNode(m_pRoot, nIndex, nCurIndex, csName, NULL);
}
CPDF_Object* CPDF_NameTree::LookupValue(const CFX_ByteString& csName) const {
  if (m_pRoot == NULL) {
    return NULL;
  }
  int nIndex = 0;
  return SearchNameNode(m_pRoot, csName, nIndex, NULL);
}
CPDF_Array* CPDF_NameTree::LookupNamedDest(CPDF_Document* pDoc,
                                           const CFX_ByteStringC& sName) {
  CPDF_Object* pValue = LookupValue(sName);
  if (pValue == NULL) {
    CPDF_Dictionary* pDests = pDoc->GetRoot()->GetDict(FX_BSTRC("Dests"));
    if (pDests == NULL) {
      return NULL;
    }
    pValue = pDests->GetElementValue(sName);
  }
  if (pValue == NULL) {
    return NULL;
  }
  if (pValue->GetType() == PDFOBJ_ARRAY) {
    return (CPDF_Array*)pValue;
  }
  if (pValue->GetType() == PDFOBJ_DICTIONARY) {
    return ((CPDF_Dictionary*)pValue)->GetArray(FX_BSTRC("D"));
  }
  return NULL;
}
#if _FXM_PLATFORM_ == _FXM_PLATFORM_APPLE_ || \
    _FXM_PLATFORM_ == _FXM_PLATFORM_WINDOWS_
static CFX_WideString ChangeSlashToPlatform(const FX_WCHAR* str) {
  CFX_WideString result;
  while (*str) {
    if (*str == '/') {
#if _FXM_PLATFORM_ == _FXM_PLATFORM_APPLE_
      result += ':';
#else
      result += '\\';
#endif
    } else {
      result += *str;
    }
    str++;
  }
  return result;
}
static CFX_WideString ChangeSlashToPDF(const FX_WCHAR* str) {
  CFX_WideString result;
  while (*str) {
    if (*str == '\\' || *str == ':') {
      result += '/';
    } else {
      result += *str;
    }
    str++;
  }
  return result;
}
#endif
static CFX_WideString FILESPEC_DecodeFileName(const CFX_WideStringC& filepath) {
  if (filepath.GetLength() <= 1) {
    return CFX_WideString();
  }
#if _FXM_PLATFORM_ == _FXM_PLATFORM_APPLE_
  if (filepath.Left(sizeof("/Mac") - 1) == CFX_WideStringC(L"/Mac")) {
    return ChangeSlashToPlatform(filepath.GetPtr() + 1);
  }
  return ChangeSlashToPlatform(filepath.GetPtr());
#elif _FXM_PLATFORM_ == _FXM_PLATFORM_WINDOWS_
  if (filepath.GetAt(0) != '/') {
    return ChangeSlashToPlatform(filepath.GetPtr());
  }
  if (filepath.GetAt(1) == '/') {
    return ChangeSlashToPlatform(filepath.GetPtr() + 1);
  }
  if (filepath.GetAt(2) == '/') {
    CFX_WideString result;
    result += filepath.GetAt(1);
    result += ':';
    result += ChangeSlashToPlatform(filepath.GetPtr() + 2);
    return result;
  }
  CFX_WideString result;
  result += '\\';
  result += ChangeSlashToPlatform(filepath.GetPtr());
  return result;
#else
  return filepath;
#endif
}
FX_BOOL CPDF_FileSpec::GetFileName(CFX_WideString& csFileName) const {
  if (m_pObj == NULL) {
    return FALSE;
  }
  if (m_pObj->GetType() == PDFOBJ_DICTIONARY) {
    CPDF_Dictionary* pDict = (CPDF_Dictionary*)m_pObj;
    csFileName = pDict->GetUnicodeText(FX_BSTRC("UF"));
    if (csFileName.IsEmpty()) {
      csFileName = CFX_WideString::FromLocal(pDict->GetString(FX_BSTRC("F")));
    }
    if (pDict->GetString(FX_BSTRC("FS")) == FX_BSTRC("URL")) {
      return TRUE;
    }
    if (csFileName.IsEmpty()) {
      if (pDict->KeyExist(FX_BSTRC("DOS"))) {
        csFileName =
            CFX_WideString::FromLocal(pDict->GetString(FX_BSTRC("DOS")));
      } else if (pDict->KeyExist(FX_BSTRC("Mac"))) {
        csFileName =
            CFX_WideString::FromLocal(pDict->GetString(FX_BSTRC("Mac")));
      } else if (pDict->KeyExist(FX_BSTRC("Unix"))) {
        csFileName =
            CFX_WideString::FromLocal(pDict->GetString(FX_BSTRC("Unix")));
      } else {
        return FALSE;
      }
    }
  } else {
    csFileName = CFX_WideString::FromLocal(m_pObj->GetString());
  }
  csFileName = FILESPEC_DecodeFileName(csFileName);
  return TRUE;
}
CPDF_FileSpec::CPDF_FileSpec() {
  m_pObj = CPDF_Dictionary::Create();
  if (m_pObj != NULL) {
    ((CPDF_Dictionary*)m_pObj)
        ->SetAtName(FX_BSTRC("Type"), FX_BSTRC("Filespec"));
  }
}
FX_BOOL CPDF_FileSpec::IsURL() const {
  if (m_pObj == NULL) {
    return FALSE;
  }
  if (m_pObj->GetType() != PDFOBJ_DICTIONARY) {
    return FALSE;
  }
  return ((CPDF_Dictionary*)m_pObj)->GetString(FX_BSTRC("FS")) ==
         FX_BSTRC("URL");
}
CFX_WideString FILESPEC_EncodeFileName(const CFX_WideStringC& filepath) {
  if (filepath.GetLength() <= 1) {
    return CFX_WideString();
  }
#if _FXM_PLATFORM_ == _FXM_PLATFORM_WINDOWS_
  if (filepath.GetAt(1) == ':') {
    CFX_WideString result;
    result = '/';
    result += filepath.GetAt(0);
    if (filepath.GetAt(2) != '\\') {
      result += '/';
    }
    result += ChangeSlashToPDF(filepath.GetPtr() + 2);
    return result;
  }
  if (filepath.GetAt(0) == '\\' && filepath.GetAt(1) == '\\') {
    return ChangeSlashToPDF(filepath.GetPtr() + 1);
  }
  if (filepath.GetAt(0) == '\\') {
    CFX_WideString result;
    result = '/';
    result += ChangeSlashToPDF(filepath.GetPtr());
    return result;
  }
  return ChangeSlashToPDF(filepath.GetPtr());
#elif _FXM_PLATFORM_ == _FXM_PLATFORM_APPLE_
  if (filepath.Left(sizeof("Mac") - 1) == FX_WSTRC(L"Mac")) {
    CFX_WideString result;
    result = '/';
    result += ChangeSlashToPDF(filepath.GetPtr());
    return result;
  }
  return ChangeSlashToPDF(filepath.GetPtr());
#else
  return filepath;
#endif
}
CPDF_Stream* CPDF_FileSpec::GetFileStream() const {
  if (m_pObj == NULL) {
    return NULL;
  }
  int32_t iType = m_pObj->GetType();
  if (iType == PDFOBJ_STREAM) {
    return (CPDF_Stream*)m_pObj;
  } else if (iType == PDFOBJ_DICTIONARY) {
    CPDF_Dictionary* pEF = ((CPDF_Dictionary*)m_pObj)->GetDict(FX_BSTRC("EF"));
    if (pEF == NULL) {
      return NULL;
    }
    return pEF->GetStream(FX_BSTRC("F"));
  }
  return NULL;
}
static void FPDFDOC_FILESPEC_SetFileName(CPDF_Object* pObj,
                                         const CFX_WideStringC& wsFileName,
                                         FX_BOOL bURL) {
  ASSERT(pObj != NULL);
  CFX_WideString wsStr;
  if (bURL) {
    wsStr = wsFileName;
  } else {
    wsStr = FILESPEC_EncodeFileName(wsFileName);
  }
  int32_t iType = pObj->GetType();
  if (iType == PDFOBJ_STRING) {
    pObj->SetString(CFX_ByteString::FromUnicode(wsStr));
  } else if (iType == PDFOBJ_DICTIONARY) {
    CPDF_Dictionary* pDict = (CPDF_Dictionary*)pObj;
    pDict->SetAtString(FX_BSTRC("F"), CFX_ByteString::FromUnicode(wsStr));
    pDict->SetAtString(FX_BSTRC("UF"), PDF_EncodeText(wsStr));
  }
}
void CPDF_FileSpec::SetFileName(const CFX_WideStringC& wsFileName,
                                FX_BOOL bURL) {
  ASSERT(m_pObj != NULL);
  if (m_pObj->GetType() == PDFOBJ_DICTIONARY && bURL) {
    ((CPDF_Dictionary*)m_pObj)->SetAtName(FX_BSTRC("FS"), "URL");
  }
  FPDFDOC_FILESPEC_SetFileName(m_pObj, wsFileName, bURL);
}
static CFX_WideString _MakeRoman(int num) {
  const int arabic[] = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
  const CFX_WideString roman[] = {L"m",  L"cm", L"d",  L"cd", L"c",
                                  L"xc", L"l",  L"xl", L"x",  L"ix",
                                  L"v",  L"iv", L"i"};
  const int nMaxNum = 1000000;
  num %= nMaxNum;
  int i = 0;
  CFX_WideString wsRomanNumber;
  while (num > 0) {
    while (num >= arabic[i]) {
      num = num - arabic[i];
      wsRomanNumber += roman[i];
    }
    i = i + 1;
  }
  return wsRomanNumber;
}
static CFX_WideString _MakeLetters(int num) {
  if (num == 0) {
    return CFX_WideString();
  }
  CFX_WideString wsLetters;
  const int nMaxCount = 1000;
  const int nLetterCount = 26;
  num -= 1;
  int count = num / nLetterCount + 1;
  count %= nMaxCount;
  FX_WCHAR ch = L'a' + num % nLetterCount;
  for (int i = 0; i < count; i++) {
    wsLetters += ch;
  }
  return wsLetters;
}
static CFX_WideString _GetLabelNumPortion(int num,
                                          const CFX_ByteString& bsStyle) {
  CFX_WideString wsNumPortion;
  if (bsStyle.IsEmpty()) {
    return wsNumPortion;
  }
  if (bsStyle == "D") {
    wsNumPortion.Format(L"%d", num);
  } else if (bsStyle == "R") {
    wsNumPortion = _MakeRoman(num);
    wsNumPortion.MakeUpper();
  } else if (bsStyle == "r") {
    wsNumPortion = _MakeRoman(num);
  } else if (bsStyle == "A") {
    wsNumPortion = _MakeLetters(num);
    wsNumPortion.MakeUpper();
  } else if (bsStyle == "a") {
    wsNumPortion = _MakeLetters(num);
  }
  return wsNumPortion;
}
CFX_WideString CPDF_PageLabel::GetLabel(int nPage) const {
  CFX_WideString wsLabel;
  if (m_pDocument == NULL) {
    return wsLabel;
  }
  CPDF_Dictionary* pPDFRoot = m_pDocument->GetRoot();
  if (pPDFRoot == NULL) {
    return wsLabel;
  }
  CPDF_Dictionary* pLabels = pPDFRoot->GetDict(FX_BSTRC("PageLabels"));
  CPDF_NumberTree numberTree(pLabels);
  CPDF_Object* pValue = NULL;
  int n = nPage;
  while (n >= 0) {
    pValue = numberTree.LookupValue(n);
    if (pValue != NULL) {
      break;
    }
    n--;
  }
  if (pValue != NULL) {
    pValue = pValue->GetDirect();
    if (pValue->GetType() == PDFOBJ_DICTIONARY) {
      CPDF_Dictionary* pLabel = (CPDF_Dictionary*)pValue;
      if (pLabel->KeyExist(FX_BSTRC("P"))) {
        wsLabel += pLabel->GetUnicodeText(FX_BSTRC("P"));
      }
      CFX_ByteString bsNumberingStyle = pLabel->GetString(FX_BSTRC("S"), NULL);
      int nLabelNum = nPage - n + pLabel->GetInteger(FX_BSTRC("St"), 1);
      CFX_WideString wsNumPortion =
          _GetLabelNumPortion(nLabelNum, bsNumberingStyle);
      wsLabel += wsNumPortion;
      return wsLabel;
    }
  }
  wsLabel.Format(L"%d", nPage + 1);
  return wsLabel;
}
int32_t CPDF_PageLabel::GetPageByLabel(const CFX_ByteStringC& bsLabel) const {
  if (m_pDocument == NULL) {
    return -1;
  }
  CPDF_Dictionary* pPDFRoot = m_pDocument->GetRoot();
  if (pPDFRoot == NULL) {
    return -1;
  }
  int nPages = m_pDocument->GetPageCount();
  CFX_ByteString bsLbl;
  CFX_ByteString bsOrig = bsLabel;
  for (int i = 0; i < nPages; i++) {
    bsLbl = PDF_EncodeText(GetLabel(i));
    if (!bsLbl.Compare(bsOrig)) {
      return i;
    }
  }
  bsLbl = bsOrig;
  int nPage = FXSYS_atoi(bsLbl);
  if (nPage > 0 && nPage <= nPages) {
    return nPage;
  }
  return -1;
}
int32_t CPDF_PageLabel::GetPageByLabel(const CFX_WideStringC& wsLabel) const {
  CFX_ByteString bsLabel = PDF_EncodeText(wsLabel.GetPtr());
  return GetPageByLabel(bsLabel);
}