// 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 "core/fpdfdoc/cpdf_formfield.h"

#include <map>
#include <set>
#include <utility>

#include "constants/form_fields.h"
#include "constants/form_flags.h"
#include "core/fpdfapi/font/cpdf_font.h"
#include "core/fpdfapi/page/cpdf_docpagedata.h"
#include "core/fpdfapi/parser/cfdf_document.h"
#include "core/fpdfapi/parser/cpdf_array.h"
#include "core/fpdfapi/parser/cpdf_dictionary.h"
#include "core/fpdfapi/parser/cpdf_name.h"
#include "core/fpdfapi/parser/cpdf_number.h"
#include "core/fpdfapi/parser/cpdf_stream.h"
#include "core/fpdfapi/parser/cpdf_string.h"
#include "core/fpdfapi/parser/fpdf_parser_decode.h"
#include "core/fpdfapi/parser/fpdf_parser_utility.h"
#include "core/fpdfdoc/cpdf_defaultappearance.h"
#include "core/fpdfdoc/cpdf_formcontrol.h"
#include "core/fpdfdoc/cpdf_generateap.h"
#include "core/fpdfdoc/cpdf_interactiveform.h"
#include "core/fxcrt/check.h"
#include "core/fxcrt/containers/contains.h"
#include "core/fxcrt/stl_util.h"

namespace {

RetainPtr<const CPDF_Object> GetFieldAttrRecursive(
    const CPDF_Dictionary* pFieldDict,
    ByteStringView name,
    int nLevel) {
  static constexpr int kGetFieldMaxRecursion = 32;
  if (!pFieldDict || nLevel > kGetFieldMaxRecursion) {
    return nullptr;
  }

  RetainPtr<const CPDF_Object> pAttr = pFieldDict->GetDirectObjectFor(name);
  if (pAttr) {
    return pAttr;
  }

  return GetFieldAttrRecursive(
      pFieldDict->GetDictFor(pdfium::form_fields::kParent).Get(), name,
      nLevel + 1);
}

bool IsComboOrListField(CPDF_FormField::Type type) {
  switch (type) {
    case CPDF_FormField::kComboBox:
    case CPDF_FormField::kListBox:
      return true;
    default:
      return false;
  }
}

}  // namespace

// static
std::optional<FormFieldType> CPDF_FormField::IntToFormFieldType(int value) {
  if (value >= static_cast<int>(FormFieldType::kUnknown) &&
      value < static_cast<int>(kFormFieldTypeCount)) {
    return static_cast<FormFieldType>(value);
  }
  return std::nullopt;
}

// static
RetainPtr<const CPDF_Object> CPDF_FormField::GetFieldAttrForDict(
    const CPDF_Dictionary* pFieldDict,
    ByteStringView name) {
  return GetFieldAttrRecursive(pFieldDict, name, 0);
}

// static
RetainPtr<CPDF_Object> CPDF_FormField::GetMutableFieldAttrForDict(
    CPDF_Dictionary* pFieldDict,
    ByteStringView name) {
  return pdfium::WrapRetain(const_cast<CPDF_Object*>(
      GetFieldAttrRecursive(pFieldDict, name, 0).Get()));
}

// static
WideString CPDF_FormField::GetFullNameForDict(
    const CPDF_Dictionary* pFieldDict) {
  WideString full_name;
  std::set<const CPDF_Dictionary*> visited;
  const CPDF_Dictionary* pLevel = pFieldDict;
  while (pLevel) {
    visited.insert(pLevel);
    WideString short_name = pLevel->GetUnicodeTextFor(pdfium::form_fields::kT);
    if (!short_name.IsEmpty()) {
      if (full_name.IsEmpty()) {
        full_name = std::move(short_name);
      } else {
        full_name = short_name + L'.' + full_name;
      }
    }
    pLevel = pLevel->GetDictFor(pdfium::form_fields::kParent).Get();
    if (pdfium::Contains(visited, pLevel)) {
      break;
    }
  }
  return full_name;
}

CPDF_FormField::CPDF_FormField(CPDF_InteractiveForm* pForm,
                               RetainPtr<CPDF_Dictionary> dict)
    : form_(pForm), dict_(std::move(dict)) {
  InitFieldFlags();
}

CPDF_FormField::~CPDF_FormField() = default;

void CPDF_FormField::InitFieldFlags() {
  RetainPtr<const CPDF_Object> ft_attr =
      GetFieldAttrInternal(pdfium::form_fields::kFT);
  ByteString type_name = ft_attr ? ft_attr->GetString() : ByteString();
  uint32_t flags = GetFieldFlags();
  required_ = flags & pdfium::form_flags::kRequired;
  no_export_ = flags & pdfium::form_flags::kNoExport;

  if (type_name == pdfium::form_fields::kBtn) {
    if (flags & pdfium::form_flags::kButtonRadio) {
      type_ = kRadioButton;
      is_unison_ = flags & pdfium::form_flags::kButtonRadiosInUnison;
    } else if (flags & pdfium::form_flags::kButtonPushbutton) {
      type_ = kPushButton;
    } else {
      type_ = kCheckBox;
      is_unison_ = true;
    }
  } else if (type_name == pdfium::form_fields::kTx) {
    if (flags & pdfium::form_flags::kTextFileSelect) {
      type_ = kFile;
    } else if (flags & pdfium::form_flags::kTextRichText) {
      type_ = kRichText;
    } else {
      type_ = kText;
    }
  } else if (type_name == pdfium::form_fields::kCh) {
    if (flags & pdfium::form_flags::kChoiceCombo) {
      type_ = kComboBox;
    } else {
      type_ = kListBox;
      is_multi_select_list_box_ =
          flags & pdfium::form_flags::kChoiceMultiSelect;
    }
    use_selected_indices_ = UseSelectedIndicesObject();
  } else if (type_name == pdfium::form_fields::kSig) {
    type_ = kSign;
  }
}

WideString CPDF_FormField::GetFullName() const {
  return GetFullNameForDict(dict_.Get());
}

RetainPtr<const CPDF_Object> CPDF_FormField::GetFieldAttr(
    ByteStringView name) const {
  return GetFieldAttrInternal(name);
}

RetainPtr<const CPDF_Dictionary> CPDF_FormField::GetFieldDict() const {
  return pdfium::WrapRetain(GetFieldDictInternal());
}

void CPDF_FormField::ResetField() {
  switch (type_) {
    case kCheckBox:
    case kRadioButton: {
      int iCount = CountControls();
      // TODO(weili): Check whether anything special needs to be done for
      // |is_unison_|.
      for (int i = 0; i < iCount; i++) {
        CheckControl(i, GetControl(i)->IsDefaultChecked(),
                     NotificationOption::kDoNotNotify);
      }
      form_->NotifyAfterCheckedStatusChange(this);
      break;
    }
    case kComboBox:
    case kListBox: {
      ClearSelection(NotificationOption::kDoNotNotify);
      WideString csValue;
      int index = GetDefaultSelectedItem();
      if (index >= 0) {
        csValue = GetOptionLabel(index);
      }
      if (!NotifyListOrComboBoxBeforeChange(csValue)) {
        return;
      }
      SetItemSelection(index, NotificationOption::kDoNotNotify);
      NotifyListOrComboBoxAfterChange();
      break;
    }
    case kText:
    case kRichText:
    case kFile:
    default: {
      WideString csDValue;
      WideString csValue;
      {
        // Limit scope of |pDV| and |pV| because they may get invalidated
        // during notification below.
        RetainPtr<const CPDF_Object> pDV = GetDefaultValueObject();
        if (pDV) {
          csDValue = pDV->GetUnicodeText();
        }

        RetainPtr<const CPDF_Object> pV = GetValueObject();
        if (pV) {
          csValue = pV->GetUnicodeText();
        }
      }

      bool bHasRV = !!GetFieldAttrInternal(pdfium::form_fields::kRV);
      if (!bHasRV && (csDValue == csValue)) {
        return;
      }

      if (!form_->NotifyBeforeValueChange(this, csDValue)) {
        return;
      }

      {
        // Limit scope of |pDV| because it may get invalidated during
        // notification below.
        RetainPtr<const CPDF_Object> pDV = GetDefaultValueObject();
        if (pDV) {
          RetainPtr<CPDF_Object> pClone = pDV->Clone();
          if (!pClone) {
            return;
          }

          dict_->SetFor(pdfium::form_fields::kV, std::move(pClone));
          if (bHasRV) {
            dict_->SetFor(pdfium::form_fields::kRV, pDV->Clone());
          }
        } else {
          dict_->RemoveFor(pdfium::form_fields::kV);
          dict_->RemoveFor(pdfium::form_fields::kRV);
        }
      }
      form_->NotifyAfterValueChange(this);
      break;
    }
  }
}

int CPDF_FormField::CountControls() const {
  return fxcrt::CollectionSize<int>(GetControls());
}

CPDF_FormControl* CPDF_FormField::GetControl(int index) const {
  return GetControls()[index];
}

int CPDF_FormField::GetControlIndex(const CPDF_FormControl* pControl) const {
  if (!pControl) {
    return -1;
  }

  const auto& controls = GetControls();
  auto it = std::ranges::find(controls, pControl);
  if (it == controls.end()) {
    return -1;
  }

  return pdfium::checked_cast<int>(it - controls.begin());
}

FormFieldType CPDF_FormField::GetFieldType() const {
  switch (type_) {
    case kPushButton:
      return FormFieldType::kPushButton;
    case kCheckBox:
      return FormFieldType::kCheckBox;
    case kRadioButton:
      return FormFieldType::kRadioButton;
    case kComboBox:
      return FormFieldType::kComboBox;
    case kListBox:
      return FormFieldType::kListBox;
    case kText:
    case kRichText:
    case kFile:
      return FormFieldType::kTextField;
    case kSign:
      return FormFieldType::kSignature;
    default:
      return FormFieldType::kUnknown;
  }
}

CPDF_AAction CPDF_FormField::GetAdditionalAction() const {
  RetainPtr<const CPDF_Object> pObj =
      GetFieldAttrInternal(pdfium::form_fields::kAA);
  return CPDF_AAction(pObj ? pObj->GetDict() : nullptr);
}

WideString CPDF_FormField::GetAlternateName() const {
  RetainPtr<const CPDF_Object> pObj =
      GetFieldAttrInternal(pdfium::form_fields::kTU);
  return pObj ? pObj->GetUnicodeText() : WideString();
}

WideString CPDF_FormField::GetMappingName() const {
  RetainPtr<const CPDF_Object> pObj =
      GetFieldAttrInternal(pdfium::form_fields::kTM);
  return pObj ? pObj->GetUnicodeText() : WideString();
}

uint32_t CPDF_FormField::GetFieldFlags() const {
  RetainPtr<const CPDF_Object> pObj =
      GetFieldAttrInternal(pdfium::form_fields::kFf);
  return pObj ? pObj->GetInteger() : 0;
}

void CPDF_FormField::SetFieldFlags(uint32_t dwFlags) {
  dict_->SetNewFor<CPDF_Number>(pdfium::form_fields::kFf,
                                static_cast<int>(dwFlags));
}

WideString CPDF_FormField::GetValue(bool bDefault) const {
  if (GetType() == kCheckBox || GetType() == kRadioButton) {
    return GetCheckValue(bDefault);
  }

  RetainPtr<const CPDF_Object> pValue =
      bDefault ? GetDefaultValueObject() : GetValueObject();
  if (!pValue) {
    if (!bDefault && type_ != kText) {
      pValue = GetDefaultValueObject();
    }
    if (!pValue) {
      return WideString();
    }
  }

  switch (pValue->GetType()) {
    case CPDF_Object::kString:
    case CPDF_Object::kStream:
      return pValue->GetUnicodeText();
    case CPDF_Object::kArray: {
      RetainPtr<const CPDF_Object> pNewValue =
          pValue->AsArray()->GetDirectObjectAt(0);
      if (pNewValue) {
        return pNewValue->GetUnicodeText();
      }
      break;
    }
    default:
      break;
  }
  return WideString();
}

WideString CPDF_FormField::GetValue() const {
  return GetValue(false);
}

WideString CPDF_FormField::GetDefaultValue() const {
  return GetValue(true);
}

bool CPDF_FormField::SetValue(const WideString& value,
                              bool bDefault,
                              NotificationOption notify) {
  switch (GetType()) {
    case kCheckBox:
    case kRadioButton: {
      SetCheckValue(value, bDefault, notify);
      return true;
    }
    case kFile:
    case kRichText:
    case kText:
    case kComboBox: {
      WideString csValue = value;
      if (notify == NotificationOption::kNotify &&
          !form_->NotifyBeforeValueChange(this, csValue)) {
        return false;
      }
      ByteString key(bDefault ? pdfium::form_fields::kDV
                              : pdfium::form_fields::kV);
      dict_->SetNewFor<CPDF_String>(key, csValue.AsStringView());

      int index;
      if (GetType() == kComboBox) {
        index = FindOption(csValue);
      } else {
        index = -1;
      }
      if (index < 0) {
        if (type_ == kRichText && !bDefault) {
          dict_->SetFor(pdfium::form_fields::kRV,
                        dict_->GetObjectFor(key.AsStringView())->Clone());
        }
        dict_->RemoveFor("I");
      } else {
        if (!bDefault) {
          ClearSelection(NotificationOption::kDoNotNotify);
          SetItemSelection(index, NotificationOption::kDoNotNotify);
        }
      }
      if (notify == NotificationOption::kNotify) {
        form_->NotifyAfterValueChange(this);
      }
      break;
    }
    case kListBox: {
      int index = FindOption(value);
      if (index < 0) {
        return false;
      }

      if (bDefault && index == GetDefaultSelectedItem()) {
        return false;
      }

      if (notify == NotificationOption::kNotify &&
          !form_->NotifyBeforeSelectionChange(this, value)) {
        return false;
      }
      if (!bDefault) {
        ClearSelection(NotificationOption::kDoNotNotify);
        SetItemSelection(index, NotificationOption::kDoNotNotify);
      }
      if (notify == NotificationOption::kNotify) {
        form_->NotifyAfterSelectionChange(this);
      }
      break;
    }
    default:
      break;
  }
  return true;
}

bool CPDF_FormField::SetValue(const WideString& value,
                              NotificationOption notify) {
  return SetValue(value, false, notify);
}

int CPDF_FormField::GetMaxLen() const {
  RetainPtr<const CPDF_Object> pObj = GetFieldAttrInternal("MaxLen");
  if (pObj) {
    return pObj->GetInteger();
  }

  for (auto& pControl : GetControls()) {
    if (!pControl) {
      continue;
    }

    RetainPtr<const CPDF_Dictionary> pWidgetDict = pControl->GetWidgetDict();
    if (pWidgetDict->KeyExist("MaxLen")) {
      return pWidgetDict->GetIntegerFor("MaxLen");
    }
  }
  return 0;
}

int CPDF_FormField::CountSelectedItems() const {
  const CPDF_Object* pValue = GetValueOrSelectedIndicesObject();
  if (!pValue) {
    return 0;
  }

  if (pValue->IsString() || pValue->IsNumber()) {
    return pValue->GetString().IsEmpty() ? 0 : 1;
  }
  const CPDF_Array* pArray = pValue->AsArray();
  return pArray ? fxcrt::CollectionSize<int>(*pArray) : 0;
}

int CPDF_FormField::GetSelectedIndex(int index) const {
  const CPDF_Object* pValue = GetValueOrSelectedIndicesObject();
  if (!pValue) {
    return -1;
  }

  if (pValue->IsNumber()) {
    return pValue->GetInteger();
  }

  WideString sel_value;
  if (pValue->IsString()) {
    if (index != 0) {
      return -1;
    }
    sel_value = pValue->GetUnicodeText();
  } else {
    const CPDF_Array* pArray = pValue->AsArray();
    if (!pArray || index < 0) {
      return -1;
    }

    RetainPtr<const CPDF_Object> elementValue =
        pArray->GetDirectObjectAt(index);
    sel_value = elementValue ? elementValue->GetUnicodeText() : WideString();
  }
  if (index < CountSelectedOptions()) {
    int iOptIndex = GetSelectedOptionIndex(index);
    WideString csOpt = GetOptionValue(iOptIndex);
    if (csOpt == sel_value) {
      return iOptIndex;
    }
  }
  for (int i = 0; i < CountOptions(); i++) {
    if (sel_value == GetOptionValue(i)) {
      return i;
    }
  }
  return -1;
}

bool CPDF_FormField::ClearSelection(NotificationOption notify) {
  if (notify == NotificationOption::kNotify) {
    WideString csValue;
    int index = GetSelectedIndex(0);
    if (index >= 0) {
      csValue = GetOptionLabel(index);
    }
    if (!NotifyListOrComboBoxBeforeChange(csValue)) {
      return false;
    }
  }
  dict_->RemoveFor(pdfium::form_fields::kV);
  dict_->RemoveFor("I");
  if (notify == NotificationOption::kNotify) {
    NotifyListOrComboBoxAfterChange();
  }
  return true;
}

bool CPDF_FormField::IsItemSelected(int index) const {
  CHECK(IsComboOrListField(GetType()));
  if (index < 0 || index >= CountOptions()) {
    return false;
  }
  // First consider the /I entry if it is valid, then fall back to the /V entry.
  return use_selected_indices_ ? IsSelectedIndex(index)
                               : IsSelectedOption(GetOptionValue(index));
}

void CPDF_FormField::SetItemSelection(int index, NotificationOption notify) {
  CHECK(IsComboOrListField(GetType()));
  if (index < 0 || index >= CountOptions()) {
    return;
  }
  WideString opt_value = GetOptionValue(index);
  if (notify == NotificationOption::kNotify &&
      !NotifyListOrComboBoxBeforeChange(opt_value)) {
    return;
  }

  SetItemSelectionSelected(index, opt_value);

  // UseSelectedIndicesObject() has a non-trivial linearithmic run-time, so run
  // only if necessary.
  if (!use_selected_indices_) {
    use_selected_indices_ = UseSelectedIndicesObject();
  }

  if (notify == NotificationOption::kNotify) {
    NotifyListOrComboBoxAfterChange();
  }
}

void CPDF_FormField::SetItemSelectionSelected(int index,
                                              const WideString& opt_value) {
  if (GetType() != kListBox) {
    dict_->SetNewFor<CPDF_String>(pdfium::form_fields::kV,
                                  opt_value.AsStringView());
    auto pI = dict_->SetNewFor<CPDF_Array>("I");
    pI->AppendNew<CPDF_Number>(index);
    return;
  }

  SelectOption(index);
  if (!is_multi_select_list_box_) {
    dict_->SetNewFor<CPDF_String>(pdfium::form_fields::kV,
                                  opt_value.AsStringView());
    return;
  }

  auto pArray = dict_->SetNewFor<CPDF_Array>(pdfium::form_fields::kV);
  for (int i = 0; i < CountOptions(); i++) {
    if (i == index || IsItemSelected(i)) {
      pArray->AppendNew<CPDF_String>(GetOptionValue(i).AsStringView());
    }
  }
}

int CPDF_FormField::GetDefaultSelectedItem() const {
  CHECK(IsComboOrListField(GetType()));
  RetainPtr<const CPDF_Object> pValue = GetDefaultValueObject();
  if (!pValue) {
    return -1;
  }
  WideString csDV = pValue->GetUnicodeText();
  if (csDV.IsEmpty()) {
    return -1;
  }
  for (int i = 0; i < CountOptions(); i++) {
    if (csDV == GetOptionValue(i)) {
      return i;
    }
  }
  return -1;
}

bool CPDF_FormField::HasOptField() const {
  switch (GetType()) {
    case CPDF_FormField::kCheckBox:
    case CPDF_FormField::kRadioButton:
    case CPDF_FormField::kComboBox:
    case CPDF_FormField::kListBox:
      return true;
    default:
      return false;
  }
}

int CPDF_FormField::CountOptions() const {
  CHECK(HasOptField());
  RetainPtr<const CPDF_Array> pArray = ToArray(GetFieldAttrInternal("Opt"));
  return pArray ? fxcrt::CollectionSize<int>(*pArray) : 0;
}

WideString CPDF_FormField::GetOptionText(int index, int sub_index) const {
  CHECK(HasOptField());
  RetainPtr<const CPDF_Array> pArray = ToArray(GetFieldAttrInternal("Opt"));
  if (!pArray) {
    return WideString();
  }

  RetainPtr<const CPDF_Object> pOption = pArray->GetDirectObjectAt(index);
  if (!pOption) {
    return WideString();
  }

  const CPDF_Array* pOptionArray = pOption->AsArray();
  if (pOptionArray) {
    pOption = pOptionArray->GetDirectObjectAt(sub_index);
  }

  if (!pOption) {
    return WideString();
  }

  const CPDF_String* pString = pOption->AsString();
  return pString ? pString->GetUnicodeText() : WideString();
}

WideString CPDF_FormField::GetOptionLabel(int index) const {
  return GetOptionText(index, 1);
}

WideString CPDF_FormField::GetOptionValue(int index) const {
  return GetOptionText(index, 0);
}

int CPDF_FormField::FindOption(const WideString& csOptValue) const {
  for (int i = 0; i < CountOptions(); i++) {
    if (GetOptionValue(i) == csOptValue) {
      return i;
    }
  }
  return -1;
}

void CPDF_FormField::CheckControl(int iControlIndex,
                                  bool bChecked,
                                  NotificationOption notify) {
  DCHECK(GetType() == kCheckBox || GetType() == kRadioButton);
  CPDF_FormControl* pControl = GetControl(iControlIndex);
  if (!pControl) {
    return;
  }
  if (!bChecked && pControl->IsChecked() == bChecked) {
    return;
  }
  const WideString csWExport = pControl->GetExportValue();
  int iCount = CountControls();
  for (int i = 0; i < iCount; i++) {
    CPDF_FormControl* pCtrl = GetControl(i);
    if (is_unison_) {
      WideString csEValue = pCtrl->GetExportValue();
      if (csEValue == csWExport) {
        if (pCtrl->GetOnStateName() == pControl->GetOnStateName()) {
          pCtrl->CheckControl(bChecked);
        } else if (bChecked) {
          pCtrl->CheckControl(false);
        }
      } else if (bChecked) {
        pCtrl->CheckControl(false);
      }
    } else {
      if (i == iControlIndex) {
        pCtrl->CheckControl(bChecked);
      } else if (bChecked) {
        pCtrl->CheckControl(false);
      }
    }
  }

  RetainPtr<const CPDF_Object> pOpt = GetFieldAttrInternal("Opt");
  if (!ToArray(pOpt)) {
    ByteString csBExport = PDF_EncodeText(csWExport.AsStringView());
    if (bChecked) {
      dict_->SetNewFor<CPDF_Name>(pdfium::form_fields::kV, csBExport);
    } else {
      ByteString csV;
      const CPDF_Object* pV = GetValueObject();
      if (pV) {
        csV = pV->GetString();
      }
      if (csV == csBExport) {
        dict_->SetNewFor<CPDF_Name>(pdfium::form_fields::kV, "Off");
      }
    }
  } else if (bChecked) {
    dict_->SetNewFor<CPDF_Name>(pdfium::form_fields::kV,
                                ByteString::FormatInteger(iControlIndex));
  }
  if (notify == NotificationOption::kNotify) {
    form_->NotifyAfterCheckedStatusChange(this);
  }
}

WideString CPDF_FormField::GetCheckValue(bool bDefault) const {
  DCHECK(GetType() == kCheckBox || GetType() == kRadioButton);
  auto csExport = WideString::FromASCII("Off");
  int iCount = CountControls();
  for (int i = 0; i < iCount; i++) {
    CPDF_FormControl* pControl = GetControl(i);
    bool bChecked =
        bDefault ? pControl->IsDefaultChecked() : pControl->IsChecked();
    if (bChecked) {
      csExport = pControl->GetExportValue();
      break;
    }
  }
  return csExport;
}

bool CPDF_FormField::SetCheckValue(const WideString& value,
                                   bool bDefault,
                                   NotificationOption notify) {
  DCHECK(GetType() == kCheckBox || GetType() == kRadioButton);
  int iCount = CountControls();
  for (int i = 0; i < iCount; i++) {
    CPDF_FormControl* pControl = GetControl(i);
    WideString csExport = pControl->GetExportValue();
    bool val = csExport == value;
    if (!bDefault) {
      CheckControl(GetControlIndex(pControl), val,
                   NotificationOption::kDoNotNotify);
    }
    if (val) {
      break;
    }
  }
  if (notify == NotificationOption::kNotify) {
    form_->NotifyAfterCheckedStatusChange(this);
  }
  return true;
}

int CPDF_FormField::GetTopVisibleIndex() const {
  RetainPtr<const CPDF_Object> pObj = GetFieldAttrInternal("TI");
  return pObj ? pObj->GetInteger() : 0;
}

int CPDF_FormField::CountSelectedOptions() const {
  RetainPtr<const CPDF_Array> pArray = ToArray(GetSelectedIndicesObject());
  return pArray ? fxcrt::CollectionSize<int>(*pArray) : 0;
}

int CPDF_FormField::GetSelectedOptionIndex(int index) const {
  if (index < 0) {
    return 0;
  }

  RetainPtr<const CPDF_Array> pArray = ToArray(GetSelectedIndicesObject());
  if (!pArray) {
    return -1;
  }

  return index < fxcrt::CollectionSize<int>(*pArray)
             ? pArray->GetIntegerAt(index)
             : -1;
}

bool CPDF_FormField::IsSelectedOption(const WideString& wsOptValue) const {
  RetainPtr<const CPDF_Object> pValueObject = GetValueObject();
  if (!pValueObject) {
    return false;
  }

  const CPDF_Array* pValueArray = pValueObject->AsArray();
  if (pValueArray) {
    CPDF_ArrayLocker locker(pValueArray);
    for (const auto& pObj : locker) {
      if (pObj->IsString() && pObj->GetUnicodeText() == wsOptValue) {
        return true;
      }
    }
  }

  return pValueObject->IsString() &&
         pValueObject->GetUnicodeText() == wsOptValue;
}

bool CPDF_FormField::IsSelectedIndex(int iOptIndex) const {
  RetainPtr<const CPDF_Object> pSelectedIndicesObject =
      GetSelectedIndicesObject();
  if (!pSelectedIndicesObject) {
    return false;
  }

  const CPDF_Array* pSelectedIndicesArray = pSelectedIndicesObject->AsArray();
  if (pSelectedIndicesArray) {
    CPDF_ArrayLocker locker(pSelectedIndicesArray);
    for (const auto& pObj : locker) {
      if (pObj->IsNumber() && pObj->GetInteger() == iOptIndex) {
        return true;
      }
    }
  }

  return pSelectedIndicesObject->IsNumber() &&
         pSelectedIndicesObject->GetInteger() == iOptIndex;
}

void CPDF_FormField::SelectOption(int iOptIndex) {
  RetainPtr<CPDF_Array> pArray = dict_->GetOrCreateArrayFor("I");
  for (size_t i = 0; i < pArray->size(); i++) {
    int iFind = pArray->GetIntegerAt(i);
    if (iFind == iOptIndex) {
      return;
    }

    if (iFind > iOptIndex) {
      pArray->InsertNewAt<CPDF_Number>(i, iOptIndex);
      return;
    }
  }
  pArray->AppendNew<CPDF_Number>(iOptIndex);
}

bool CPDF_FormField::UseSelectedIndicesObject() const {
  CHECK(IsComboOrListField(GetType()));

  RetainPtr<const CPDF_Object> pSelectedIndicesObject =
      GetSelectedIndicesObject();
  if (!pSelectedIndicesObject) {
    return false;
  }

  // If there's not value object, then just use the indices object.
  RetainPtr<const CPDF_Object> pValueObject = GetValueObject();
  if (!pValueObject) {
    return true;
  }

  // Verify that the selected indices object is either an array or a number and
  // count the number of indices.
  size_t selected_indices_size;
  const CPDF_Array* pSelectedIndicesArray = pSelectedIndicesObject->AsArray();
  if (pSelectedIndicesArray) {
    selected_indices_size = pSelectedIndicesArray->size();
  } else if (pSelectedIndicesObject->IsNumber()) {
    selected_indices_size = 1;
  } else {
    return false;
  }

  // Verify that the number of values is equal to |selected_indices_size|. Then,
  // count the number of occurrences of each of the distinct values in the
  // values object.
  std::map<WideString, size_t> values;
  const CPDF_Array* pValueArray = pValueObject->AsArray();
  if (pValueArray) {
    if (pValueArray->size() != selected_indices_size) {
      return false;
    }
    CPDF_ArrayLocker locker(pValueArray);
    for (const auto& pObj : locker) {
      if (pObj->IsString()) {
        values[pObj->GetUnicodeText()]++;
      }
    }
  } else if (pValueObject->IsString()) {
    if (selected_indices_size != 1) {
      return false;
    }
    values[pValueObject->GetUnicodeText()]++;
  }

  // Validate each index in the selected indices object. Then, verify that items
  // identified by selected indices entry do not differ from those in the values
  // entry of the field dictionary.
  const int num_options = CountOptions();
  if (pSelectedIndicesArray) {
    CPDF_ArrayLocker locker(pSelectedIndicesArray);
    for (const auto& pObj : locker) {
      if (!pObj->IsNumber()) {
        return false;
      }

      int index = pObj->GetInteger();
      if (index < 0 || index >= num_options) {
        return false;
      }

      WideString wsOptValue = GetOptionValue(index);
      auto it = values.find(wsOptValue);
      if (it == values.end()) {
        return false;
      }

      it->second--;
      if (it->second == 0) {
        values.erase(it);
      }
    }

    return values.empty();
  }

  DCHECK(pSelectedIndicesObject->IsNumber());
  int index = pSelectedIndicesObject->GetInteger();
  if (index < 0 || index >= num_options) {
    return false;
  }

  return pdfium::Contains(values, GetOptionValue(index));
}

bool CPDF_FormField::NotifyListOrComboBoxBeforeChange(const WideString& value) {
  switch (GetType()) {
    case kListBox:
      return form_->NotifyBeforeSelectionChange(this, value);
    case kComboBox:
      return form_->NotifyBeforeValueChange(this, value);
    default:
      return true;
  }
}

void CPDF_FormField::NotifyListOrComboBoxAfterChange() {
  switch (GetType()) {
    case kListBox:
      form_->NotifyAfterSelectionChange(this);
      break;
    case kComboBox:
      form_->NotifyAfterValueChange(this);
      break;
    default:
      break;
  }
}

RetainPtr<const CPDF_Object> CPDF_FormField::GetFieldAttrInternal(
    ByteStringView name) const {
  return GetFieldAttrRecursive(dict_.Get(), name, 0);
}

const CPDF_Dictionary* CPDF_FormField::GetFieldDictInternal() const {
  return dict_.Get();
}

RetainPtr<const CPDF_Object> CPDF_FormField::GetDefaultValueObject() const {
  return GetFieldAttrInternal(pdfium::form_fields::kDV);
}

RetainPtr<const CPDF_Object> CPDF_FormField::GetValueObject() const {
  return GetFieldAttrInternal(pdfium::form_fields::kV);
}

RetainPtr<const CPDF_Object> CPDF_FormField::GetSelectedIndicesObject() const {
  CHECK(IsComboOrListField(GetType()));
  return GetFieldAttrInternal("I");
}

RetainPtr<const CPDF_Object> CPDF_FormField::GetValueOrSelectedIndicesObject()
    const {
  CHECK(IsComboOrListField(GetType()));
  RetainPtr<const CPDF_Object> pValue = GetValueObject();
  return pValue ? pValue : GetSelectedIndicesObject();
}

const std::vector<UnownedPtr<CPDF_FormControl>>& CPDF_FormField::GetControls()
    const {
  return form_->GetControlsForField(this);
}
