// 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 "public/fpdf_save.h"

#include <vector>

#include "core/fpdfapi/fpdf_edit/include/cpdf_creator.h"
#include "core/fpdfapi/fpdf_parser/include/cpdf_array.h"
#include "core/fpdfapi/fpdf_parser/include/cpdf_document.h"
#include "core/fpdfapi/fpdf_parser/include/cpdf_reference.h"
#include "core/fpdfapi/fpdf_parser/include/cpdf_stream_acc.h"
#include "core/fpdfapi/fpdf_parser/include/cpdf_string.h"
#include "core/fxcrt/include/fx_ext.h"
#include "fpdfsdk/include/fsdk_define.h"
#include "public/fpdf_edit.h"

#ifdef PDF_ENABLE_XFA
#include "fpdfsdk/fpdfxfa/include/fpdfxfa_app.h"
#include "fpdfsdk/fpdfxfa/include/fpdfxfa_doc.h"
#include "fpdfsdk/fpdfxfa/include/fpdfxfa_util.h"
#include "public/fpdf_formfill.h"
#include "xfa/fxfa/include/cxfa_eventparam.h"
#include "xfa/fxfa/include/xfa_checksum.h"
#include "xfa/fxfa/include/xfa_ffapp.h"
#include "xfa/fxfa/include/xfa_ffdocview.h"
#include "xfa/fxfa/include/xfa_ffwidgethandler.h"
#endif

#if _FX_OS_ == _FX_ANDROID_
#include "time.h"
#else
#include <ctime>
#endif

class CFX_IFileWrite final : public IFX_StreamWrite {
 public:
  CFX_IFileWrite();
  FX_BOOL Init(FPDF_FILEWRITE* pFileWriteStruct);
  FX_BOOL WriteBlock(const void* pData, size_t size) override;
  void Release() override;

 protected:
  ~CFX_IFileWrite() override {}

  FPDF_FILEWRITE* m_pFileWriteStruct;
};

CFX_IFileWrite::CFX_IFileWrite() {
  m_pFileWriteStruct = nullptr;
}

FX_BOOL CFX_IFileWrite::Init(FPDF_FILEWRITE* pFileWriteStruct) {
  if (!pFileWriteStruct)
    return FALSE;

  m_pFileWriteStruct = pFileWriteStruct;
  return TRUE;
}

FX_BOOL CFX_IFileWrite::WriteBlock(const void* pData, size_t size) {
  if (!m_pFileWriteStruct)
    return FALSE;

  m_pFileWriteStruct->WriteBlock(m_pFileWriteStruct, pData, size);
  return TRUE;
}

void CFX_IFileWrite::Release() {
  delete this;
}

namespace {

#ifdef PDF_ENABLE_XFA
bool SaveXFADocumentData(CPDFXFA_Document* pDocument,
                         std::vector<ScopedFileStream>* fileList) {
  if (!pDocument)
    return false;

  if (pDocument->GetDocType() != DOCTYPE_DYNAMIC_XFA &&
      pDocument->GetDocType() != DOCTYPE_STATIC_XFA)
    return true;

  if (!CPDFXFA_App::GetInstance()->GetXFAApp())
    return true;

  CXFA_FFDocView* pXFADocView = pDocument->GetXFADocView();
  if (!pXFADocView)
    return true;

  CPDF_Document* pPDFDocument = pDocument->GetPDFDoc();
  if (!pDocument)
    return false;

  CPDF_Dictionary* pRoot = pPDFDocument->GetRoot();
  if (!pRoot)
    return false;

  CPDF_Dictionary* pAcroForm = pRoot->GetDictBy("AcroForm");
  if (!pAcroForm)
    return false;

  CPDF_Object* pXFA = pAcroForm->GetObjectBy("XFA");
  if (!pXFA)
    return true;

  CPDF_Array* pArray = pXFA->AsArray();
  if (!pArray)
    return false;

  int size = pArray->GetCount();
  int iFormIndex = -1;
  int iDataSetsIndex = -1;
  int iTemplate = -1;
  int iLast = size - 2;
  for (int i = 0; i < size - 1; i++) {
    CPDF_Object* pPDFObj = pArray->GetObjectAt(i);
    if (!pPDFObj->IsString())
      continue;
    if (pPDFObj->GetString() == "form")
      iFormIndex = i + 1;
    else if (pPDFObj->GetString() == "datasets")
      iDataSetsIndex = i + 1;
    else if (pPDFObj->GetString() == "template")
      iTemplate = i + 1;
  }
  std::unique_ptr<CXFA_ChecksumContext> pContext(new CXFA_ChecksumContext);
  pContext->StartChecksum();

  // template
  if (iTemplate > -1) {
    CPDF_Stream* pTemplateStream = pArray->GetStreamAt(iTemplate);
    CPDF_StreamAcc streamAcc;
    streamAcc.LoadAllData(pTemplateStream);
    uint8_t* pData = (uint8_t*)streamAcc.GetData();
    uint32_t dwSize2 = streamAcc.GetSize();
    ScopedFileStream pTemplate(FX_CreateMemoryStream(pData, dwSize2));
    pContext->UpdateChecksum(pTemplate.get());
  }
  CPDF_Stream* pFormStream = nullptr;
  CPDF_Stream* pDataSetsStream = nullptr;
  if (iFormIndex != -1) {
    // Get form CPDF_Stream
    CPDF_Object* pFormPDFObj = pArray->GetObjectAt(iFormIndex);
    if (pFormPDFObj->IsReference()) {
      CPDF_Object* pFormDirectObj = pFormPDFObj->GetDirect();
      if (pFormDirectObj && pFormDirectObj->IsStream()) {
        pFormStream = (CPDF_Stream*)pFormDirectObj;
      }
    } else if (pFormPDFObj->IsStream()) {
      pFormStream = (CPDF_Stream*)pFormPDFObj;
    }
  }

  if (iDataSetsIndex != -1) {
    // Get datasets CPDF_Stream
    CPDF_Object* pDataSetsPDFObj = pArray->GetObjectAt(iDataSetsIndex);
    if (pDataSetsPDFObj->IsReference()) {
      CPDF_Reference* pDataSetsRefObj = (CPDF_Reference*)pDataSetsPDFObj;
      CPDF_Object* pDataSetsDirectObj = pDataSetsRefObj->GetDirect();
      if (pDataSetsDirectObj && pDataSetsDirectObj->IsStream()) {
        pDataSetsStream = (CPDF_Stream*)pDataSetsDirectObj;
      }
    } else if (pDataSetsPDFObj->IsStream()) {
      pDataSetsStream = (CPDF_Stream*)pDataSetsPDFObj;
    }
  }
  // L"datasets"
  {
    ScopedFileStream pDsfileWrite(FX_CreateMemoryStream());
    if (pXFADocView->GetDoc()->SavePackage(XFA_HASHCODE_Datasets,
                                           pDsfileWrite.get(), nullptr) &&
        pDsfileWrite->GetSize() > 0) {
      // Datasets
      pContext->UpdateChecksum(pDsfileWrite.get());
      pContext->FinishChecksum();
      CPDF_Dictionary* pDataDict = new CPDF_Dictionary;
      if (iDataSetsIndex != -1) {
        if (pDataSetsStream)
          pDataSetsStream->InitStreamFromFile(pDsfileWrite.get(), pDataDict);
      } else {
        CPDF_Stream* pData = new CPDF_Stream(nullptr, 0, nullptr);
        pData->InitStreamFromFile(pDsfileWrite.get(), pDataDict);
        pPDFDocument->AddIndirectObject(pData);
        iLast = pArray->GetCount() - 2;
        pArray->InsertAt(iLast, new CPDF_String("datasets", FALSE));
        pArray->InsertAt(iLast + 1, pData, pPDFDocument);
      }
      fileList->push_back(std::move(pDsfileWrite));
    }
  }
  // L"form"
  {
    ScopedFileStream pfileWrite(FX_CreateMemoryStream());
    if (pXFADocView->GetDoc()->SavePackage(XFA_HASHCODE_Form, pfileWrite.get(),
                                           pContext.get()) &&
        pfileWrite->GetSize() > 0) {
      CPDF_Dictionary* pDataDict = new CPDF_Dictionary;
      if (iFormIndex != -1) {
        if (pFormStream)
          pFormStream->InitStreamFromFile(pfileWrite.get(), pDataDict);
      } else {
        CPDF_Stream* pData = new CPDF_Stream(nullptr, 0, nullptr);
        pData->InitStreamFromFile(pfileWrite.get(), pDataDict);
        pPDFDocument->AddIndirectObject(pData);
        iLast = pArray->GetCount() - 2;
        pArray->InsertAt(iLast, new CPDF_String("form", FALSE));
        pArray->InsertAt(iLast + 1, pData, pPDFDocument);
      }
      fileList->push_back(std::move(pfileWrite));
    }
  }
  return true;
}

bool SendPostSaveToXFADoc(CPDFXFA_Document* pDocument) {
  if (!pDocument)
    return false;

  if (pDocument->GetDocType() != DOCTYPE_DYNAMIC_XFA &&
      pDocument->GetDocType() != DOCTYPE_STATIC_XFA)
    return true;

  CXFA_FFDocView* pXFADocView = pDocument->GetXFADocView();
  if (!pXFADocView)
    return false;

  CXFA_FFWidgetHandler* pWidgetHander = pXFADocView->GetWidgetHandler();
  std::unique_ptr<CXFA_WidgetAccIterator> pWidgetAccIterator(
      pXFADocView->CreateWidgetAccIterator());
  while (CXFA_WidgetAcc* pWidgetAcc = pWidgetAccIterator->MoveToNext()) {
    CXFA_EventParam preParam;
    preParam.m_eType = XFA_EVENT_PostSave;
    pWidgetHander->ProcessEvent(pWidgetAcc, &preParam);
  }
  pXFADocView->UpdateDocView();
  pDocument->_ClearChangeMark();
  return true;
}

bool SendPreSaveToXFADoc(CPDFXFA_Document* pDocument,
                         std::vector<ScopedFileStream>* fileList) {
  if (pDocument->GetDocType() != DOCTYPE_DYNAMIC_XFA &&
      pDocument->GetDocType() != DOCTYPE_STATIC_XFA)
    return true;

  CXFA_FFDocView* pXFADocView = pDocument->GetXFADocView();
  if (!pXFADocView)
    return true;

  CXFA_FFWidgetHandler* pWidgetHander = pXFADocView->GetWidgetHandler();
  std::unique_ptr<CXFA_WidgetAccIterator> pWidgetAccIterator(
      pXFADocView->CreateWidgetAccIterator());
  while (CXFA_WidgetAcc* pWidgetAcc = pWidgetAccIterator->MoveToNext()) {
    CXFA_EventParam preParam;
    preParam.m_eType = XFA_EVENT_PreSave;
    pWidgetHander->ProcessEvent(pWidgetAcc, &preParam);
  }
  pXFADocView->UpdateDocView();
  return SaveXFADocumentData(pDocument, fileList);
}
#endif  // PDF_ENABLE_XFA

bool FPDF_Doc_Save(FPDF_DOCUMENT document,
                   FPDF_FILEWRITE* pFileWrite,
                   FPDF_DWORD flags,
                   FPDF_BOOL bSetVersion,
                   int fileVerion) {
  CPDF_Document* pPDFDoc = CPDFDocumentFromFPDFDocument(document);
  if (!pPDFDoc)
    return 0;

#ifdef PDF_ENABLE_XFA
  CPDFXFA_Document* pDoc = static_cast<CPDFXFA_Document*>(document);
  std::vector<ScopedFileStream> fileList;
  SendPreSaveToXFADoc(pDoc, &fileList);
#endif  // PDF_ENABLE_XFA

  if (flags < FPDF_INCREMENTAL || flags > FPDF_REMOVE_SECURITY)
    flags = 0;

  CPDF_Creator FileMaker(pPDFDoc);
  if (bSetVersion)
    FileMaker.SetFileVersion(fileVerion);
  if (flags == FPDF_REMOVE_SECURITY) {
    flags = 0;
    FileMaker.RemoveSecurity();
  }

  CFX_IFileWrite* pStreamWrite = new CFX_IFileWrite;
  pStreamWrite->Init(pFileWrite);
  bool bRet = FileMaker.Create(pStreamWrite, flags);
#ifdef PDF_ENABLE_XFA
  SendPostSaveToXFADoc(pDoc);
#endif  // PDF_ENABLE_XFA
  pStreamWrite->Release();
  return bRet;
}

}  // namespace

DLLEXPORT FPDF_BOOL STDCALL FPDF_SaveAsCopy(FPDF_DOCUMENT document,
                                            FPDF_FILEWRITE* pFileWrite,
                                            FPDF_DWORD flags) {
  return FPDF_Doc_Save(document, pFileWrite, flags, FALSE, 0);
}

DLLEXPORT FPDF_BOOL STDCALL FPDF_SaveWithVersion(FPDF_DOCUMENT document,
                                                 FPDF_FILEWRITE* pFileWrite,
                                                 FPDF_DWORD flags,
                                                 int fileVersion) {
  return FPDF_Doc_Save(document, pFileWrite, flags, TRUE, fileVersion);
}