// 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 "xfa/fde/fde_render.h"

#include "xfa/fde/fde_object.h"
#include "xfa/fde/fde_renderdevice.h"
#include "xfa/fgas/crt/fgas_memory.h"

#define FDE_PATHRENDER_Stroke 1
#define FDE_PATHRENDER_Fill 2

namespace {

class CFDE_RenderContext : public IFDE_RenderContext, public CFX_Target {
 public:
  CFDE_RenderContext();
  virtual ~CFDE_RenderContext();
  virtual void Release() { delete this; }
  virtual FX_BOOL StartRender(IFDE_RenderDevice* pRenderDevice,
                              IFDE_CanvasSet* pCanvasSet,
                              const CFX_Matrix& tmDoc2Device);
  virtual FDE_RENDERSTATUS GetStatus() const { return m_eStatus; }
  virtual FDE_RENDERSTATUS DoRender(IFX_Pause* pPause = nullptr);
  virtual void StopRender();
  void RenderText(IFDE_TextSet* pTextSet, FDE_HVISUALOBJ hText);
  FX_BOOL ApplyClip(IFDE_VisualSet* pVisualSet,
                    FDE_HVISUALOBJ hObj,
                    FDE_HDEVICESTATE& hState);
  void RestoreClip(FDE_HDEVICESTATE hState);

 protected:
  FDE_RENDERSTATUS m_eStatus;
  IFDE_RenderDevice* m_pRenderDevice;
  CFDE_Brush* m_pBrush;
  CFX_Matrix m_Transform;
  FXTEXT_CHARPOS* m_pCharPos;
  int32_t m_iCharPosCount;
  IFDE_VisualSetIterator* m_pIterator;
};

}  // namespace

void FDE_GetPageMatrix(CFX_Matrix& pageMatrix,
                       const CFX_RectF& docPageRect,
                       const CFX_Rect& devicePageRect,
                       int32_t iRotate,
                       uint32_t dwCoordinatesType) {
  FXSYS_assert(iRotate >= 0 && iRotate <= 3);
  FX_BOOL bFlipX = (dwCoordinatesType & 0x01) != 0;
  FX_BOOL bFlipY = (dwCoordinatesType & 0x02) != 0;
  CFX_Matrix m;
  m.Set((bFlipX ? -1.0f : 1.0f), 0, 0, (bFlipY ? -1.0f : 1.0f), 0, 0);
  if (iRotate == 0 || iRotate == 2) {
    m.a *= (FX_FLOAT)devicePageRect.width / docPageRect.width;
    m.d *= (FX_FLOAT)devicePageRect.height / docPageRect.height;
  } else {
    m.a *= (FX_FLOAT)devicePageRect.height / docPageRect.width;
    m.d *= (FX_FLOAT)devicePageRect.width / docPageRect.height;
  }
  m.Rotate(iRotate * 1.57079632675f);
  switch (iRotate) {
    case 0:
      m.e = bFlipX ? (FX_FLOAT)devicePageRect.right()
                   : (FX_FLOAT)devicePageRect.left;
      m.f = bFlipY ? (FX_FLOAT)devicePageRect.bottom()
                   : (FX_FLOAT)devicePageRect.top;
      break;
    case 1:
      m.e = bFlipY ? (FX_FLOAT)devicePageRect.left
                   : (FX_FLOAT)devicePageRect.right();
      m.f = bFlipX ? (FX_FLOAT)devicePageRect.bottom()
                   : (FX_FLOAT)devicePageRect.top;
      break;
    case 2:
      m.e = bFlipX ? (FX_FLOAT)devicePageRect.left
                   : (FX_FLOAT)devicePageRect.right();
      m.f = bFlipY ? (FX_FLOAT)devicePageRect.top
                   : (FX_FLOAT)devicePageRect.bottom();
      break;
    case 3:
      m.e = bFlipY ? (FX_FLOAT)devicePageRect.right()
                   : (FX_FLOAT)devicePageRect.left;
      m.f = bFlipX ? (FX_FLOAT)devicePageRect.top
                   : (FX_FLOAT)devicePageRect.bottom();
      break;
    default:
      break;
  }
  pageMatrix = m;
}

IFDE_RenderContext* IFDE_RenderContext::Create() {
  return new CFDE_RenderContext;
}

CFDE_RenderContext::CFDE_RenderContext()
    : m_eStatus(FDE_RENDERSTATUS_Reset),
      m_pRenderDevice(nullptr),
      m_pBrush(nullptr),
      m_Transform(),
      m_pCharPos(nullptr),
      m_iCharPosCount(0),
      m_pIterator(nullptr) {
  m_Transform.SetIdentity();
}

CFDE_RenderContext::~CFDE_RenderContext() {
  StopRender();
}

FX_BOOL CFDE_RenderContext::StartRender(IFDE_RenderDevice* pRenderDevice,
                                        IFDE_CanvasSet* pCanvasSet,
                                        const CFX_Matrix& tmDoc2Device) {
  if (m_pRenderDevice)
    return FALSE;
  if (!pRenderDevice)
    return FALSE;
  if (!pCanvasSet)
    return FALSE;

  m_eStatus = FDE_RENDERSTATUS_Paused;
  m_pRenderDevice = pRenderDevice;
  m_Transform = tmDoc2Device;
  if (!m_pIterator) {
    m_pIterator = IFDE_VisualSetIterator::Create();
    FXSYS_assert(m_pIterator);
  }
  return m_pIterator->AttachCanvas(pCanvasSet) && m_pIterator->FilterObjects();
}

FDE_RENDERSTATUS CFDE_RenderContext::DoRender(IFX_Pause* pPause) {
  if (!m_pRenderDevice)
    return FDE_RENDERSTATUS_Failed;
  if (!m_pIterator)
    return FDE_RENDERSTATUS_Failed;

  FDE_RENDERSTATUS eStatus = FDE_RENDERSTATUS_Paused;
  CFX_Matrix rm;
  rm.SetReverse(m_Transform);
  CFX_RectF rtDocClip = m_pRenderDevice->GetClipRect();
  if (rtDocClip.IsEmpty()) {
    rtDocClip.left = rtDocClip.top = 0;
    rtDocClip.width = (FX_FLOAT)m_pRenderDevice->GetWidth();
    rtDocClip.height = (FX_FLOAT)m_pRenderDevice->GetHeight();
  }
  rm.TransformRect(rtDocClip);
  IFDE_VisualSet* pVisualSet;
  FDE_HVISUALOBJ hVisualObj;
  CFX_RectF rtObj;
  int32_t iCount = 0;
  while (TRUE) {
    hVisualObj = m_pIterator->GetNext(pVisualSet);
    if (!hVisualObj || !pVisualSet) {
      eStatus = FDE_RENDERSTATUS_Done;
      break;
    }
    rtObj.Empty();
    pVisualSet->GetRect(hVisualObj, rtObj);
    if (!rtDocClip.IntersectWith(rtObj))
      continue;

    switch (pVisualSet->GetType()) {
      case FDE_VISUALOBJ_Text:
        RenderText((IFDE_TextSet*)pVisualSet, hVisualObj);
        iCount += 5;
        break;
      case FDE_VISUALOBJ_Canvas:
        FXSYS_assert(FALSE);
        break;
      default:
        break;
    }
    if (iCount >= 100 && pPause && pPause->NeedToPauseNow()) {
      eStatus = FDE_RENDERSTATUS_Paused;
      break;
    }
  }
  return m_eStatus = eStatus;
}

void CFDE_RenderContext::StopRender() {
  m_eStatus = FDE_RENDERSTATUS_Reset;
  m_pRenderDevice = nullptr;
  m_Transform.SetIdentity();
  if (m_pIterator) {
    m_pIterator->Release();
    m_pIterator = nullptr;
  }
  if (m_pBrush) {
    delete m_pBrush;
    m_pBrush = nullptr;
  }
  FX_Free(m_pCharPos);
  m_pCharPos = nullptr;
  m_iCharPosCount = 0;
}

void CFDE_RenderContext::RenderText(IFDE_TextSet* pTextSet,
                                    FDE_HVISUALOBJ hText) {
  FXSYS_assert(m_pRenderDevice);
  FXSYS_assert(pTextSet && hText);

  IFX_Font* pFont = pTextSet->GetFont(hText);
  if (!pFont)
    return;

  int32_t iCount = pTextSet->GetDisplayPos(hText, nullptr, FALSE);
  if (iCount < 1)
    return;

  if (!m_pBrush)
    m_pBrush = new CFDE_Brush;

  if (!m_pCharPos)
    m_pCharPos = FX_Alloc(FXTEXT_CHARPOS, iCount);
  else if (m_iCharPosCount < iCount)
    m_pCharPos = FX_Realloc(FXTEXT_CHARPOS, m_pCharPos, iCount);

  if (m_iCharPosCount < iCount)
    m_iCharPosCount = iCount;

  iCount = pTextSet->GetDisplayPos(hText, m_pCharPos, FALSE);
  FX_FLOAT fFontSize = pTextSet->GetFontSize(hText);
  FX_ARGB dwColor = pTextSet->GetFontColor(hText);
  m_pBrush->SetColor(dwColor);
  FDE_HDEVICESTATE hState;
  FX_BOOL bClip = ApplyClip(pTextSet, hText, hState);
  m_pRenderDevice->DrawString(m_pBrush, pFont, m_pCharPos, iCount, fFontSize,
                              &m_Transform);
  if (bClip)
    RestoreClip(hState);
}

FX_BOOL CFDE_RenderContext::ApplyClip(IFDE_VisualSet* pVisualSet,
                                      FDE_HVISUALOBJ hObj,
                                      FDE_HDEVICESTATE& hState) {
  CFX_RectF rtClip;
  if (!pVisualSet->GetClip(hObj, rtClip))
    return FALSE;

  CFX_RectF rtObj;
  pVisualSet->GetRect(hObj, rtObj);
  rtClip.Offset(rtObj.left, rtObj.top);
  m_Transform.TransformRect(rtClip);
  const CFX_RectF& rtDevClip = m_pRenderDevice->GetClipRect();
  rtClip.Intersect(rtDevClip);
  hState = m_pRenderDevice->SaveState();
  return m_pRenderDevice->SetClipRect(rtClip);
}

void CFDE_RenderContext::RestoreClip(FDE_HDEVICESTATE hState) {
  m_pRenderDevice->RestoreState(hState);
}