/* 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 _nsAutoCompleteController_
#define _nsAutoCompleteController_

#include "nsIAutoCompleteController.h"

#include "nsCOMPtr.h"
#include "nsIAutoCompleteInput.h"
#include "nsIAutoCompletePopup.h"
#include "nsIAutoCompleteResult.h"
#include "nsIAutoCompleteSearch.h"
#include "nsINamed.h"
#include "nsString.h"
#include "nsITimer.h"
#include "nsTArray.h"
#include "nsCOMArray.h"
#include "nsCycleCollectionParticipant.h"
#include "mozilla/dom/Element.h"

class nsAutoCompleteController final : public nsIAutoCompleteController,
                                       public nsIAutoCompleteObserver,
                                       public nsITimerCallback,
                                       public nsINamed {
 public:
  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsAutoCompleteController,
                                           nsIAutoCompleteController)
  NS_DECL_NSIAUTOCOMPLETECONTROLLER
  NS_DECL_NSIAUTOCOMPLETEOBSERVER
  NS_DECL_NSITIMERCALLBACK
  NS_DECL_NSINAMED

  nsAutoCompleteController();

 protected:
  MOZ_CAN_RUN_SCRIPT virtual ~nsAutoCompleteController();

  /**
   * SetValueOfInputTo() sets value of mInput to aValue.
   */
  void SetValueOfInputTo(const nsString& aValue);

  /**
   * SetSearchStringInternal() sets both mSearchString and mSetValue to
   * aSearchString.
   */
  void SetSearchStringInternal(const nsAString& aSearchString) {
    mSearchString = mSetValue = aSearchString;
  }

  MOZ_CAN_RUN_SCRIPT nsresult OpenPopup();
  MOZ_CAN_RUN_SCRIPT nsresult ClosePopup();

  nsresult StartSearch();

  MOZ_CAN_RUN_SCRIPT nsresult DoSearches();
  nsresult BeforeSearches();
  MOZ_CAN_RUN_SCRIPT nsresult StartSearches();
  MOZ_CAN_RUN_SCRIPT void AfterSearches();
  nsresult ClearSearchTimer();
  void MaybeCompletePlaceholder();

  MOZ_CAN_RUN_SCRIPT nsresult ProcessResult(int32_t aSearchIndex,
                                            nsIAutoCompleteResult* aResult);
  MOZ_CAN_RUN_SCRIPT nsresult PostSearchCleanup();

  MOZ_CAN_RUN_SCRIPT nsresult EnterMatch(bool aIsPopupSelection,
                                         mozilla::dom::Event* aEvent);
  nsresult RevertTextValue();

  nsresult CompleteDefaultIndex(int32_t aResultIndex);
  nsresult CompleteValue(nsString& aValue);

  nsresult GetResultAt(int32_t aIndex, nsIAutoCompleteResult** aResult,
                       int32_t* aMatchIndex);
  nsresult GetResultValueAt(int32_t aIndex, bool aGetFinalValue,
                            nsAString& _retval);
  nsresult GetResultLabelAt(int32_t aIndex, nsAString& _retval);

  /**
   * Returns autocomplete popup for the autocomplete input. nsIAutoCompleteInput
   * can be implemented two different ways to return a popup. The first one is
   * to return a popup object implementing nsIAutoCompletePopup interface,
   * the second one is a DOM element representing a popup and implementing
   * that interface.
   */
  already_AddRefed<nsIAutoCompletePopup> GetPopup() {
    nsCOMPtr<nsIAutoCompletePopup> popup;
    mInput->GetPopup(getter_AddRefs(popup));
    if (popup) {
      return popup.forget();
    }

    nsCOMPtr<mozilla::dom::Element> popupEl;
    mInput->GetPopupElement(getter_AddRefs(popupEl));
    if (popupEl) {
      return popupEl->AsAutoCompletePopup();
    }
    return nullptr;
  }

 private:
  nsresult GetResultValueLabelAt(int32_t aIndex, bool aGetFinalValue,
                                 bool aGetValue, nsAString& _retval);

  /**
   * Gets and validates the defaultComplete result and the relative
   * defaultIndex value.
   *
   * @param aResultIndex
   *        Index of the defaultComplete result to be used.  Pass -1 to search
   *        for the first result providing a valid defaultIndex.
   * @param _result
   *        The found result.
   * @param _defaultIndex
   *        The defaultIndex relative to _result.
   */
  nsresult GetDefaultCompleteResult(int32_t aResultIndex,
                                    nsIAutoCompleteResult** _result,
                                    int32_t* _defaultIndex);

  /**
   * Gets the defaultComplete value to be suggested to the user.
   *
   * @param aResultIndex
   *        Index of the defaultComplete result to be used.
   * @param aPreserveCasing
   *        Whether user casing should be preserved.
   * @param _retval
   *        The value to be completed.
   */
  nsresult GetDefaultCompleteValue(int32_t aResultIndex, bool aPreserveCasing,
                                   nsAString& _retval);

  /**
   * Gets the defaultComplete value to be used when the user confirms the
   * current match.
   * The value is returned only if it case-insensitively matches the current
   * input text, otherwise the method returns NS_ERROR_FAILURE.
   * This happens because we don't want to replace text if the user backspaces
   * just before Enter.
   *
   * @param _retval
   *        The value to be completed.
   */
  nsresult GetFinalDefaultCompleteValue(nsAString& _retval);

  nsresult ClearResults(bool aIsSearching = false);

  nsresult MatchIndexToSearch(int32_t aMatchIndex, int32_t* aSearchIndex,
                              int32_t* aItemIndex);

  // members //////////////////////////////////////////

  nsCOMPtr<nsIAutoCompleteInput> mInput;

  nsCOMArray<nsIAutoCompleteSearch> mSearches;
  // This is used as a sparse array, always use SafeObjectAt to access it.
  nsCOMArray<nsIAutoCompleteResult> mResults;
  // Temporarily keeps the results alive while invoking startSearch() for each
  // search.  This is needed to allow the searches to reuse the previous result,
  // since otherwise the first search clears mResults.
  nsCOMArray<nsIAutoCompleteResult> mResultCache;

  nsCOMPtr<nsITimer> mTimer;

  // mSearchString stores value which is the original value of the input or
  // typed by the user.  When user is choosing an item from the popup, this
  // is NOT modified by the item because this is used for reverting the input
  // value when user cancels choosing an item from the popup.
  // This should be set through only SetSearchStringInternal().
  nsString mSearchString;
  nsString mPlaceholderCompletionString;
  // mSetValue stores value which is expected in the input.  So, if input's
  // value and mSetValue are different, it means somebody has changed the
  // value like JS of the web content.
  // This is set only by SetValueOfInputTo() or when modifying mSearchString
  // through SetSearchStringInternal().
  nsString mSetValue;
  bool mDefaultIndexCompleted;
  bool mPopupClosedByCompositionStart;

  // Whether autofill is allowed for the next search.
  bool mProhibitAutoFill;

  // Indicates whether the user cleared the autofilled part, returning to the
  // originally entered search string.
  bool mUserClearedAutoFill;

  enum CompositionState {
    eCompositionState_None,
    eCompositionState_Composing,
    eCompositionState_Committing
  };
  CompositionState mCompositionState;
  uint16_t mSearchStatus;
  uint32_t mMatchCount;
  uint32_t mSearchesOngoing;
  uint32_t mSearchesFailed;
  // The index of the match on the popup that was selected using the keyboard,
  // if the completeselectedindex attribute is set.
  // This is used to distinguish that selection (which would have been put in
  // the input on being selected) from a moused-over selectedIndex value. This
  // distinction is used to prevent mouse moves from inadvertently changing
  // what happens once the user hits Enter on the keyboard.
  // See bug 1043584 for more details.
  int32_t mCompletedSelectionIndex;
};

#endif /* _nsAutoCompleteController_ */
