// 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 CORE_FPDFAPI_PARSER_CPDF_DATA_AVAIL_H_
#define CORE_FPDFAPI_PARSER_CPDF_DATA_AVAIL_H_

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

#include "core/fpdfapi/parser/cpdf_document.h"
#include "core/fpdfapi/parser/cpdf_parser.h"
#include "core/fxcrt/retain_ptr.h"
#include "core/fxcrt/unowned_ptr.h"

class CPDF_CrossRefAvail;
class CPDF_Dictionary;
class CPDF_HintTables;
class CPDF_IndirectObjectHolder;
class CPDF_LinearizedHeader;
class CPDF_PageObjectAvail;
class CPDF_ReadValidator;
class CPDF_SyntaxParser;

class CPDF_DataAvail final : public Observable::ObserverIface {
 public:
  // Must match PDF_DATA_* definitions in public/fpdf_dataavail.h, but cannot
  // #include that header. fpdfsdk/fpdf_dataavail.cpp has static_asserts
  // to make sure the two sets of values match.
  enum DocAvailStatus {
    kDataError = -1,        // PDF_DATA_ERROR
    kDataNotAvailable = 0,  // PDF_DATA_NOTAVAIL
    kDataAvailable = 1,     // PDF_DATA_AVAIL
  };

  // Must match PDF_*LINEAR* definitions in public/fpdf_dataavail.h, but cannot
  // #include that header. fpdfsdk/fpdf_dataavail.cpp has static_asserts
  // to make sure the two sets of values match.
  enum DocLinearizationStatus {
    kLinearizationUnknown = -1,  // PDF_LINEARIZATION_UNKNOWN
    kNotLinearized = 0,          // PDF_NOT_LINEARIZED
    kLinearized = 1,             // PDF_LINEARIZED
  };

  // Must match PDF_FORM_* definitions in public/fpdf_dataavail.h, but cannot
  // #include that header. fpdfsdk/fpdf_dataavail.cpp has static_asserts
  // to make sure the two sets of values match.
  enum DocFormStatus {
    kFormError = -1,        // PDF_FORM_ERROR
    kFormNotAvailable = 0,  // PDF_FORM_NOTAVAIL
    kFormAvailable = 1,     // PDF_FORM_AVAIL
    kFormNotExist = 2,      // PDF_FORM_NOTEXIST
  };

  class FileAvail {
   public:
    virtual ~FileAvail();
    virtual bool IsDataAvail(FX_FILESIZE offset, size_t size) = 0;
  };

  class DownloadHints {
   public:
    virtual ~DownloadHints();
    virtual void AddSegment(FX_FILESIZE offset, size_t size) = 0;
  };

  CPDF_DataAvail(FileAvail* pFileAvail,
                 RetainPtr<IFX_SeekableReadStream> pFileRead);
  ~CPDF_DataAvail() override;

  // Observable::ObserverIface:
  void OnObservableDestroyed() override;

  DocAvailStatus IsDocAvail(DownloadHints* pHints);
  DocAvailStatus IsPageAvail(uint32_t dwPage, DownloadHints* pHints);
  DocFormStatus IsFormAvail(DownloadHints* pHints);
  DocLinearizationStatus IsLinearizedPDF();
  int GetPageCount() const;
  RetainPtr<const CPDF_Dictionary> GetPageDictionary(int index) const;
  RetainPtr<CPDF_ReadValidator> GetValidator() const;

  std::pair<CPDF_Parser::Error, std::unique_ptr<CPDF_Document>> ParseDocument(
      std::unique_ptr<CPDF_Document::RenderDataIface> pRenderData,
      std::unique_ptr<CPDF_Document::PageDataIface> pPageData,
      const ByteString& password);

  const CPDF_HintTables* GetHintTablesForTest() const {
    return m_pHintTables.get();
  }

 private:
  enum class InternalStatus : uint8_t {
    kHeader = 0,
    kFirstPage,
    kHintTable,
    kLoadAllCrossRef,
    kRoot,
    kInfo,
    kPageTree,
    kPage,
    kPageLaterLoad,
    kResources,
    kDone,
    kError,
    kLoadAllFile,
  };

  class PageNode {
   public:
    enum class Type { kUnknown = 0, kPage, kPages, kArray };

    PageNode();
    ~PageNode();

    Type m_type = Type::kUnknown;
    uint32_t m_dwPageNo = 0;
    std::vector<std::unique_ptr<PageNode>> m_ChildNodes;
  };

  static constexpr int kMaxPageRecursionDepth = 1024;

  bool CheckDocStatus();
  bool CheckHeader();
  bool CheckFirstPage();
  bool CheckHintTables();
  bool CheckRoot();
  bool CheckInfo();
  bool CheckPages();
  bool CheckPage();
  DocAvailStatus CheckResources(RetainPtr<CPDF_Dictionary> page);
  DocFormStatus CheckAcroForm();
  bool CheckPageStatus();

  DocAvailStatus CheckHeaderAndLinearized();
  RetainPtr<CPDF_Object> ParseIndirectObjectAt(
      FX_FILESIZE pos,
      uint32_t objnum,
      CPDF_IndirectObjectHolder* pObjList) const;
  RetainPtr<CPDF_Object> GetObject(uint32_t objnum, bool* pExistInFile);
  bool GetPageKids(CPDF_Object* pPages);
  bool PreparePageItem();
  bool LoadPages();
  bool CheckAndLoadAllXref();
  bool LoadAllFile();
  DocAvailStatus CheckLinearizedData();

  bool CheckPage(uint32_t dwPage);
  bool LoadDocPages();
  bool LoadDocPage(uint32_t dwPage);
  bool CheckPageNode(const PageNode& pageNode,
                     int32_t iPage,
                     int32_t& iCount,
                     int level);
  bool CheckUnknownPageNode(uint32_t dwPageNo, PageNode* pPageNode);
  bool CheckArrayPageNode(uint32_t dwPageNo, PageNode* pPageNode);
  bool CheckPageCount();
  bool IsFirstCheck(uint32_t dwPage);
  void ResetFirstCheck(uint32_t dwPage);
  bool ValidatePage(uint32_t dwPage) const;
  CPDF_SyntaxParser* GetSyntaxParser() const;

  RetainPtr<CPDF_ReadValidator> m_pFileRead;
  CPDF_Parser m_parser;
  RetainPtr<CPDF_Dictionary> m_pRoot;
  std::unique_ptr<CPDF_LinearizedHeader> m_pLinearized;
  bool m_bDocAvail = false;
  InternalStatus m_internalStatus = InternalStatus::kHeader;
  std::unique_ptr<CPDF_CrossRefAvail> m_pCrossRefAvail;
  const FX_FILESIZE m_dwFileLen;
  UnownedPtr<CPDF_Document> m_pDocument;
  std::vector<uint32_t> m_PageObjList;
  std::set<uint32_t> m_SeenPageObjList;
  uint32_t m_PagesObjNum = 0;
  bool m_bLinearedDataOK = false;
  bool m_bMainXRefLoadTried = false;
  bool m_bMainXRefLoadedOK = false;
  bool m_bPagesTreeLoad = false;
  bool m_bPagesLoad = false;
  std::unique_ptr<CPDF_PageObjectAvail> m_pFormAvail;
  std::vector<RetainPtr<CPDF_Object>> m_PagesArray;
  bool m_bTotalLoadPageTree = false;
  bool m_bCurPageDictLoadOK = false;
  bool m_bHeaderAvail = false;
  PageNode m_PageNode;
  std::set<uint32_t> m_pageMapCheckState;
  std::set<uint32_t> m_pagesLoadState;
  std::unique_ptr<CPDF_HintTables> m_pHintTables;
  std::map<uint32_t, std::unique_ptr<CPDF_PageObjectAvail>> m_PagesObjAvail;
  std::map<RetainPtr<const CPDF_Object>,
           std::unique_ptr<CPDF_PageObjectAvail>,
           std::less<>>
      m_PagesResourcesAvail;
};

#endif  // CORE_FPDFAPI_PARSER_CPDF_DATA_AVAIL_H_
