// 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

#include "xfa/fxfa/cxfa_ffwidgethandler.h"

#include "fxjs/xfa/cjx_object.h"
#include "xfa/fxfa/cxfa_ffdoc.h"
#include "xfa/fxfa/cxfa_ffdocview.h"
#include "xfa/fxfa/cxfa_fffield.h"
#include "xfa/fxfa/cxfa_ffwidget.h"
#include "xfa/fxfa/cxfa_fwladapterwidgetmgr.h"
#include "xfa/fxfa/layout/cxfa_layoutprocessor.h"
#include "xfa/fxfa/parser/cxfa_calculate.h"
#include "xfa/fxfa/parser/cxfa_checkbutton.h"
#include "xfa/fxfa/parser/cxfa_measurement.h"
#include "xfa/fxfa/parser/cxfa_node.h"
#include "xfa/fxfa/parser/cxfa_ui.h"
#include "xfa/fxfa/parser/cxfa_validate.h"

CXFA_FFWidgetHandler::CXFA_FFWidgetHandler(CXFA_FFDocView* pDocView)
    : m_pDocView(pDocView) {}

CXFA_FFWidgetHandler::~CXFA_FFWidgetHandler() = default;

void CXFA_FFWidgetHandler::Trace(cppgc::Visitor* visitor) const {
  visitor->Trace(m_pDocView);
}

bool CXFA_FFWidgetHandler::OnMouseEnter(CXFA_FFWidget* hWidget) {
  CXFA_FFDocView::UpdateScope scope(m_pDocView);
  return hWidget->OnMouseEnter();
}

bool CXFA_FFWidgetHandler::OnMouseExit(CXFA_FFWidget* hWidget) {
  CXFA_FFDocView::UpdateScope scope(m_pDocView);
  return hWidget->OnMouseExit();
}

bool CXFA_FFWidgetHandler::OnLButtonDown(CXFA_FFWidget* hWidget,
                                         Mask<XFA_FWL_KeyFlag> dwFlags,
                                         const CFX_PointF& point) {
  CXFA_FFDocView::UpdateScope scope(m_pDocView);
  if (!hWidget->AcceptsFocusOnButtonDown(
          dwFlags, hWidget->Rotate2Normal(point),
          CFWL_MessageMouse::MouseCommand::kLeftButtonDown)) {
    return false;
  }
  // May re-enter JS.
  if (m_pDocView->SetFocus(hWidget))
    m_pDocView->GetDoc()->SetFocusWidget(hWidget);

  return hWidget->OnLButtonDown(dwFlags, hWidget->Rotate2Normal(point));
}

bool CXFA_FFWidgetHandler::OnLButtonUp(CXFA_FFWidget* hWidget,
                                       Mask<XFA_FWL_KeyFlag> dwFlags,
                                       const CFX_PointF& point) {
  CXFA_FFDocView::UpdateScope scope(m_pDocView);
  m_pDocView->SetLayoutEvent();
  return hWidget->OnLButtonUp(dwFlags, hWidget->Rotate2Normal(point));
}

bool CXFA_FFWidgetHandler::OnLButtonDblClk(CXFA_FFWidget* hWidget,
                                           Mask<XFA_FWL_KeyFlag> dwFlags,
                                           const CFX_PointF& point) {
  return hWidget->OnLButtonDblClk(dwFlags, hWidget->Rotate2Normal(point));
}

bool CXFA_FFWidgetHandler::OnMouseMove(CXFA_FFWidget* hWidget,
                                       Mask<XFA_FWL_KeyFlag> dwFlags,
                                       const CFX_PointF& point) {
  return hWidget->OnMouseMove(dwFlags, hWidget->Rotate2Normal(point));
}

bool CXFA_FFWidgetHandler::OnMouseWheel(CXFA_FFWidget* hWidget,
                                        Mask<XFA_FWL_KeyFlag> dwFlags,
                                        const CFX_PointF& point,
                                        const CFX_Vector& delta) {
  return hWidget->OnMouseWheel(dwFlags, hWidget->Rotate2Normal(point), delta);
}

bool CXFA_FFWidgetHandler::OnRButtonDown(CXFA_FFWidget* hWidget,
                                         Mask<XFA_FWL_KeyFlag> dwFlags,
                                         const CFX_PointF& point) {
  if (!hWidget->AcceptsFocusOnButtonDown(
          dwFlags, hWidget->Rotate2Normal(point),
          CFWL_MessageMouse::MouseCommand::kRightButtonDown)) {
    return false;
  }
  // May re-enter JS.
  if (m_pDocView->SetFocus(hWidget)) {
    m_pDocView->GetDoc()->SetFocusWidget(hWidget);
  }
  return hWidget->OnRButtonDown(dwFlags, hWidget->Rotate2Normal(point));
}

bool CXFA_FFWidgetHandler::OnRButtonUp(CXFA_FFWidget* hWidget,
                                       Mask<XFA_FWL_KeyFlag> dwFlags,
                                       const CFX_PointF& point) {
  return hWidget->OnRButtonUp(dwFlags, hWidget->Rotate2Normal(point));
}

bool CXFA_FFWidgetHandler::OnRButtonDblClk(CXFA_FFWidget* hWidget,
                                           Mask<XFA_FWL_KeyFlag> dwFlags,
                                           const CFX_PointF& point) {
  return hWidget->OnRButtonDblClk(dwFlags, hWidget->Rotate2Normal(point));
}

bool CXFA_FFWidgetHandler::OnKeyDown(CXFA_FFWidget* hWidget,
                                     XFA_FWL_VKEYCODE dwKeyCode,
                                     Mask<XFA_FWL_KeyFlag> dwFlags) {
  bool bRet = hWidget->OnKeyDown(dwKeyCode, dwFlags);
  m_pDocView->UpdateDocView();
  return bRet;
}

bool CXFA_FFWidgetHandler::OnChar(CXFA_FFWidget* hWidget,
                                  uint32_t dwChar,
                                  Mask<XFA_FWL_KeyFlag> dwFlags) {
  return hWidget->OnChar(dwChar, dwFlags);
}

WideString CXFA_FFWidgetHandler::GetText(CXFA_FFWidget* widget) {
  return widget->GetText();
}

WideString CXFA_FFWidgetHandler::GetSelectedText(CXFA_FFWidget* widget) {
  if (!widget->CanCopy())
    return WideString();

  return widget->Copy().value_or(WideString());
}

void CXFA_FFWidgetHandler::PasteText(CXFA_FFWidget* widget,
                                     const WideString& text) {
  if (!widget->CanPaste())
    return;

  widget->Paste(text);
}

bool CXFA_FFWidgetHandler::SelectAllText(CXFA_FFWidget* widget) {
  if (!widget->CanSelectAll())
    return false;

  widget->SelectAll();
  return true;
}

bool CXFA_FFWidgetHandler::CanUndo(CXFA_FFWidget* widget) {
  return widget->CanUndo();
}

bool CXFA_FFWidgetHandler::CanRedo(CXFA_FFWidget* widget) {
  return widget->CanRedo();
}

bool CXFA_FFWidgetHandler::Undo(CXFA_FFWidget* widget) {
  return widget->Undo();
}

bool CXFA_FFWidgetHandler::Redo(CXFA_FFWidget* widget) {
  return widget->Redo();
}

FWL_WidgetHit CXFA_FFWidgetHandler::HitTest(CXFA_FFWidget* pWidget,
                                            const CFX_PointF& point) {
  if (!pWidget->GetLayoutItem()->TestStatusBits(XFA_WidgetStatus::kVisible))
    return FWL_WidgetHit::Unknown;
  return pWidget->HitTest(pWidget->Rotate2Normal(point));
}

void CXFA_FFWidgetHandler::RenderWidget(CXFA_FFWidget* hWidget,
                                        CFGAS_GEGraphics* pGS,
                                        const CFX_Matrix& matrix,
                                        bool bHighlight) {
  hWidget->RenderWidget(
      pGS, matrix,
      bHighlight ? CXFA_FFWidget::kHighlight : CXFA_FFWidget::kNoHighlight);
}

bool CXFA_FFWidgetHandler::HasEvent(CXFA_Node* pNode,
                                    XFA_EVENTTYPE eEventType) {
  if (eEventType == XFA_EVENT_Unknown)
    return false;
  if (!pNode || pNode->GetElementType() == XFA_Element::Draw)
    return false;

  switch (eEventType) {
    case XFA_EVENT_Calculate: {
      CXFA_Calculate* calc = pNode->GetCalculateIfExists();
      return calc && calc->GetScriptIfExists();
    }
    case XFA_EVENT_Validate: {
      CXFA_Validate* validate = pNode->GetValidateIfExists();
      return validate && validate->GetScriptIfExists();
    }
    default:
      break;
  }
  return !pNode->GetEventByActivity(kXFAEventActivity[eEventType], false)
              .empty();
}

XFA_EventError CXFA_FFWidgetHandler::ProcessEvent(CXFA_Node* pNode,
                                                  CXFA_EventParam* pParam) {
  if (!pParam || pParam->m_eType == XFA_EVENT_Unknown)
    return XFA_EventError::kNotExist;
  if (!pNode || pNode->GetElementType() == XFA_Element::Draw)
    return XFA_EventError::kNotExist;

  switch (pParam->m_eType) {
    case XFA_EVENT_Calculate:
      return pNode->ProcessCalculate(m_pDocView.Get());
    case XFA_EVENT_Validate:
      if (m_pDocView->GetDoc()->IsValidationsEnabled())
        return pNode->ProcessValidate(m_pDocView.Get(), 0);
      return XFA_EventError::kDisabled;
    case XFA_EVENT_InitCalculate: {
      CXFA_Calculate* calc = pNode->GetCalculateIfExists();
      if (!calc)
        return XFA_EventError::kNotExist;
      if (pNode->IsUserInteractive())
        return XFA_EventError::kDisabled;
      return pNode->ExecuteScript(m_pDocView.Get(), calc->GetScriptIfExists(),
                                  pParam);
    }
    default:
      break;
  }
  return pNode->ProcessEvent(m_pDocView.Get(),
                             kXFAEventActivity[pParam->m_eType], pParam);
}
