/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef nsIFormControl_h_
#define nsIFormControl_h_

#include "mozilla/EventForwards.h"
#include "mozilla/StaticPrefs_dom.h"
#include "nsISupports.h"

class nsINode;
namespace mozilla {
class PresState;
namespace dom {
class Element;
class EventTarget;
class FormData;
class HTMLFieldSetElement;
class HTMLFormElement;
}  // namespace dom
}  // namespace mozilla

// Elements with different types, the value is used as a mask.
// When changing the order, adding or removing elements, be sure to update
// the static_assert checks accordingly.
constexpr uint8_t kFormControlButtonElementMask = 0x40;  // 0b01000000
constexpr uint8_t kFormControlInputElementMask = 0x80;   // 0b10000000

enum class FormControlType : uint8_t {
  Fieldset = 1,
  Output,
  Select,
  Textarea,
  Object,
  FormAssociatedCustomElement,

  LastWithoutSubtypes = FormAssociatedCustomElement,

  ButtonButton = kFormControlButtonElementMask + 1,
  ButtonReset,
  ButtonSubmit,
  LastButtonElement = ButtonSubmit,

  InputButton = kFormControlInputElementMask + 1,
  InputCheckbox,
  InputColor,
  InputDate,
  InputEmail,
  InputFile,
  InputHidden,
  InputReset,
  InputImage,
  InputMonth,
  InputNumber,
  InputPassword,
  InputRadio,
  InputSearch,
  InputSubmit,
  InputTel,
  InputText,
  InputTime,
  InputUrl,
  InputRange,
  InputWeek,
  InputDatetimeLocal,
  LastInputElement = InputDatetimeLocal,
};

static_assert(uint8_t(FormControlType::LastWithoutSubtypes) <
                  kFormControlButtonElementMask,
              "Too many FormControlsTypes without sub-types");
static_assert(uint8_t(FormControlType::LastButtonElement) <
                  kFormControlInputElementMask,
              "Too many ButtonElementTypes");
static_assert(uint32_t(FormControlType::LastInputElement) < (1 << 8),
              "Too many form control types");

#define NS_IFORMCONTROL_IID \
  {0x4b89980c, 0x4dcd, 0x428f, {0xb7, 0xad, 0x43, 0x5b, 0x93, 0x29, 0x79, 0xec}}

/**
 * Interface which all form controls (e.g. buttons, checkboxes, text,
 * radio buttons, select, etc) implement in addition to their dom specific
 * interface.
 */
class nsIFormControl : public nsISupports {
 public:
  nsIFormControl(FormControlType aType) : mType(aType) {}

  NS_INLINE_DECL_STATIC_IID(NS_IFORMCONTROL_IID)

  static nsIFormControl* FromEventTarget(mozilla::dom::EventTarget* aTarget);
  static nsIFormControl* FromEventTargetOrNull(
      mozilla::dom::EventTarget* aTarget);
  static const nsIFormControl* FromEventTarget(
      const mozilla::dom::EventTarget* aTarget);
  static const nsIFormControl* FromEventTargetOrNull(
      const mozilla::dom::EventTarget* aTarget);

  static nsIFormControl* FromNode(nsINode* aNode);
  static nsIFormControl* FromNodeOrNull(nsINode* aNode);
  static const nsIFormControl* FromNode(const nsINode* aNode);
  static const nsIFormControl* FromNodeOrNull(const nsINode* aNode);

  /**
   * Get the fieldset for this form control.
   * @return the fieldset
   */
  virtual mozilla::dom::HTMLFieldSetElement* GetFieldSet() = 0;

  /**
   * Get the form for this form control, retargeted to the appropriate scope.
   * @return the form
   */
  virtual mozilla::dom::Element* GetFormForBindings() const = 0;

  /**
   * Get the form for this form control.
   * @return the form
   */
  virtual mozilla::dom::HTMLFormElement* GetFormInternal() const = 0;

  /**
   * Set the form for this form control.
   * @param aForm the form.  This must not be null.
   *
   * @note that when setting the form the control is not added to the
   * form.  It adds itself when it gets bound to the tree thereafter,
   * so that it can be properly sorted with the other controls in the
   * form.
   */
  virtual void SetForm(mozilla::dom::HTMLFormElement* aForm) = 0;

  /**
   * Tell the control to forget about its form.
   *
   * @param aRemoveFromForm set false if you do not want this element removed
   *        from the form.  (Used by nsFormControlList::Clear())
   * @param aUnbindOrDelete set true if the element is being deleted or unbound
   *        from tree.
   */
  virtual void ClearForm(bool aRemoveFromForm, bool aUnbindOrDelete) = 0;

  /**
   * Get the type of this control as an int (see NS_FORM_* above)
   * @return the type of this control
   */
  FormControlType ControlType() const { return mType; }

  /**
   * Reset this form control (as it should be when the user clicks the Reset
   * button)
   */
  MOZ_CAN_RUN_SCRIPT NS_IMETHOD Reset() = 0;

  /**
   * Tells the form control to submit its names and values to the form data
   * object
   *
   * @param aFormData the form data to notify of names/values/files to submit
   */
  NS_IMETHOD
  SubmitNamesValues(mozilla::dom::FormData* aFormData) = 0;

  /**
   * Returns whether this is a control which submits the form when activated by
   * the user.
   * @return whether this is a submit control.
   */
  inline bool IsSubmitControl() const;

  /**
   * Returns whether this is a text control.
   * @param  aExcludePassword  to have NS_FORM_INPUT_PASSWORD returning false.
   * @return whether this is a text control.
   */
  inline bool IsTextControl(bool aExcludePassword) const;

  /**
   * Returns whether this is a single line text control.
   * @param  aExcludePassword  to have NS_FORM_INPUT_PASSWORD returning false.
   * @return whether this is a single line text control.
   */
  inline bool IsSingleLineTextControl(bool aExcludePassword) const;

  /**
   * Returns whether this is a submittable form control.
   * @return whether this is a submittable form control.
   */
  inline bool IsSubmittableControl() const;

  /**
   * https://html.spec.whatwg.org/multipage/forms.html#concept-button
   */
  inline bool IsConceptButton() const;

  /**
   * Returns whether this is an ordinal button or a concept button that has no
   * form associated.
   */
  inline bool IsButtonControl() const;

  /**
   * Returns whether this form control can have draggable children.
   * @return whether this form control can have draggable children.
   */
  inline bool AllowDraggableChildren() const;

  // Returns a number for this form control that is unique within its
  // owner document.  This is used by nsContentUtils::GenerateStateKey
  // to identify form controls that are inserted into the document by
  // the parser.  -1 is returned for form controls with no state or
  // which were inserted into the document by some other means than
  // the parser from the network.
  virtual int32_t GetParserInsertedControlNumberForStateKey() const {
    return -1;
  };

 protected:
  /**
   * Returns whether mType corresponds to a single line text control type.
   * @param aExcludePassword to have NS_FORM_INPUT_PASSWORD ignored.
   * @param aType the type to be tested.
   * @return whether mType corresponds to a single line text control type.
   */
  inline static bool IsSingleLineTextControl(bool aExcludePassword,
                                             FormControlType);

  inline static bool IsButtonElement(FormControlType aType) {
    return uint8_t(aType) & kFormControlButtonElementMask;
  }

  inline static bool IsInputElement(FormControlType aType) {
    return uint8_t(aType) & kFormControlInputElementMask;
  }

  FormControlType mType;
};

bool nsIFormControl::IsSubmitControl() const {
  FormControlType type = ControlType();
  return type == FormControlType::InputSubmit ||
         type == FormControlType::InputImage ||
         type == FormControlType::ButtonSubmit;
}

bool nsIFormControl::IsTextControl(bool aExcludePassword) const {
  FormControlType type = ControlType();
  return type == FormControlType::Textarea ||
         IsSingleLineTextControl(aExcludePassword, type);
}

bool nsIFormControl::IsSingleLineTextControl(bool aExcludePassword) const {
  return IsSingleLineTextControl(aExcludePassword, ControlType());
}

/*static*/
bool nsIFormControl::IsSingleLineTextControl(bool aExcludePassword,
                                             FormControlType aType) {
  switch (aType) {
    case FormControlType::InputText:
    case FormControlType::InputEmail:
    case FormControlType::InputSearch:
    case FormControlType::InputTel:
    case FormControlType::InputUrl:
    case FormControlType::InputNumber:
    // TODO: those are temporary until bug 773205 is fixed.
    case FormControlType::InputMonth:
    case FormControlType::InputWeek:
      return true;
    case FormControlType::InputPassword:
      return !aExcludePassword;
    default:
      return false;
  }
}

bool nsIFormControl::IsSubmittableControl() const {
  auto type = ControlType();
  return type == FormControlType::Object || type == FormControlType::Textarea ||
         type == FormControlType::Select || IsButtonElement(type) ||
         IsInputElement(type);
}

bool nsIFormControl::IsConceptButton() const {
  auto type = ControlType();
  return IsSubmitControl() || type == FormControlType::InputReset ||
         type == FormControlType::InputButton || IsButtonElement(type);
}

bool nsIFormControl::IsButtonControl() const {
  return IsConceptButton() && (!GetFormInternal() || !IsSubmitControl());
}

bool nsIFormControl::AllowDraggableChildren() const {
  auto type = ControlType();
  return type == FormControlType::Object || type == FormControlType::Fieldset ||
         type == FormControlType::Output ||
         type == FormControlType::FormAssociatedCustomElement;
}

#endif /* nsIFormControl_h_ */
