// 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_edit.h"
#include "../../public/fpdf_formfill.h"
#include "../../public/fpdf_save.h"
#include "../include/fsdk_define.h"
#include "../include/fpdfxfa/fpdfxfa_doc.h"
#include "../include/fpdfxfa/fpdfxfa_app.h"
#include "../include/fpdfxfa/fpdfxfa_util.h"

#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 = NULL;
}

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;
}

#define XFA_DATASETS 0
#define XFA_FORMS 1

FX_BOOL _SaveXFADocumentData(CPDFXFA_Document* pDocument,
                             CFX_PtrArray& fileList) {
#ifdef PDF_ENABLE_XFA
  if (!pDocument)
    return FALSE;
  if (pDocument->GetDocType() != DOCTYPE_DYNIMIC_XFA &&
      pDocument->GetDocType() != DOCTYPE_STATIC_XFA)
    return TRUE;
  if (!CPDFXFA_App::GetInstance()->GetXFAApp())
    return TRUE;

  IXFA_DocView* pXFADocView = pDocument->GetXFADocView();
  if (NULL == pXFADocView)
    return TRUE;

  IXFA_DocHandler* pXFADocHandler =
      CPDFXFA_App::GetInstance()->GetXFAApp()->GetDocHandler();
  CPDF_Document* pPDFDocument = pDocument->GetPDFDoc();
  if (pDocument == NULL)
    return FALSE;

  CPDF_Dictionary* pRoot = pPDFDocument->GetRoot();
  if (pRoot == NULL)
    return FALSE;
  CPDF_Dictionary* pAcroForm = pRoot->GetDict("AcroForm");
  if (NULL == pAcroForm)
    return FALSE;
  CPDF_Object* pXFA = pAcroForm->GetElement("XFA");
  if (pXFA == NULL)
    return TRUE;
  if (pXFA->GetType() != PDFOBJ_ARRAY)
    return FALSE;
  CPDF_Array* pArray = pXFA->GetArray();
  if (NULL == 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->GetElement(i);
    if (pPDFObj->GetType() != PDFOBJ_STRING)
      continue;
    if (pPDFObj->GetString() == "form")
      iFormIndex = i + 1;
    else if (pPDFObj->GetString() == "datasets")
      iDataSetsIndex = i + 1;
    else if (pPDFObj->GetString() == FX_BSTRC("template"))
      iTemplate = i + 1;
  }
  IXFA_ChecksumContext* pContext = NULL;
#define XFA_USECKSUM
#ifdef XFA_USECKSUM
  // Checksum
  pContext = XFA_Checksum_Create();
  FXSYS_assert(pContext);
  pContext->StartChecksum();

  // template
  if (iTemplate > -1) {
    CPDF_Stream* pTemplateStream = pArray->GetStream(iTemplate);
    CPDF_StreamAcc streamAcc;
    streamAcc.LoadAllData(pTemplateStream);
    uint8_t* pData = (uint8_t*)streamAcc.GetData();
    FX_DWORD dwSize2 = streamAcc.GetSize();
    IFX_FileStream* pTemplate = FX_CreateMemoryStream(pData, dwSize2);
    pContext->UpdateChecksum((IFX_FileRead*)pTemplate);
    pTemplate->Release();
  }
#endif
  CPDF_Stream* pFormStream = NULL;
  CPDF_Stream* pDataSetsStream = NULL;
  if (iFormIndex != -1) {
    // Get form CPDF_Stream
    CPDF_Object* pFormPDFObj = pArray->GetElement(iFormIndex);
    if (pFormPDFObj->GetType() == PDFOBJ_REFERENCE) {
      CPDF_Reference* pFormRefObj = (CPDF_Reference*)pFormPDFObj;
      CPDF_Object* pFormDircetObj = pFormPDFObj->GetDirect();
      if (NULL != pFormDircetObj &&
          pFormDircetObj->GetType() == PDFOBJ_STREAM) {
        pFormStream = (CPDF_Stream*)pFormDircetObj;
      }
    } else if (pFormPDFObj->GetType() == PDFOBJ_STREAM) {
      pFormStream = (CPDF_Stream*)pFormPDFObj;
    }
  }

  if (iDataSetsIndex != -1) {
    // Get datasets CPDF_Stream
    CPDF_Object* pDataSetsPDFObj = pArray->GetElement(iDataSetsIndex);
    if (pDataSetsPDFObj->GetType() == PDFOBJ_REFERENCE) {
      CPDF_Reference* pDataSetsRefObj = (CPDF_Reference*)pDataSetsPDFObj;
      CPDF_Object* pDataSetsDircetObj = pDataSetsRefObj->GetDirect();
      if (NULL != pDataSetsDircetObj &&
          pDataSetsDircetObj->GetType() == PDFOBJ_STREAM) {
        pDataSetsStream = (CPDF_Stream*)pDataSetsDircetObj;
      }
    } else if (pDataSetsPDFObj->GetType() == PDFOBJ_STREAM) {
      pDataSetsStream = (CPDF_Stream*)pDataSetsPDFObj;
    }
  }
  // end
  // L"datasets"
  {
    IFX_FileStream* pDsfileWrite = FX_CreateMemoryStream();
    if (NULL == pDsfileWrite) {
      pContext->Release();
      pDsfileWrite->Release();
      return FALSE;
    }
    if (pXFADocHandler->SavePackage(pXFADocView->GetDoc(),
                                    CFX_WideStringC(L"datasets"),
                                    pDsfileWrite) &&
        pDsfileWrite->GetSize() > 0) {
#ifdef XFA_USECKSUM
      // Datasets
      pContext->UpdateChecksum((IFX_FileRead*)pDsfileWrite);
      pContext->FinishChecksum();
#endif
      CPDF_Dictionary* pDataDict = new CPDF_Dictionary;
      if (iDataSetsIndex != -1) {
        if (pDataSetsStream)
          pDataSetsStream->InitStream(pDsfileWrite, pDataDict);
      } else {
        CPDF_Stream* pData = new CPDF_Stream(NULL, 0, NULL);
        pData->InitStream(pDsfileWrite, pDataDict);
        FX_DWORD AppStreamobjnum = pPDFDocument->AddIndirectObject(pData);
        CPDF_Reference* pRef =
            (CPDF_Reference*)pPDFDocument->GetIndirectObject(AppStreamobjnum);
        {
          iLast = pArray->GetCount() - 2;
          pArray->InsertAt(iLast, CPDF_String::Create("datasets"));
          pArray->InsertAt(iLast + 1, pData, pPDFDocument);
        }
      }
      fileList.Add(pDsfileWrite);
    }
  }

  // L"form"
  {
    IFX_FileStream* pfileWrite = FX_CreateMemoryStream();
    if (NULL == pfileWrite) {
      pContext->Release();
      return FALSE;
    }
    if (pXFADocHandler->SavePackage(pXFADocView->GetDoc(),
                                    CFX_WideStringC(L"form"), pfileWrite,
                                    pContext) &&
        pfileWrite > 0) {
      CPDF_Dictionary* pDataDict = new CPDF_Dictionary;
      if (iFormIndex != -1) {
        if (pFormStream)
          pFormStream->InitStream(pfileWrite, pDataDict);
      } else {
        CPDF_Stream* pData = new CPDF_Stream(NULL, 0, NULL);
        pData->InitStream(pfileWrite, pDataDict);
        FX_DWORD AppStreamobjnum = pPDFDocument->AddIndirectObject(pData);
        CPDF_Reference* pRef =
            (CPDF_Reference*)pPDFDocument->GetIndirectObject(AppStreamobjnum);
        {
          iLast = pArray->GetCount() - 2;
          pArray->InsertAt(iLast, CPDF_String::Create("form"));
          pArray->InsertAt(iLast + 1, pData, pPDFDocument);
        }
      }
      fileList.Add(pfileWrite);
    }
  }
  pContext->Release();
#endif  // PDF_ENABLE_XFA

  return TRUE;
}

FX_BOOL _SendPostSaveToXFADoc(CPDFXFA_Document* pDocument) {
  if (!pDocument)
    return FALSE;

  if (pDocument->GetDocType() != DOCTYPE_DYNIMIC_XFA &&
      pDocument->GetDocType() != DOCTYPE_STATIC_XFA)
    return TRUE;

  IXFA_DocView* pXFADocView = pDocument->GetXFADocView();
  if (NULL == pXFADocView)
    return FALSE;
  IXFA_WidgetHandler* pWidgetHander = pXFADocView->GetWidgetHandler();

  CXFA_WidgetAcc* pWidgetAcc = NULL;
  IXFA_WidgetAccIterator* pWidgetAccIterator =
      pXFADocView->CreateWidgetAccIterator();
  pWidgetAcc = pWidgetAccIterator->MoveToNext();
  while (pWidgetAcc) {
    CXFA_EventParam preParam;
    preParam.m_eType = XFA_EVENT_PostSave;
    pWidgetHander->ProcessEvent(pWidgetAcc, &preParam);
    pWidgetAcc = pWidgetAccIterator->MoveToNext();
  }
  pWidgetAccIterator->Release();
  pXFADocView->UpdateDocView();
  pDocument->_ClearChangeMark();
  return TRUE;
}

FX_BOOL _SendPreSaveToXFADoc(CPDFXFA_Document* pDocument,
                             CFX_PtrArray& fileList) {
  if (pDocument->GetDocType() != DOCTYPE_DYNIMIC_XFA &&
      pDocument->GetDocType() != DOCTYPE_STATIC_XFA)
    return TRUE;
  IXFA_DocView* pXFADocView = pDocument->GetXFADocView();
  if (NULL == pXFADocView)
    return TRUE;
  IXFA_WidgetHandler* pWidgetHander = pXFADocView->GetWidgetHandler();
  CXFA_WidgetAcc* pWidgetAcc = NULL;
  IXFA_WidgetAccIterator* pWidgetAccIterator =
      pXFADocView->CreateWidgetAccIterator();
  pWidgetAcc = pWidgetAccIterator->MoveToNext();
  while (pWidgetAcc) {
    CXFA_EventParam preParam;
    preParam.m_eType = XFA_EVENT_PreSave;
    pWidgetHander->ProcessEvent(pWidgetAcc, &preParam);
    pWidgetAcc = pWidgetAccIterator->MoveToNext();
  }
  pWidgetAccIterator->Release();
  pXFADocView->UpdateDocView();
  return _SaveXFADocumentData(pDocument, fileList);
}

FPDF_BOOL _FPDF_Doc_Save(FPDF_DOCUMENT document,
                         FPDF_FILEWRITE* pFileWrite,
                         FPDF_DWORD flags,
                         FPDF_BOOL bSetVersion,
                         int fileVerion) {
  CPDFXFA_Document* pDoc = (CPDFXFA_Document*)document;

  CFX_PtrArray fileList;

  _SendPreSaveToXFADoc(pDoc, fileList);

  CPDF_Document* pPDFDoc = pDoc->GetPDFDoc();
  if (!pPDFDoc)
    return 0;

  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 = NULL;
  FX_BOOL bRet;
  pStreamWrite = new CFX_IFileWrite;
  pStreamWrite->Init(pFileWrite);
  bRet = FileMaker.Create(pStreamWrite, flags);

  _SendPostSaveToXFADoc(pDoc);

  for (int i = 0; i < fileList.GetSize(); i++) {
    IFX_FileStream* pFile = (IFX_FileStream*)fileList.GetAt(i);
    pFile->Release();
  }
  fileList.RemoveAll();

  pStreamWrite->Release();
  return bRet;
}

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);
}