/*
 * Copyright (C) 2005, 2006, 2008 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef CompositeEditCommand_h
#define CompositeEditCommand_h

#include "core/CSSPropertyNames.h"
#include "core/CoreExport.h"
#include "core/editing/commands/EditCommand.h"
#include "core/editing/commands/EditingState.h"
#include "core/editing/commands/UndoStep.h"
#include "platform/wtf/Vector.h"

namespace blink {

class EditingStyle;
class Element;
class HTMLBRElement;
class HTMLElement;
class HTMLSpanElement;
class Text;

class CORE_EXPORT CompositeEditCommand : public EditCommand {
 public:
  enum ShouldPreserveSelection { kPreserveSelection, kDoNotPreserveSelection };
  enum ShouldPreserveStyle { kPreserveStyle, kDoNotPreserveStyle };

  ~CompositeEditCommand() override;

  const VisibleSelection& StartingSelection() const {
    return starting_selection_;
  }
  const VisibleSelection& EndingSelection() const { return ending_selection_; }

  void SetStartingSelection(const VisibleSelection&);
  void SetEndingSelection(const SelectionInDOMTree&);
  // TODO(yosin): |setEndingVisibleSelection()| will take |SelectionInUndoStep|
  // You should not use this function other than copying existing selection.
  void SetEndingVisibleSelection(const VisibleSelection&);

  void SetParent(CompositeEditCommand*) override;

  // Returns |false| if the command failed.  e.g. It's aborted.
  bool Apply();
  bool IsFirstCommand(EditCommand* command) {
    return !commands_.IsEmpty() && commands_.front() == command;
  }
  UndoStep* GetUndoStep() { return undo_step_.Get(); }
  UndoStep* EnsureUndoStep();
  // Append undo step from an already applied command.
  void AppendCommandToUndoStep(CompositeEditCommand*);

  virtual bool IsReplaceSelectionCommand() const;
  virtual bool IsTypingCommand() const;
  virtual bool IsCommandGroupWrapper() const;
  virtual bool IsDragAndDropCommand() const;
  virtual bool PreservesTypingStyle() const;
  virtual void SetShouldRetainAutocorrectionIndicator(bool);

  DECLARE_VIRTUAL_TRACE();

 protected:
  explicit CompositeEditCommand(Document&);

  //
  // sugary-sweet convenience functions to help create and apply edit commands
  // in composite commands
  //
  void AppendNode(Node*, ContainerNode* parent, EditingState*);
  void ApplyCommandToComposite(EditCommand*, EditingState*);
  void ApplyCommandToComposite(CompositeEditCommand*,
                               const VisibleSelection&,
                               EditingState*);
  void ApplyStyle(const EditingStyle*, EditingState*);
  void ApplyStyle(const EditingStyle*,
                  const Position& start,
                  const Position& end,
                  EditingState*);
  void ApplyStyledElement(Element*, EditingState*);
  void RemoveStyledElement(Element*, EditingState*);
  void DeleteSelection(EditingState*,
                       bool smart_delete = false,
                       bool merge_blocks_after_delete = true,
                       bool expand_for_special_elements = true,
                       bool sanitize_markup = true);
  virtual void DeleteTextFromNode(Text*, unsigned offset, unsigned count);
  bool IsRemovableBlock(const Node*);
  void InsertNodeAfter(Node*, Node* ref_child, EditingState*);
  void InsertNodeAt(Node*, const Position&, EditingState*);
  void InsertNodeAtTabSpanPosition(Node*, const Position&, EditingState*);
  void InsertNodeBefore(Node*,
                        Node* ref_child,
                        EditingState*,
                        ShouldAssumeContentIsAlwaysEditable =
                            kDoNotAssumeContentIsAlwaysEditable);
  void InsertParagraphSeparator(
      EditingState*,
      bool use_default_paragraph_element = false,
      bool paste_blockqutoe_into_unquoted_area = false);
  void InsertTextIntoNode(Text*, unsigned offset, const String& text);
  void MergeIdenticalElements(Element*, Element*, EditingState*);
  void RebalanceWhitespace();
  void RebalanceWhitespaceAt(const Position&);
  void RebalanceWhitespaceOnTextSubstring(Text*,
                                          int start_offset,
                                          int end_offset);
  void PrepareWhitespaceAtPositionForSplit(Position&);
  void ReplaceCollapsibleWhitespaceWithNonBreakingSpaceIfNeeded(
      const VisiblePosition&);
  bool CanRebalance(const Position&) const;
  void RemoveCSSProperty(Element*, CSSPropertyID);
  void RemoveElementAttribute(Element*, const QualifiedName& attribute);
  void RemoveChildrenInRange(Node*, unsigned from, unsigned to, EditingState*);
  virtual void RemoveNode(Node*,
                          EditingState*,
                          ShouldAssumeContentIsAlwaysEditable =
                              kDoNotAssumeContentIsAlwaysEditable);
  HTMLSpanElement* ReplaceElementWithSpanPreservingChildrenAndAttributes(
      HTMLElement*);
  void RemoveNodePreservingChildren(Node*,
                                    EditingState*,
                                    ShouldAssumeContentIsAlwaysEditable =
                                        kDoNotAssumeContentIsAlwaysEditable);
  void RemoveNodeAndPruneAncestors(Node*,
                                   EditingState*,
                                   Node* exclude_node = nullptr);
  void MoveRemainingSiblingsToNewParent(Node*,
                                        Node* past_last_node_to_move,
                                        Element* new_parent,
                                        EditingState*);
  void UpdatePositionForNodeRemovalPreservingChildren(Position&, Node&);
  void Prune(Node*, EditingState*, Node* exclude_node = nullptr);
  void ReplaceTextInNode(Text*,
                         unsigned offset,
                         unsigned count,
                         const String& replacement_text);
  Position ReplaceSelectedTextInNode(const String&);
  Position PositionOutsideTabSpan(const Position&);
  void SetNodeAttribute(Element*,
                        const QualifiedName& attribute,
                        const AtomicString& value);
  void SplitElement(Element*, Node* at_child);
  void SplitTextNode(Text*, unsigned offset);
  void SplitTextNodeContainingElement(Text*, unsigned offset);
  void WrapContentsInDummySpan(Element*);

  void DeleteInsignificantText(Text*, unsigned start, unsigned end);
  void DeleteInsignificantText(const Position& start, const Position& end);
  void DeleteInsignificantTextDownstream(const Position&);

  HTMLBRElement* AppendBlockPlaceholder(Element*, EditingState*);
  HTMLBRElement* InsertBlockPlaceholder(const Position&, EditingState*);
  HTMLBRElement* AddBlockPlaceholderIfNeeded(Element*, EditingState*);
  void RemovePlaceholderAt(const Position&);

  HTMLElement* InsertNewDefaultParagraphElementAt(const Position&,
                                                  EditingState*);

  HTMLElement* MoveParagraphContentsToNewBlockIfNecessary(const Position&,
                                                          EditingState*);

  void PushAnchorElementDown(Element*, EditingState*);

  void MoveParagraph(const VisiblePosition&,
                     const VisiblePosition&,
                     const VisiblePosition&,
                     EditingState*,
                     ShouldPreserveSelection = kDoNotPreserveSelection,
                     ShouldPreserveStyle = kPreserveStyle,
                     Node* constraining_ancestor = nullptr);
  void MoveParagraphs(const VisiblePosition&,
                      const VisiblePosition&,
                      const VisiblePosition&,
                      EditingState*,
                      ShouldPreserveSelection = kDoNotPreserveSelection,
                      ShouldPreserveStyle = kPreserveStyle,
                      Node* constraining_ancestor = nullptr);
  void MoveParagraphWithClones(
      const VisiblePosition& start_of_paragraph_to_move,
      const VisiblePosition& end_of_paragraph_to_move,
      HTMLElement* block_element,
      Node* outer_node,
      EditingState*);
  void CloneParagraphUnderNewElement(const Position& start,
                                     const Position& end,
                                     Node* outer_node,
                                     Element* block_element,
                                     EditingState*);
  void CleanupAfterDeletion(EditingState*,
                            VisiblePosition destination = VisiblePosition());

  bool BreakOutOfEmptyListItem(EditingState*);
  bool BreakOutOfEmptyMailBlockquotedParagraph(EditingState*);

  Position PositionAvoidingSpecialElementBoundary(const Position&,
                                                  EditingState*);

  Node* SplitTreeToNode(Node*, Node*, bool split_ancestor = false);

  static bool IsNodeVisiblyContainedWithin(Node&, const Range&);

  HeapVector<Member<EditCommand>> commands_;

 private:
  bool IsCompositeEditCommand() const final { return true; }

  VisibleSelection starting_selection_;
  VisibleSelection ending_selection_;
  Member<UndoStep> undo_step_;
};

DEFINE_TYPE_CASTS(CompositeEditCommand,
                  EditCommand,
                  command,
                  command->IsCompositeEditCommand(),
                  command.IsCompositeEditCommand());

}  // namespace blink

#endif  // CompositeEditCommand_h
