// 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_ffcheckbutton.h"

#include "core/fxcrt/check.h"
#include "v8/include/cppgc/visitor.h"
#include "xfa/fwl/cfwl_checkbox.h"
#include "xfa/fwl/cfwl_messagemouse.h"
#include "xfa/fwl/cfwl_notedriver.h"
#include "xfa/fwl/cfwl_widgetmgr.h"
#include "xfa/fxfa/cxfa_ffapp.h"
#include "xfa/fxfa/cxfa_ffdoc.h"
#include "xfa/fxfa/cxfa_ffdocview.h"
#include "xfa/fxfa/cxfa_ffexclgroup.h"
#include "xfa/fxfa/cxfa_fffield.h"
#include "xfa/fxfa/cxfa_ffpageview.h"
#include "xfa/fxfa/cxfa_ffwidget.h"
#include "xfa/fxfa/parser/cxfa_border.h"
#include "xfa/fxfa/parser/cxfa_caption.h"
#include "xfa/fxfa/parser/cxfa_checkbutton.h"
#include "xfa/fxfa/parser/cxfa_para.h"

CXFA_FFCheckButton::CXFA_FFCheckButton(CXFA_Node* pNode,
                                       CXFA_CheckButton* button)
    : CXFA_FFField(pNode), button_(button) {}

CXFA_FFCheckButton::~CXFA_FFCheckButton() = default;

void CXFA_FFCheckButton::Trace(cppgc::Visitor* visitor) const {
  CXFA_FFField::Trace(visitor);
  visitor->Trace(old_delegate_);
  visitor->Trace(button_);
}

bool CXFA_FFCheckButton::LoadWidget() {
  DCHECK(!IsLoaded());

  CFWL_CheckBox* pCheckBox = cppgc::MakeGarbageCollected<CFWL_CheckBox>(
      GetFWLApp()->GetHeap()->GetAllocationHandle(), GetFWLApp());
  SetNormalWidget(pCheckBox);
  pCheckBox->SetAdapterIface(this);

  CFWL_NoteDriver* pNoteDriver = pCheckBox->GetFWLApp()->GetNoteDriver();
  pNoteDriver->RegisterEventTarget(pCheckBox, pCheckBox);
  old_delegate_ = pCheckBox->GetDelegate();
  pCheckBox->SetDelegate(this);
  if (node_->IsRadioButton()) {
    pCheckBox->ModifyStyleExts(FWL_STYLEEXT_CKB_RadioButton, 0xFFFFFFFF);
  }

  {
    CFWL_Widget::ScopedUpdateLock update_lock(pCheckBox);
    UpdateWidgetProperty();
    SetFWLCheckState(node_->GetCheckState());
  }

  return CXFA_FFField::LoadWidget();
}

void CXFA_FFCheckButton::UpdateWidgetProperty() {
  auto* pCheckBox = static_cast<CFWL_CheckBox*>(GetNormalWidget());
  if (!pCheckBox) {
    return;
  }

  pCheckBox->SetBoxSize(node_->GetCheckButtonSize());
  uint32_t dwStyleEx = FWL_STYLEEXT_CKB_SignShapeCross;
  switch (button_->GetMark()) {
    case XFA_AttributeValue::Check:
      dwStyleEx = FWL_STYLEEXT_CKB_SignShapeCheck;
      break;
    case XFA_AttributeValue::Circle:
      dwStyleEx = FWL_STYLEEXT_CKB_SignShapeCircle;
      break;
    case XFA_AttributeValue::Cross:
      break;
    case XFA_AttributeValue::Diamond:
      dwStyleEx = FWL_STYLEEXT_CKB_SignShapeDiamond;
      break;
    case XFA_AttributeValue::Square:
      dwStyleEx = FWL_STYLEEXT_CKB_SignShapeSquare;
      break;
    case XFA_AttributeValue::Star:
      dwStyleEx = FWL_STYLEEXT_CKB_SignShapeStar;
      break;
    default: {
      if (button_->IsRound()) {
        dwStyleEx = FWL_STYLEEXT_CKB_SignShapeCircle;
      }
    } break;
  }
  if (button_->IsAllowNeutral()) {
    dwStyleEx |= FWL_STYLEEXT_CKB_3State;
  }

  pCheckBox->ModifyStyleExts(
      dwStyleEx, FWL_STYLEEXT_CKB_SignShapeMask | FWL_STYLEEXT_CKB_3State);
}

void CXFA_FFCheckButton::PerformLayout() {
  CXFA_FFWidget::PerformLayout();

  float fCheckSize = node_->GetCheckButtonSize();
  CXFA_Margin* margin = node_->GetMarginIfExists();
  CFX_RectF rtWidget = GetRectWithoutRotate();
  XFA_RectWithoutMargin(&rtWidget, margin);

  XFA_AttributeValue iCapPlacement = XFA_AttributeValue::Unknown;
  float fCapReserve = 0;
  CXFA_Caption* caption = node_->GetCaptionIfExists();
  if (caption && caption->IsVisible()) {
    caption_rect_ = rtWidget;
    iCapPlacement = caption->GetPlacementType();
    fCapReserve = caption->GetReserve();
    if (fCapReserve <= 0) {
      if (iCapPlacement == XFA_AttributeValue::Top ||
          iCapPlacement == XFA_AttributeValue::Bottom) {
        fCapReserve = rtWidget.height - fCheckSize;
      } else {
        fCapReserve = rtWidget.width - fCheckSize;
      }
    }
  }

  XFA_AttributeValue iHorzAlign = XFA_AttributeValue::Left;
  XFA_AttributeValue iVertAlign = XFA_AttributeValue::Top;
  CXFA_Para* para = node_->GetParaIfExists();
  if (para) {
    iHorzAlign = para->GetHorizontalAlign();
    iVertAlign = para->GetVerticalAlign();
  }

  uirect_ = rtWidget;
  CXFA_Margin* captionMargin = caption ? caption->GetMarginIfExists() : nullptr;
  switch (iCapPlacement) {
    case XFA_AttributeValue::Left: {
      caption_rect_.width = fCapReserve;
      CapLeftRightPlacement(captionMargin);
      uirect_.width -= fCapReserve;
      uirect_.left += fCapReserve;
      break;
    }
    case XFA_AttributeValue::Top: {
      caption_rect_.height = fCapReserve;
      XFA_RectWithoutMargin(&caption_rect_, captionMargin);
      uirect_.height -= fCapReserve;
      uirect_.top += fCapReserve;
      break;
    }
    case XFA_AttributeValue::Right: {
      caption_rect_.left = caption_rect_.right() - fCapReserve;
      caption_rect_.width = fCapReserve;
      CapLeftRightPlacement(captionMargin);
      uirect_.width -= fCapReserve;
      break;
    }
    case XFA_AttributeValue::Bottom: {
      caption_rect_.top = caption_rect_.bottom() - fCapReserve;
      caption_rect_.height = fCapReserve;
      XFA_RectWithoutMargin(&caption_rect_, captionMargin);
      uirect_.height -= fCapReserve;
      break;
    }
    case XFA_AttributeValue::Inline:
      break;
    default:
      iHorzAlign = XFA_AttributeValue::Right;
      break;
  }

  if (iHorzAlign == XFA_AttributeValue::Center) {
    uirect_.left += (uirect_.width - fCheckSize) / 2;
  } else if (iHorzAlign == XFA_AttributeValue::Right) {
    uirect_.left = uirect_.right() - fCheckSize;
  }

  if (iVertAlign == XFA_AttributeValue::Middle) {
    uirect_.top += (uirect_.height - fCheckSize) / 2;
  } else if (iVertAlign == XFA_AttributeValue::Bottom) {
    uirect_.top = uirect_.bottom() - fCheckSize;
  }

  uirect_.width = fCheckSize;
  uirect_.height = fCheckSize;
  AddUIMargin(iCapPlacement);
  check_box_rect_ = uirect_;
  CXFA_Border* borderUI = node_->GetUIBorder();
  if (borderUI) {
    CXFA_Margin* borderMargin = borderUI->GetMarginIfExists();
    XFA_RectWithoutMargin(&uirect_, borderMargin);
  }

  uirect_.Normalize();
  LayoutCaption();
  SetFWLRect();
  if (GetNormalWidget()) {
    GetNormalWidget()->Update();
  }
}

void CXFA_FFCheckButton::CapLeftRightPlacement(
    const CXFA_Margin* captionMargin) {
  XFA_RectWithoutMargin(&caption_rect_, captionMargin);
  if (caption_rect_.height < 0) {
    caption_rect_.top += caption_rect_.height;
  }
  if (caption_rect_.width < 0) {
    caption_rect_.left += caption_rect_.width;
    caption_rect_.width = -caption_rect_.width;
  }
}

void CXFA_FFCheckButton::AddUIMargin(XFA_AttributeValue iCapPlacement) {
  CFX_RectF rtUIMargin = node_->GetUIMargin();
  uirect_.top -= rtUIMargin.top / 2 - rtUIMargin.height / 2;

  float fLeftAddRight = rtUIMargin.left + rtUIMargin.width;
  float fTopAddBottom = rtUIMargin.top + rtUIMargin.height;
  if (uirect_.width < fLeftAddRight) {
    if (iCapPlacement == XFA_AttributeValue::Right ||
        iCapPlacement == XFA_AttributeValue::Left) {
      uirect_.left -= fLeftAddRight - uirect_.width;
    } else {
      uirect_.left -= 2 * (fLeftAddRight - uirect_.width);
    }
    uirect_.width += 2 * (fLeftAddRight - uirect_.width);
  }
  if (uirect_.height < fTopAddBottom) {
    if (iCapPlacement == XFA_AttributeValue::Right) {
      uirect_.left -= fTopAddBottom - uirect_.height;
    }

    uirect_.top -= fTopAddBottom - uirect_.height;
    uirect_.height += 2 * (fTopAddBottom - uirect_.height);
  }
}

void CXFA_FFCheckButton::RenderWidget(CFGAS_GEGraphics* pGS,
                                      const CFX_Matrix& matrix,
                                      HighlightOption highlight) {
  if (!HasVisibleStatus()) {
    return;
  }

  CFX_Matrix mtRotate = GetRotateMatrix();
  mtRotate.Concat(matrix);

  CXFA_FFWidget::RenderWidget(pGS, mtRotate, highlight);
  DrawBorderWithFlag(pGS, node_->GetUIBorder(), uirect_, mtRotate,
                     button_->IsRound());
  RenderCaption(pGS, mtRotate);
  DrawHighlight(pGS, mtRotate, highlight,
                button_->IsRound() ? kRoundShape : kSquareShape);
  CFX_Matrix mt(1, 0, 0, 1, check_box_rect_.left, check_box_rect_.top);
  mt.Concat(mtRotate);
  GetApp()->GetFWLWidgetMgr()->OnDrawWidget(GetNormalWidget(), pGS, mt);
}

bool CXFA_FFCheckButton::OnLButtonUp(Mask<XFA_FWL_KeyFlag> dwFlags,
                                     const CFX_PointF& point) {
  if (!GetNormalWidget() || !IsButtonDown()) {
    return false;
  }

  SetButtonDown(false);
  CFWL_MessageMouse msg(GetNormalWidget(),
                        CFWL_MessageMouse::MouseCommand::kLeftButtonUp, dwFlags,
                        FWLToClient(point));
  SendMessageToFWLWidget(&msg);
  return true;
}

XFA_CheckState CXFA_FFCheckButton::FWLState2XFAState() {
  Mask<pdfium::WidgetState> state = GetNormalWidget()->GetStates();
  if (state & pdfium::WidgetState::kCheckboxChecked) {
    return XFA_CheckState::kOn;
  }
  if (state & pdfium::WidgetState::kCheckboxNeutral) {
    return XFA_CheckState::kNeutral;
  }
  return XFA_CheckState::kOff;
}

bool CXFA_FFCheckButton::CommitData() {
  node_->SetCheckState(FWLState2XFAState());
  return true;
}

bool CXFA_FFCheckButton::IsDataChanged() {
  XFA_CheckState eCheckState = FWLState2XFAState();
  return node_->GetCheckState() != eCheckState;
}

void CXFA_FFCheckButton::SetFWLCheckState(XFA_CheckState eCheckState) {
  if (eCheckState == XFA_CheckState::kNeutral) {
    GetNormalWidget()->SetStates(pdfium::WidgetState::kCheckboxNeutral);
  } else if (eCheckState == XFA_CheckState::kOn) {
    GetNormalWidget()->SetStates(pdfium::WidgetState::kCheckboxChecked);
  } else {
    GetNormalWidget()->ClearStates(pdfium::WidgetState::kCheckboxChecked);
  }
}

void CXFA_FFCheckButton::UpdateFWLData() {
  if (!GetNormalWidget()) {
    return;
  }
  SetFWLCheckState(node_->GetCheckState());
  GetNormalWidget()->Update();
}

void CXFA_FFCheckButton::OnProcessMessage(CFWL_Message* pMessage) {
  old_delegate_->OnProcessMessage(pMessage);
}

void CXFA_FFCheckButton::OnProcessEvent(CFWL_Event* pEvent) {
  CXFA_FFField::OnProcessEvent(pEvent);
  switch (pEvent->GetType()) {
    case CFWL_Event::Type::CheckStateChanged: {
      CXFA_EventParam eParam(XFA_EVENT_Change);
      eParam.prev_text_ = node_->GetValue(XFA_ValuePicture::kRaw);
      CXFA_Node* exclNode = node_->GetExclGroupIfExists();
      if (ProcessCommittedData()) {
        if (exclNode) {
          doc_view_->AddValidateNode(exclNode);
          doc_view_->AddCalculateNode(exclNode);
          exclNode->ProcessEvent(GetDocView(), XFA_AttributeValue::Change,
                                 &eParam);
        }
        node_->ProcessEvent(GetDocView(), XFA_AttributeValue::Change, &eParam);
      } else {
        SetFWLCheckState(node_->GetCheckState());
      }
      if (exclNode) {
        exclNode->ProcessEvent(GetDocView(), XFA_AttributeValue::Click,
                               &eParam);
      }
      node_->ProcessEvent(GetDocView(), XFA_AttributeValue::Click, &eParam);
      break;
    }
    default:
      break;
  }
  old_delegate_->OnProcessEvent(pEvent);
}

void CXFA_FFCheckButton::OnDrawWidget(CFGAS_GEGraphics* pGraphics,
                                      const CFX_Matrix& matrix) {
  old_delegate_->OnDrawWidget(pGraphics, matrix);
}

FormFieldType CXFA_FFCheckButton::GetFormFieldType() {
  return FormFieldType::kXFA_CheckBox;
}
