// Copyright 2016 The PDFium Authors
// 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

#ifndef FPDFSDK_CPDFSDK_FORMFILLENVIRONMENT_H_
#define FPDFSDK_CPDFSDK_FORMFILLENVIRONMENT_H_

#include <stdint.h>

#include <map>
#include <memory>
#include <set>
#include <utility>
#include <vector>

#include "core/fpdfapi/page/cpdf_occontext.h"
#include "core/fpdfapi/parser/cpdf_document.h"
#include "core/fpdfdoc/cpdf_aaction.h"
#include "core/fxcrt/cfx_timer.h"
#include "core/fxcrt/mask.h"
#include "core/fxcrt/observed_ptr.h"
#include "core/fxcrt/retain_ptr.h"
#include "core/fxcrt/unowned_ptr.h"
#include "fpdfsdk/cpdfsdk_annot.h"
#include "fpdfsdk/formfiller/cffl_interactiveformfiller.h"
#include "fpdfsdk/pwl/cpwl_wnd.h"
#include "fpdfsdk/pwl/ipwl_fillernotify.h"
#include "public/fpdf_formfill.h"
#include "third_party/base/containers/span.h"

class CPDF_Action;
class CPDF_FormField;
class CPDFSDK_InteractiveForm;
class CPDFSDK_PageView;
class IJS_EventContext;
class IJS_Runtime;
class IPDF_Page;
struct CFFL_FieldAction;

// NOTE: |bsUTF16LE| must outlive the use of the result. Care must be taken
// since modifying the result would impact |bsUTF16LE|.
FPDF_WIDESTRING AsFPDFWideString(ByteString* bsUTF16LE);

// The CPDFSDK_FormFillEnvironment is "owned" by the embedder across the
// C API as a FPDF_FormHandle, and may pop out of existence at any time,
// so long as the associated embedder-owned FPDF_Document outlives it.
// Pointers from objects in the FPDF_Document ownership hierarchy should
// be ObservedPtr<> so as to clear themselves when the embedder "exits"
// the form fill environment.  Pointers from objects in this ownership
// heirarcy to objects in the FPDF_Document ownership hierarcy should be
// UnownedPtr<>, as should pointers from objects in this ownership
// hierarcy back to the form fill environment itself, so as to flag any
// lingering lifetime issues via the memory tools.

class CPDFSDK_FormFillEnvironment final
    : public CFX_Timer::HandlerIface,
      public CFFL_InteractiveFormFiller::CallbackIface {
 public:
  CPDFSDK_FormFillEnvironment(CPDF_Document* pDoc, FPDF_FORMFILLINFO* pFFinfo);

  ~CPDFSDK_FormFillEnvironment() override;

  // TimerHandlerIface:
  int32_t SetTimer(int32_t uElapse, TimerCallback lpTimerFunc) override;
  void KillTimer(int32_t nTimerID) override;

  // CFFL_InteractiveFormFiller::CallbackIface:
  void InvalidateRect(CPDFSDK_Widget* widget,
                      const CFX_FloatRect& rect) override;
  void OutputSelectedRect(CFFL_FormField* pFormField,
                          const CFX_FloatRect& rect) override;
  bool IsSelectionImplemented() const override;
  void SetCursor(IPWL_FillerNotify::CursorStyle nCursorType) override;
  void OnSetFieldInputFocus(const WideString& text) override;
  void OnCalculate(ObservedPtr<CPDFSDK_Annot>& pAnnot) override;
  void OnFormat(ObservedPtr<CPDFSDK_Annot>& pAnnot) override;
  void Invalidate(IPDF_Page* page, const FX_RECT& rect) override;
  CPDFSDK_PageView* GetOrCreatePageView(IPDF_Page* pUnderlyingPage) override;
  CPDFSDK_PageView* GetPageView(IPDF_Page* pUnderlyingPage) override;
  CFX_Timer::HandlerIface* GetTimerHandler() override;
  CPDFSDK_Annot* GetFocusAnnot() const override;
  bool SetFocusAnnot(ObservedPtr<CPDFSDK_Annot>& pAnnot) override;
  bool HasPermissions(uint32_t flags) const override;
  void OnChange() override;

  CPDFSDK_PageView* GetPageViewAtIndex(int nIndex);
  void RemovePageView(IPDF_Page* pUnderlyingPage);
  void UpdateAllViews(CPDFSDK_Annot* pAnnot);

  bool KillFocusAnnot(Mask<FWL_EVENTFLAG> nFlags);
  void ClearAllFocusedAnnots();

  int GetPageCount() const;

  bool GetChangeMark() const { return m_bChangeMask; }
  void SetChangeMark() { m_bChangeMask = true; }
  void ClearChangeMark() { m_bChangeMask = false; }

  void ProcJavascriptAction();
  bool ProcOpenAction();

  void ExecuteNamedAction(const ByteString& namedAction);
  void DoURIAction(const ByteString& bsURI, Mask<FWL_EVENTFLAG> modifiers);
  void DoGoToAction(int nPageIndex,
                    int zoomMode,
                    pdfium::span<float> fPosArray);

  CPDF_Document* GetPDFDocument() const { return m_pCPDFDoc; }
  CPDF_Document::Extension* GetDocExtension() const {
    return m_pCPDFDoc->GetExtension();
  }

  bool IsJSPlatformPresent() const { return m_pInfo && m_pInfo->m_pJsPlatform; }
  IPDF_JSPLATFORM* GetJSPlatform() const {
    return m_pInfo ? m_pInfo->m_pJsPlatform : nullptr;
  }

  // Actions.
  bool DoActionDocOpen(const CPDF_Action& action);
  bool DoActionJavaScript(const CPDF_Action& JsAction, WideString csJSName);
  bool DoActionPage(const CPDF_Action& action, CPDF_AAction::AActionType eType);
  bool DoActionDocument(const CPDF_Action& action,
                        CPDF_AAction::AActionType eType);
  bool DoActionField(const CPDF_Action& action,
                     CPDF_AAction::AActionType type,
                     CPDF_FormField* pFormField,
                     CFFL_FieldAction* data);
  bool DoActionFieldJavaScript(const CPDF_Action& JsAction,
                               CPDF_AAction::AActionType type,
                               CPDF_FormField* pFormField,
                               CFFL_FieldAction* data);
  bool DoActionLink(const CPDF_Action& action,
                    CPDF_AAction::AActionType type,
                    Mask<FWL_EVENTFLAG> modifiers);
  bool DoActionDestination(const CPDF_Dest& dest);
  void DoActionNoJs(const CPDF_Action& action, CPDF_AAction::AActionType type);
  void DoActionGoTo(const CPDF_Action& action);
  void DoActionLaunch(const CPDF_Action& action);
  void DoActionURI(const CPDF_Action& action, Mask<FWL_EVENTFLAG> modifiers);
  void DoActionNamed(const CPDF_Action& action);
  bool DoActionHide(const CPDF_Action& action);
  bool DoActionSubmitForm(const CPDF_Action& action);
  void DoActionResetForm(const CPDF_Action& action);

#ifdef PDF_ENABLE_V8
  CPDFSDK_PageView* GetCurrentView();
  IPDF_Page* GetCurrentPage() const;

  WideString GetLanguage();
  WideString GetPlatform();

  int JS_appAlert(const WideString& Msg,
                  const WideString& Title,
                  int Type,
                  int Icon);
  int JS_appResponse(const WideString& Question,
                     const WideString& Title,
                     const WideString& Default,
                     const WideString& cLabel,
                     FPDF_BOOL bPassword,
                     pdfium::span<uint8_t> response);
  void JS_appBeep(int nType);
  WideString JS_fieldBrowse();
  void JS_docmailForm(pdfium::span<const uint8_t> mailData,
                      FPDF_BOOL bUI,
                      const WideString& To,
                      const WideString& Subject,
                      const WideString& CC,
                      const WideString& BCC,
                      const WideString& Msg);
  void JS_docprint(FPDF_BOOL bUI,
                   int nStart,
                   int nEnd,
                   FPDF_BOOL bSilent,
                   FPDF_BOOL bShrinkToFit,
                   FPDF_BOOL bPrintAsImage,
                   FPDF_BOOL bReverse,
                   FPDF_BOOL bAnnotations);
  void JS_docgotoPage(int nPageNum);
  WideString JS_docGetFilePath();

#ifdef PDF_ENABLE_XFA
  int GetPageViewCount() const;
  void DisplayCaret(IPDF_Page* page,
                    FPDF_BOOL bVisible,
                    double left,
                    double top,
                    double right,
                    double bottom);
  int GetCurrentPageIndex() const;
  void SetCurrentPage(int iCurPage);

  // TODO(dsinclair): This should probably change to PDFium?
  WideString FFI_GetAppName() const { return WideString(L"Acrobat"); }

  void GotoURL(const WideString& wsURL);
  FS_RECTF GetPageViewRect(IPDF_Page* page);
  bool PopupMenu(IPDF_Page* page, int menuFlag, const CFX_PointF& pt);
  void EmailTo(FPDF_FILEHANDLER* fileHandler,
               FPDF_WIDESTRING pTo,
               FPDF_WIDESTRING pSubject,
               FPDF_WIDESTRING pCC,
               FPDF_WIDESTRING pBcc,
               FPDF_WIDESTRING pMsg);
  void UploadTo(FPDF_FILEHANDLER* fileHandler,
                int fileFlag,
                FPDF_WIDESTRING uploadTo);
  FPDF_FILEHANDLER* OpenFile(int fileType,
                             FPDF_WIDESTRING wsURL,
                             const char* mode);
  RetainPtr<IFX_SeekableReadStream> DownloadFromURL(const WideString& url);
  WideString PostRequestURL(const WideString& wsURL,
                            const WideString& wsData,
                            const WideString& wsContentType,
                            const WideString& wsEncode,
                            const WideString& wsHeader);
  FPDF_BOOL PutRequestURL(const WideString& wsURL,
                          const WideString& wsData,
                          const WideString& wsEncode);

  void PageEvent(int iPageCount, uint32_t dwEventType) const;
#endif  // PDF_ENABLE_XFA
#endif  // PDF_ENABLE_V8

  WideString GetFilePath() const;
  ByteString GetAppName() const { return ByteString(); }
  FPDF_FORMFILLINFO* GetFormFillInfo() const { return m_pInfo; }
  void SubmitForm(pdfium::span<const uint8_t> form_data, const WideString& URL);

  void SetFocusableAnnotSubtypes(
      const std::vector<CPDF_Annot::Subtype>& focusableAnnotTypes) {
    m_FocusableAnnotTypes = focusableAnnotTypes;
  }
  const std::vector<CPDF_Annot::Subtype>& GetFocusableAnnotSubtypes() const {
    return m_FocusableAnnotTypes;
  }

  // Never returns null.
  CFFL_InteractiveFormFiller* GetInteractiveFormFiller() {
    return m_pInteractiveFormFiller.get();
  }

  IJS_Runtime* GetIJSRuntime();                   // Creates if not present.
  CPDFSDK_InteractiveForm* GetInteractiveForm();  // Creates if not present.

 private:
  using RunScriptCallback = std::function<void(IJS_EventContext* context)>;

  IPDF_Page* GetPage(int nIndex) const;
  void OnSetFieldInputFocusInternal(const WideString& text, bool bFocus);
  void SendOnFocusChange(ObservedPtr<CPDFSDK_Annot>& pAnnot);

  // Support methods for Actions.
  void RunScript(const WideString& script, const RunScriptCallback& cb);
  bool ExecuteDocumentOpenAction(const CPDF_Action& action,
                                 std::set<const CPDF_Dictionary*>* visited);
  bool ExecuteDocumentPageAction(const CPDF_Action& action,
                                 CPDF_AAction::AActionType type,
                                 std::set<const CPDF_Dictionary*>* visited);
  bool ExecuteFieldAction(const CPDF_Action& action,
                          CPDF_AAction::AActionType type,
                          CPDF_FormField* pFormField,
                          CFFL_FieldAction* data,
                          std::set<const CPDF_Dictionary*>* visited);
  void RunDocumentPageJavaScript(CPDF_AAction::AActionType type,
                                 const WideString& script);
  void RunDocumentOpenJavaScript(const WideString& sScriptName,
                                 const WideString& script);
  void RunFieldJavaScript(CPDF_FormField* pFormField,
                          CPDF_AAction::AActionType type,
                          CFFL_FieldAction* data,
                          const WideString& script);
  bool IsValidField(const CPDF_Dictionary* pFieldDict);

  UnownedPtr<FPDF_FORMFILLINFO> const m_pInfo;
  std::unique_ptr<IJS_Runtime> m_pIJSRuntime;

  // Iterator stability guarantees as provided by std::map<> required.
  std::map<IPDF_Page*, std::unique_ptr<CPDFSDK_PageView>> m_PageMap;

  std::unique_ptr<CPDFSDK_InteractiveForm> m_pInteractiveForm;
  ObservedPtr<CPDFSDK_Annot> m_pFocusAnnot;
  UnownedPtr<CPDF_Document> const m_pCPDFDoc;
  std::unique_ptr<CFFL_InteractiveFormFiller> m_pInteractiveFormFiller;
  bool m_bChangeMask = false;
  bool m_bBeingDestroyed = false;

  // Holds the list of focusable annot types.
  // Annotations of type WIDGET are by default focusable.
  std::vector<CPDF_Annot::Subtype> m_FocusableAnnotTypes = {
      CPDF_Annot::Subtype::WIDGET};
};

#endif  // FPDFSDK_CPDFSDK_FORMFILLENVIRONMENT_H_
