/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 nsTextEditorState_h__
#define nsTextEditorState_h__

#include "nsAutoPtr.h"
#include "nsString.h"
#include "nsITextControlElement.h"
#include "nsITextControlFrame.h"
#include "nsCycleCollectionParticipant.h"
#include "mozilla/dom/Element.h"
#include "mozilla/Attributes.h"
#include "mozilla/WeakPtr.h"

class nsTextInputListener;
class nsTextControlFrame;
class nsTextInputSelectionImpl;
class nsAnonDivObserver;
class nsISelectionController;
class nsFrameSelection;
class nsIEditor;
class nsITextControlElement;
class nsFrame;

namespace mozilla {
namespace dom {
class HTMLInputElement;
} // namespace dom
} // namespace mozilla

/**
 * nsTextEditorState is a class which is responsible for managing the state of
 * plaintext controls.  This currently includes the following HTML elements:
 *   <input type=text>
 *   <input type=password>
 *   <textarea>
 * and also XUL controls such as <textbox> which use one of these elements behind
 * the scenes.
 *
 * This class is held as a member of HTMLInputElement and nsHTMLTextAreaElement.
 * The public functions in this class include the public APIs which content/ uses.
 * Layout code uses the nsITextControlElement interface to invoke functions on this
 * class.
 *
 * The design motivation behind this class is maintaining all of the things which
 * collectively are considered the "state" of the text control in a single location.
 * This state includes several things:
 *
 *  * The control's value.  This value is stored in the mValue member, and is only
 *    used when there is no frame for the control, or when the editor object has
 *    not been initialized yet.
 *
 *  * The control's associated frame.  This value is stored in the mBoundFrame member.
 *    A text control might never have an associated frame during its life cycle,
 *    or might have several different ones, but at any given moment in time there is
 *    a maximum of 1 bound frame to each text control.
 *
 *  * The control's associated editor.  This value is stored in the mEditor member.
 *    An editor is initilized for the control only when necessary (that is, when either
 *    the user is about to interact with the text control, or when some other code
 *    needs to access the editor object.  Without a frame bound to the control, an
 *    editor is never initialzied.  Once initialized, the editor might outlive the frame,
 *    in which case the same editor will be used if a new frame gets bound to the
 *    text control.
 *
 *  * The anonymous content associated with the text control's frame, including the
 *    value div (the DIV element responsible for holding the value of the text control)
 *    and the placeholder div (the DIV element responsible for holding the placeholder
 *    value of the text control.)  These values are stored in the mRootNode and
 *    mPlaceholderDiv members, respectively.  They will be created when a
 *    frame is bound to the text control.  They will be destroyed when the frame is
 *    unbound from the object.  We could try and hold on to the anonymous content
 *    between different frames, but unfortunately that is not currently possible
 *    because they are not unbound from the document in time.
 *
 *  * The frame selection controller.  This value is stored in the mSelCon member.
 *    The frame selection controller is responsible for maintaining the selection state
 *    on a frame.  It is created when a frame is bound to the text control element,
 *    and will be destroy when the frame is being unbound from the text control element.
 *    It is created alongside with the frame selection object which is stored in the
 *    mFrameSel member.
 *
 *  * The editor text listener.  This value is stored in the mTextListener member.
 *    Its job is to listen to selection and keyboard events, and act accordingly.
 *    It is created when an a frame is first bound to the control, and will be destroyed
 *    when the frame is unbound from the text control element.
 *
 *  * The editor's cached value.  This value is stored in the mCachedValue member.
 *    It is used to improve the performance of append operations to the text
 *    control.  A mutation observer stored in the mMutationObserver has the job of
 *    invalidating this cache when the anonymous contect containing the value is
 *    changed.
 *
 *  * The editor's cached selection properties.  These vales are stored in the
 *    mSelectionProperties member, and include the selection's start, end and
 *    direction. They are only used when there is no frame available for the
 *    text field.
 *
 *
 * As a general rule, nsTextEditorState objects own the value of the text control, and any
 * attempt to retrieve or set the value must be made through those objects.  Internally,
 * the value can be represented in several different ways, based on the state the control is
 * in.
 *
 *   * When the control is first initialized, its value is equal to the default value of
 *     the DOM node.  For <input> text controls, this default value is the value of the
 *     value attribute.  For <textarea> elements, this default value is the value of the
 *     text node children of the element.
 *
 *   * If the value has been changed through the DOM node (before the editor for the object
 *     is initialized), the value is stored as a simple string inside the mValue member of
 *     the nsTextEditorState object.
 *
 *   * If an editor has been initialized for the control, the value is set and retrievd via
 *     the nsIPlaintextEditor interface, and is internally managed by the editor as the
 *     native anonymous content tree attached to the control's frame.
 *
 *   * If the text editor state object is unbound from the control's frame, the value is
 *     transferred to the mValue member variable, and will be managed there until a new
 *     frame is bound to the text editor state object.
 */

class RestoreSelectionState;

class nsTextEditorState : public mozilla::SupportsWeakPtr<nsTextEditorState> {
public:
  MOZ_DECLARE_WEAKREFERENCE_TYPENAME(nsTextEditorState)
  explicit nsTextEditorState(nsITextControlElement* aOwningElement);
  ~nsTextEditorState();

  void Traverse(nsCycleCollectionTraversalCallback& cb);
  void Unlink();

  nsIEditor* GetEditor();
  nsISelectionController* GetSelectionController() const;
  nsFrameSelection* GetConstFrameSelection();
  nsresult BindToFrame(nsTextControlFrame* aFrame);
  void UnbindFromFrame(nsTextControlFrame* aFrame);
  nsresult PrepareEditor(const nsAString *aValue = nullptr);
  void InitializeKeyboardEventListeners();

  enum SetValueFlags
  {
    // The call is for internal processing.
    eSetValue_Internal              = 0,
    // The value is changed by a call of setUserInput() from chrome.
    eSetValue_BySetUserInput        = 1 << 0,
    // The value is changed by changing value attribute of the element or
    // something like setRangeText().
    eSetValue_ByContent             = 1 << 1,
    // Whether the value change should be notified to the frame/contet nor not.
    eSetValue_Notify                = 1 << 2
  };
  MOZ_WARN_UNUSED_RESULT bool SetValue(const nsAString& aValue,
                                       uint32_t aFlags);
  void GetValue(nsAString& aValue, bool aIgnoreWrap) const;
  void EmptyValue() { if (mValue) mValue->Truncate(); }
  bool IsEmpty() const { return mValue ? mValue->IsEmpty() : true; }

  nsresult CreatePlaceholderNode();

  mozilla::dom::Element* GetRootNode() {
    if (!mRootNode)
      CreateRootNode();
    return mRootNode;
  }
  mozilla::dom::Element* GetPlaceholderNode() {
    return mPlaceholderDiv;
  }

  bool IsSingleLineTextControl() const {
    return mTextCtrlElement->IsSingleLineTextControl();
  }
  bool IsTextArea() const {
    return mTextCtrlElement->IsTextArea();
  }
  bool IsPlainTextControl() const {
    return mTextCtrlElement->IsPlainTextControl();
  }
  bool IsPasswordTextControl() const {
    return mTextCtrlElement->IsPasswordTextControl();
  }
  int32_t GetCols() {
    return mTextCtrlElement->GetCols();
  }
  int32_t GetWrapCols() {
    return mTextCtrlElement->GetWrapCols();
  }
  int32_t GetRows() {
    return mTextCtrlElement->GetRows();
  }

  // placeholder methods
  void UpdatePlaceholderVisibility(bool aNotify);
  bool GetPlaceholderVisibility() {
    return mPlaceholderVisibility;
  }
  void UpdatePlaceholderText(bool aNotify); 

  /**
   * Get the maxlength attribute
   * @param aMaxLength the value of the max length attr
   * @returns false if attr not defined
   */
  bool GetMaxLength(int32_t* aMaxLength);

  void ClearValueCache() { mCachedValue.Truncate(); }

  void HideSelectionIfBlurred();

  struct SelectionProperties {
    SelectionProperties() : mStart(0), mEnd(0),
      mDirection(nsITextControlFrame::eForward) {}
    bool IsDefault() const {
      return mStart == 0 && mEnd == 0 &&
             mDirection == nsITextControlFrame::eForward;
    }
    int32_t mStart, mEnd;
    nsITextControlFrame::SelectionDirection mDirection;
  };

  bool IsSelectionCached() const;
  SelectionProperties& GetSelectionProperties();
  void WillInitEagerly() { mSelectionRestoreEagerInit = true; }
  bool HasNeverInitializedBefore() const { return !mEverInited; }

  void UpdateEditableState(bool aNotify) {
    if (mRootNode) {
      mRootNode->UpdateEditableState(aNotify);
    }
  }

private:
  friend class RestoreSelectionState;

  // not copy constructible
  nsTextEditorState(const nsTextEditorState&);
  // not assignable
  void operator= (const nsTextEditorState&);

  nsresult CreateRootNode();

  void ValueWasChanged(bool aNotify);

  void DestroyEditor();
  void Clear();

  nsresult InitializeRootNode();

  void FinishedRestoringSelection();

  mozilla::dom::HTMLInputElement* GetParentNumberControl(nsFrame* aFrame) const;

  bool EditorHasComposition();

  class InitializationGuard {
  public:
    explicit InitializationGuard(nsTextEditorState& aState) :
      mState(aState),
      mGuardSet(false)
    {
      if (!mState.mInitializing) {
        mGuardSet = true;
        mState.mInitializing = true;
      }
    }
    ~InitializationGuard() {
      if (mGuardSet) {
        mState.mInitializing = false;
      }
    }
    bool IsInitializingRecursively() const {
      return !mGuardSet;
    }
  private:
    nsTextEditorState& mState;
    bool mGuardSet;
  };
  friend class InitializationGuard;
  friend class PrepareEditorEvent;

  // The text control element owns this object, and ensures that this object
  // has a smaller lifetime.
  nsITextControlElement* const MOZ_NON_OWNING_REF mTextCtrlElement;
  RefPtr<nsTextInputSelectionImpl> mSelCon;
  RefPtr<RestoreSelectionState> mRestoringSelection;
  nsCOMPtr<nsIEditor> mEditor;
  nsCOMPtr<mozilla::dom::Element> mRootNode;
  nsCOMPtr<mozilla::dom::Element> mPlaceholderDiv;
  nsTextControlFrame* mBoundFrame;
  RefPtr<nsTextInputListener> mTextListener;
  nsAutoPtr<nsCString> mValue;
  RefPtr<nsAnonDivObserver> mMutationObserver;
  mutable nsString mCachedValue; // Caches non-hard-wrapped value on a multiline control.
  // mValueBeingSet is available only while SetValue() is requesting to commit
  // composition.  I.e., this is valid only while mIsCommittingComposition is
  // true.  While active composition is being committed, GetValue() needs
  // the latest value which is set by SetValue().  So, this is cache for that.
  nsString mValueBeingSet;
  SelectionProperties mSelectionProperties;
  bool mEverInited; // Have we ever been initialized?
  bool mEditorInitialized;
  bool mInitializing; // Whether we're in the process of initialization
  bool mValueTransferInProgress; // Whether a value is being transferred to the frame
  bool mSelectionCached; // Whether mSelectionProperties is valid
  mutable bool mSelectionRestoreEagerInit; // Whether we're eager initing because of selection restore
  bool mPlaceholderVisibility;
  bool mIsCommittingComposition;
};

inline void
ImplCycleCollectionUnlink(nsTextEditorState& aField)
{
  aField.Unlink();
}

inline void
ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
                            nsTextEditorState& aField,
                            const char* aName,
                            uint32_t aFlags = 0)
{
  aField.Traverse(aCallback);
}

#endif
