// Copyright 2014 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 XFA_FXFA_LAYOUT_CXFA_CONTENTLAYOUTPROCESSOR_H_
#define XFA_FXFA_LAYOUT_CXFA_CONTENTLAYOUTPROCESSOR_H_

#include <float.h>

#include <list>
#include <map>
#include <utility>
#include <vector>

#include "core/fxcrt/fx_coordinates.h"
#include "core/fxcrt/unowned_ptr.h"
#include "fxjs/gc/heap.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "v8/include/cppgc/garbage-collected.h"
#include "v8/include/cppgc/macros.h"
#include "v8/include/cppgc/member.h"
#include "v8/include/cppgc/persistent.h"
#include "xfa/fxfa/fxfa_basic.h"

constexpr float kXFALayoutPrecision = 0.0005f;

class CXFA_ContentLayoutItem;
class CXFA_Node;
class CXFA_ViewLayoutItem;
class CXFA_ViewLayoutProcessor;

class CXFA_ContentLayoutProcessor
    : public cppgc::GarbageCollected<CXFA_ContentLayoutProcessor> {
 public:
  enum class Result : uint8_t {
    kDone,
    kPageFullBreak,
    kRowFullBreak,
    kManualBreak,
  };

  enum class Stage : uint8_t {
    kNone,
    kBookendLeader,
    kBreakBefore,
    kKeep,
    kContainer,
    kBreakAfter,
    kBookendTrailer,
    kDone,
  };

  CONSTRUCT_VIA_MAKE_GARBAGE_COLLECTED;
  ~CXFA_ContentLayoutProcessor();

  void Trace(cppgc::Visitor* visitor) const;
  cppgc::Heap* GetHeap() const { return m_pHeap; }

  Result DoLayout(bool bUseBreakControl, float fHeightLimit, float fRealHeight);
  void DoLayoutPageArea(CXFA_ViewLayoutItem* pPageAreaLayoutItem);

  CXFA_Node* GetFormNode() { return m_pFormNode; }
  CXFA_ContentLayoutItem* ExtractLayoutItem();

 private:
  class Context {
    CPPGC_STACK_ALLOCATED();  // Allows Raw/Unowned pointers.

   public:
    Context();
    ~Context();

    absl::optional<float> m_fCurColumnWidth;
    UnownedPtr<std::vector<float>> m_prgSpecifiedColumnWidths;
    UnownedPtr<CXFA_ContentLayoutProcessor> m_pOverflowProcessor;  // OK, stack
    UnownedPtr<CXFA_Node> m_pOverflowNode;                         // Ok, stack
  };

  CXFA_ContentLayoutProcessor(cppgc::Heap* pHeap,
                              CXFA_Node* pNode,
                              CXFA_ViewLayoutProcessor* pViewLayoutProcessor);

  Result DoLayoutInternal(bool bUseBreakControl,
                          float fHeightLimit,
                          float fRealHeight,
                          Context* pContext);

  CFX_SizeF GetCurrentComponentSize();
  bool HasLayoutItem() const { return !!m_pLayoutItem; }
  void SplitLayoutItem(float fSplitPos);
  float FindSplitPos(float fProposedSplitPos);
  bool ProcessKeepForSplit(
      CXFA_ContentLayoutProcessor* pChildProcessor,
      Result eRetValue,
      std::vector<cppgc::Persistent<CXFA_ContentLayoutItem>>*
          rgCurLineLayoutItem,
      float* fContentCurRowAvailWidth,
      float* fContentCurRowHeight,
      float* fContentCurRowY,
      bool* bAddedItemInRow,
      bool* bForceEndPage,
      Result* result);
  void ProcessUnUseOverFlow(CXFA_Node* pLeaderNode,
                            CXFA_Node* pTrailerNode,
                            CXFA_ContentLayoutItem* pTrailerItem,
                            CXFA_Node* pFormNode);
  bool IsAddNewRowForTrailer(CXFA_ContentLayoutItem* pTrailerItem);
  bool JudgeLeaderOrTrailerForOccur(CXFA_Node* pFormNode);

  // Object comes from GCed heap.
  CXFA_ContentLayoutItem* CreateContentLayoutItem(CXFA_Node* pFormNode);

  void SetCurrentComponentPos(const CFX_PointF& pos);
  void SetCurrentComponentSize(const CFX_SizeF& size);

  void SplitLayoutItem(CXFA_ContentLayoutItem* pLayoutItem,
                       CXFA_ContentLayoutItem* pSecondParent,
                       float fSplitPos);
  float InsertKeepLayoutItems();
  bool CalculateRowChildPosition(
      std::vector<cppgc::Persistent<CXFA_ContentLayoutItem>> (
          &rgCurLineLayoutItems)[3],
      XFA_AttributeValue eFlowStrategy,
      bool bContainerHeightAutoSize,
      bool bContainerWidthAutoSize,
      float* fContentCalculatedWidth,
      float* fContentCalculatedHeight,
      float* fContentCurRowY,
      float fContentCurRowHeight,
      float fContentWidthLimit,
      bool bRootForceTb);
  void ProcessUnUseBinds(CXFA_Node* pFormNode);
  bool JudgePutNextPage(CXFA_ContentLayoutItem* pParentLayoutItem,
                        float fChildHeight,
                        std::vector<CXFA_ContentLayoutItem*>* pKeepItems);

  void DoLayoutPositionedContainer(Context* pContext);
  void DoLayoutTableContainer(CXFA_Node* pLayoutNode);
  Result DoLayoutFlowedContainer(bool bUseBreakControl,
                                 XFA_AttributeValue eFlowStrategy,
                                 float fHeightLimit,
                                 float fRealHeight,
                                 Context* pContext,
                                 bool bRootForceTb);
  void DoLayoutField();

  void GotoNextContainerNodeSimple();

  // Return new stage and new action node.
  std::pair<Stage, CXFA_Node*> GotoNextContainerNode(
      Stage nCurStage,
      CXFA_Node* pParentContainer,
      CXFA_Node* pCurActionNode);

  absl::optional<Stage> ProcessKeepNodesForCheckNext(CXFA_Node** pCurActionNode,
                                                     CXFA_Node** pNextContainer,
                                                     bool* pLastKeepNode);

  absl::optional<Stage> ProcessKeepNodesForBreakBefore(
      CXFA_Node** pCurActionNode,
      CXFA_Node* pContainerNode);

  CXFA_Node* GetSubformSetParent(CXFA_Node* pSubformSet);

  void UpdatePendingItemLayout(CXFA_ContentLayoutItem* pLayoutItem);
  void AddTrailerBeforeSplit(float fSplitPos,
                             CXFA_ContentLayoutItem* pTrailerLayoutItem,
                             bool bUseInherited);
  void AddLeaderAfterSplit(CXFA_ContentLayoutItem* pLeaderLayoutItem);
  void AddPendingNode(CXFA_Node* pPendingNode, bool bBreakPending);
  float InsertPendingItems(CXFA_Node* pCurChildNode);
  Result InsertFlowedItem(
      CXFA_ContentLayoutProcessor* pProcessor,
      bool bContainerWidthAutoSize,
      bool bContainerHeightAutoSize,
      float fContainerHeight,
      XFA_AttributeValue eFlowStrategy,
      uint8_t* uCurHAlignState,
      std::vector<cppgc::Persistent<CXFA_ContentLayoutItem>> (
          &rgCurLineLayoutItems)[3],
      bool bUseBreakControl,
      float fAvailHeight,
      float fRealHeight,
      float fContentWidthLimit,
      float* fContentCurRowY,
      float* fContentCurRowAvailWidth,
      float* fContentCurRowHeight,
      bool* bAddedItemInRow,
      bool* bForceEndPage,
      Context* pLayoutContext,
      bool bNewRow);

  absl::optional<Stage> HandleKeep(CXFA_Node* pBreakAfterNode,
                                   CXFA_Node** pCurActionNode);
  absl::optional<Stage> HandleBookendLeader(CXFA_Node* pParentContainer,
                                            CXFA_Node** pCurActionNode);
  absl::optional<Stage> HandleBreakBefore(CXFA_Node* pChildContainer,
                                          CXFA_Node** pCurActionNode);
  absl::optional<Stage> HandleBreakAfter(CXFA_Node* pChildContainer,
                                         CXFA_Node** pCurActionNode);
  absl::optional<Stage> HandleCheckNextChildContainer(
      CXFA_Node* pParentContainer,
      CXFA_Node* pChildContainer,
      CXFA_Node** pCurActionNode);
  absl::optional<Stage> HandleBookendTrailer(CXFA_Node* pParentContainer,
                                             CXFA_Node** pCurActionNode);
  void ProcessKeepNodesEnd();
  void AdjustContainerSpecifiedSize(Context* pContext,
                                    CFX_SizeF* pSize,
                                    bool* pContainerWidthAutoSize,
                                    bool* pContainerHeightAutoSize);
  CXFA_ContentLayoutItem* FindLastContentLayoutItem(
      XFA_AttributeValue eFlowStrategy);
  CFX_SizeF CalculateLayoutItemSize(const CXFA_ContentLayoutItem* pLayoutChild);

  Stage m_nCurChildNodeStage = Stage::kNone;
  Result m_ePreProcessRs = Result::kDone;
  bool m_bBreakPending = true;
  bool m_bUseInherited = false;
  bool m_bKeepBreakFinish = false;
  bool m_bIsProcessKeep = false;
  bool m_bHasAvailHeight = true;
  float m_fUsedSize = 0;
  float m_fLastRowWidth = 0;
  float m_fLastRowY = 0;
  float m_fWidthLimit = 0;
  UnownedPtr<cppgc::Heap> m_pHeap;
  cppgc::Member<CXFA_Node> const m_pFormNode;
  cppgc::Member<CXFA_Node> m_pCurChildNode;
  cppgc::Member<CXFA_Node> m_pKeepHeadNode;
  cppgc::Member<CXFA_Node> m_pKeepTailNode;
  cppgc::Member<CXFA_ContentLayoutItem> m_pLayoutItem;
  cppgc::Member<CXFA_ContentLayoutItem> m_pOldLayoutItem;
  cppgc::Member<CXFA_ViewLayoutProcessor> m_pViewLayoutProcessor;
  std::vector<float> m_rgSpecifiedColumnWidths;
  std::vector<cppgc::Member<CXFA_ContentLayoutItem>> m_ArrayKeepItems;
  std::list<cppgc::Member<CXFA_Node>> m_PendingNodes;
  std::map<cppgc::Member<CXFA_Node>, int32_t> m_PendingNodesCount;
  cppgc::Member<CXFA_ContentLayoutProcessor> m_pCurChildPreprocessor;
};

#endif  // XFA_FXFA_LAYOUT_CXFA_CONTENTLAYOUTPROCESSOR_H_
