/*
 * Copyright (C) 2004 Apple Computer, 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 __htmleditingimpl_h__
#define __htmleditingimpl_h__

#include "misc/shared.h"

#include "xml/dom_position.h"
#include "xml/dom_selection.h"
#include "dom/dom_string.h"

#include "xml/dom_nodeimpl.h"

#include <QList>

namespace DOM
{
class CSSProperty;
class CSSStyleDeclarationImpl;
class DocumentFragmentImpl;
class DocumentImpl;
class DOMString;
class ElementImpl;
class HTMLElementImpl;
class NodeImpl;
class NodeListImpl;
class Position;
class Range;
class Selection;
class TextImpl;
class TreeWalkerImpl;
}

using DOM::DocumentImpl;
using DOM::NodeImpl;

namespace khtml
{

class EditCommandImpl;

class SharedCommandImpl : public Shared<SharedCommandImpl>
{
public:
    SharedCommandImpl() {}
    virtual ~SharedCommandImpl() {}

    virtual bool isCompositeStep() const = 0;

    virtual void apply() = 0;
    virtual void unapply() = 0;
    virtual void reapply() = 0;

    virtual DOM::DocumentImpl *document() const = 0;

    virtual DOM::Selection startingSelection() const = 0;
    virtual DOM::Selection endingSelection() const = 0;

    virtual void setStartingSelection(const DOM::Selection &s) = 0;
    virtual void setEndingSelection(const DOM::Selection &s) = 0;

    virtual EditCommandImpl *parent() const = 0;
    virtual void setParent(EditCommandImpl *) = 0;
};

//------------------------------------------------------------------------------------------
// EditCommandImpl

class EditCommandImpl : public SharedCommandImpl
{
public:
    EditCommandImpl(DOM::DocumentImpl *);
    virtual ~EditCommandImpl();

    bool isCompositeStep() const Q_DECL_OVERRIDE
    {
        return parent();
    }
    EditCommandImpl *parent() const Q_DECL_OVERRIDE;
    void setParent(EditCommandImpl *) Q_DECL_OVERRIDE;

    enum ECommandState { NotApplied, Applied };

    void apply() Q_DECL_OVERRIDE;
    void unapply() Q_DECL_OVERRIDE;
    void reapply() Q_DECL_OVERRIDE;

    virtual void doApply() = 0;
    virtual void doUnapply() = 0;
    virtual void doReapply();  // calls doApply()

    DOM::DocumentImpl *document() const Q_DECL_OVERRIDE
    {
        return m_document.get();
    }

    DOM::Selection startingSelection() const Q_DECL_OVERRIDE
    {
        return m_startingSelection;
    }
    DOM::Selection endingSelection() const Q_DECL_OVERRIDE
    {
        return m_endingSelection;
    }

    ECommandState state() const
    {
        return m_state;
    }
    void setState(ECommandState state)
    {
        m_state = state;
    }

    void setStartingSelection(const DOM::Selection &s) Q_DECL_OVERRIDE;
    void setEndingSelection(const DOM::Selection &s) Q_DECL_OVERRIDE;

public:
    virtual bool isTypingCommand() const
    {
        return false;
    }
    virtual bool isInputTextCommand() const
    {
        return false;
    }

private:
    DocPtr<DOM::DocumentImpl> m_document;
    ECommandState m_state;
    DOM::Selection m_startingSelection;
    DOM::Selection m_endingSelection;
    RefPtr<EditCommandImpl> m_parent;
};

//------------------------------------------------------------------------------------------
// CompositeEditCommandImpl

class CompositeEditCommandImpl : public EditCommandImpl
{
public:
    CompositeEditCommandImpl(DOM::DocumentImpl *);
    virtual ~CompositeEditCommandImpl();

    void doApply() Q_DECL_OVERRIDE = 0;
    void doUnapply() Q_DECL_OVERRIDE;
    void doReapply() Q_DECL_OVERRIDE;

protected:
    //
    // sugary-sweet convenience functions to help create and apply edit commands in composite commands
    //
    void appendNode(DOM::NodeImpl *parent, DOM::NodeImpl *appendChild);
    void applyCommandToComposite(PassRefPtr<EditCommandImpl>);
    void deleteCollapsibleWhitespace();
    void deleteCollapsibleWhitespace(const DOM::Selection &selection);
    void deleteKeyPressed();
    void deleteSelection();
    void deleteSelection(const DOM::Selection &selection);
    void deleteText(DOM::TextImpl *node, long offset, long count);
    void inputText(const DOM::DOMString &text);
    void insertNodeAfter(DOM::NodeImpl *insertChild, DOM::NodeImpl *refChild);
    void insertNodeAt(DOM::NodeImpl *insertChild, DOM::NodeImpl *refChild, long offset);
    void insertNodeBefore(DOM::NodeImpl *insertChild, DOM::NodeImpl *refChild);
    void insertText(DOM::TextImpl *node, long offset, const DOM::DOMString &text);
    void joinTextNodes(DOM::TextImpl *text1, DOM::TextImpl *text2);
    void removeCSSProperty(DOM::CSSStyleDeclarationImpl *, int property);
    void removeNodeAttribute(DOM::ElementImpl *, int attribute);
    void removeNode(DOM::NodeImpl *removeChild);
    void removeNodeAndPrune(DOM::NodeImpl *pruneNode, DOM::NodeImpl *stopNode = nullptr);
    void removeNodePreservingChildren(DOM::NodeImpl *node);
    void replaceText(DOM::TextImpl *node, long offset, long count, const DOM::DOMString &replacementText);
    void setNodeAttribute(DOM::ElementImpl *, int attribute, const DOM::DOMString &);
    void splitTextNode(DOM::TextImpl *text, long offset);

    DOM::ElementImpl *createTypingStyleElement() const;

    QList<RefPtr<EditCommandImpl> > m_cmds;
};

//==========================================================================================
// Concrete commands
//------------------------------------------------------------------------------------------
// AppendNodeCommandImpl

class AppendNodeCommandImpl : public EditCommandImpl
{
public:
    AppendNodeCommandImpl(DOM::DocumentImpl *, DOM::NodeImpl *parentNode, DOM::NodeImpl *appendChild);
    virtual ~AppendNodeCommandImpl();

    void doApply() Q_DECL_OVERRIDE;
    void doUnapply() Q_DECL_OVERRIDE;

    DOM::NodeImpl *parentNode() const
    {
        return m_parentNode;
    }
    DOM::NodeImpl *appendChild() const
    {
        return m_appendChild;
    }

private:
    DOM::NodeImpl *m_parentNode;
    DOM::NodeImpl *m_appendChild;
};

//------------------------------------------------------------------------------------------
// ApplyStyleCommandImpl

class ApplyStyleCommandImpl : public CompositeEditCommandImpl
{
public:
    ApplyStyleCommandImpl(DOM::DocumentImpl *, DOM::CSSStyleDeclarationImpl *style);
    virtual ~ApplyStyleCommandImpl();

    void doApply() Q_DECL_OVERRIDE;

    DOM::CSSStyleDeclarationImpl *style() const
    {
        return m_style;
    }

    struct StyleChange {
        StyleChange() : applyBold(false), applyItalic(false) {}
        DOM::DOMString cssStyle;
        bool applyBold: 1;
        bool applyItalic: 1;
    };

private:
    // style-removal helpers
    bool isHTMLStyleNode(DOM::HTMLElementImpl *);
    void removeHTMLStyleNode(DOM::HTMLElementImpl *);
    void removeCSSStyle(DOM::HTMLElementImpl *);
    void removeStyle(const DOM::Position &start, const DOM::Position &end);
    bool nodeFullySelected(const DOM::NodeImpl *node) const;

    // style-application helpers
    bool currentlyHasStyle(const DOM::Position &, const DOM::CSSProperty *) const;
    StyleChange computeStyleChange(const DOM::Position &, DOM::CSSStyleDeclarationImpl *);
    bool splitTextAtStartIfNeeded(const DOM::Position &start, const DOM::Position &end);
    DOM::NodeImpl *splitTextAtEndIfNeeded(const DOM::Position &start, const DOM::Position &end);
    void surroundNodeRangeWithElement(DOM::NodeImpl *start, DOM::NodeImpl *end, DOM::ElementImpl *element);
    DOM::Position positionInsertionPoint(DOM::Position);
    void applyStyleIfNeeded(DOM::NodeImpl *start, DOM::NodeImpl *end);

    DOM::CSSStyleDeclarationImpl *m_style;
};

//------------------------------------------------------------------------------------------
// DeleteCollapsibleWhitespaceCommandImpl

class DeleteCollapsibleWhitespaceCommandImpl : public CompositeEditCommandImpl
{
public:
    DeleteCollapsibleWhitespaceCommandImpl(DOM::DocumentImpl *document);
    DeleteCollapsibleWhitespaceCommandImpl(DOM::DocumentImpl *document, const DOM::Selection &selection);

    virtual ~DeleteCollapsibleWhitespaceCommandImpl();

    void doApply() Q_DECL_OVERRIDE;

private:
    DOM::Position deleteWhitespace(const DOM::Position &pos);

    unsigned long m_charactersDeleted;
    DOM::Selection m_selectionToCollapse;
    bool m_hasSelectionToCollapse;
};

//------------------------------------------------------------------------------------------
// DeleteSelectionCommandImpl

class DeleteSelectionCommandImpl : public CompositeEditCommandImpl
{
public:
    DeleteSelectionCommandImpl(DOM::DocumentImpl *document);
    DeleteSelectionCommandImpl(DOM::DocumentImpl *document, const DOM::Selection &selection);

    virtual ~DeleteSelectionCommandImpl();

    void doApply() Q_DECL_OVERRIDE;

private:
    void deleteContentBeforeOffset(NodeImpl *node, int offset);
    void deleteContentAfterOffset(NodeImpl *node, int offset);
    void deleteContentInsideNode(NodeImpl *node, int startOffset, int endOffset);
    void deleteDownstreamWS(const DOM::Position &start);
    bool containsOnlyWhitespace(const DOM::Position &start, const DOM::Position &end);
    void joinTextNodesWithSameStyle();

    DOM::Selection m_selectionToDelete;
    bool m_hasSelectionToDelete;
};

//------------------------------------------------------------------------------------------
// DeleteTextCommandImpl

class DeleteTextCommandImpl : public EditCommandImpl
{
public:
    DeleteTextCommandImpl(DOM::DocumentImpl *document, DOM::TextImpl *node, long offset, long count);
    virtual ~DeleteTextCommandImpl();

    void doApply() Q_DECL_OVERRIDE;
    void doUnapply() Q_DECL_OVERRIDE;

    DOM::TextImpl *node() const
    {
        return m_node;
    }
    long offset() const
    {
        return m_offset;
    }
    long count() const
    {
        return m_count;
    }

private:
    DOM::TextImpl *m_node;
    long m_offset;
    long m_count;
    DOM::DOMString m_text;
};

//------------------------------------------------------------------------------------------
// InputNewlineCommandImpl

class InputNewlineCommandImpl : public CompositeEditCommandImpl
{
public:
    InputNewlineCommandImpl(DOM::DocumentImpl *document);
    virtual ~InputNewlineCommandImpl();

    void doApply() Q_DECL_OVERRIDE;

private:
    void insertNodeAfterPosition(DOM::NodeImpl *node, const DOM::Position &pos);
    void insertNodeBeforePosition(DOM::NodeImpl *node, const DOM::Position &pos);
};

//------------------------------------------------------------------------------------------
// InputTextCommandImpl

class InputTextCommandImpl : public CompositeEditCommandImpl
{
public:
    InputTextCommandImpl(DOM::DocumentImpl *document);
    virtual ~InputTextCommandImpl();

    void doApply() Q_DECL_OVERRIDE;

    void deleteCharacter();
    void input(const DOM::DOMString &text);

    unsigned long charactersAdded() const
    {
        return m_charactersAdded;
    }

    bool isInputTextCommand() const Q_DECL_OVERRIDE
    {
        return true;
    }
private:
    DOM::Position prepareForTextInsertion(bool adjustDownstream);
    void execute(const DOM::DOMString &text);
    void insertSpace(DOM::TextImpl *textNode, unsigned long offset);

    unsigned long m_charactersAdded;
};

//------------------------------------------------------------------------------------------
// InsertNodeBeforeCommandImpl

class InsertNodeBeforeCommandImpl : public EditCommandImpl
{
public:
    InsertNodeBeforeCommandImpl(DOM::DocumentImpl *, DOM::NodeImpl *insertChild, DOM::NodeImpl *refChild);
    virtual ~InsertNodeBeforeCommandImpl();

    void doApply() Q_DECL_OVERRIDE;
    void doUnapply() Q_DECL_OVERRIDE;

    DOM::NodeImpl *insertChild() const
    {
        return m_insertChild;
    }
    DOM::NodeImpl *refChild() const
    {
        return m_refChild;
    }

private:
    DOM::NodeImpl *m_insertChild;
    DOM::NodeImpl *m_refChild;
};

//------------------------------------------------------------------------------------------
// InsertTextCommandImpl

class InsertTextCommandImpl : public EditCommandImpl
{
public:
    InsertTextCommandImpl(DOM::DocumentImpl *document, DOM::TextImpl *, long, const DOM::DOMString &);
    virtual ~InsertTextCommandImpl();

    void doApply() Q_DECL_OVERRIDE;
    void doUnapply() Q_DECL_OVERRIDE;

    DOM::TextImpl *node() const
    {
        return m_node;
    }
    long offset() const
    {
        return m_offset;
    }
    DOM::DOMString text() const
    {
        return m_text;
    }

private:
    DOM::TextImpl *m_node;
    long m_offset;
    DOM::DOMString m_text;
};

//------------------------------------------------------------------------------------------
// JoinTextNodesCommandImpl

class JoinTextNodesCommandImpl : public EditCommandImpl
{
public:
    JoinTextNodesCommandImpl(DOM::DocumentImpl *, DOM::TextImpl *, DOM::TextImpl *);
    virtual ~JoinTextNodesCommandImpl();

    void doApply() Q_DECL_OVERRIDE;
    void doUnapply() Q_DECL_OVERRIDE;

    DOM::TextImpl *firstNode() const
    {
        return m_text1;
    }
    DOM::TextImpl *secondNode() const
    {
        return m_text2;
    }

private:
    DOM::TextImpl *m_text1;
    DOM::TextImpl *m_text2;
    unsigned long m_offset;
};

//------------------------------------------------------------------------------------------
// ReplaceSelectionCommandImpl

class ReplaceSelectionCommandImpl : public CompositeEditCommandImpl
{
public:
    ReplaceSelectionCommandImpl(DOM::DocumentImpl *document, DOM::DocumentFragmentImpl *fragment, bool selectReplacement = true);
    virtual ~ReplaceSelectionCommandImpl();

    void doApply() Q_DECL_OVERRIDE;

private:
    DOM::DocumentFragmentImpl *m_fragment;
    bool m_selectReplacement;
};

//------------------------------------------------------------------------------------------
// MoveSelectionCommandImpl

class MoveSelectionCommandImpl : public CompositeEditCommandImpl
{
public:
    MoveSelectionCommandImpl(DOM::DocumentImpl *document, DOM::DocumentFragmentImpl *fragment, DOM::Position &position);
    virtual ~MoveSelectionCommandImpl();

    void doApply() Q_DECL_OVERRIDE;

private:
    DOM::DocumentFragmentImpl *m_fragment;
    DOM::Position m_position;
};

//------------------------------------------------------------------------------------------
// RemoveCSSPropertyCommand

class RemoveCSSPropertyCommandImpl : public EditCommandImpl
{
public:
    RemoveCSSPropertyCommandImpl(DOM::DocumentImpl *, DOM::CSSStyleDeclarationImpl *, int property);
    virtual ~RemoveCSSPropertyCommandImpl();

    void doApply() Q_DECL_OVERRIDE;
    void doUnapply() Q_DECL_OVERRIDE;

    DOM::CSSStyleDeclarationImpl *styleDeclaration() const
    {
        return m_decl;
    }
    int property() const
    {
        return m_property;
    }

private:
    DOM::CSSStyleDeclarationImpl *m_decl;
    int m_property;
    DOM::DOMString m_oldValue;
    bool m_important;
};

//------------------------------------------------------------------------------------------
// RemoveNodeAttributeCommandImpl

class RemoveNodeAttributeCommandImpl : public EditCommandImpl
{
public:
    RemoveNodeAttributeCommandImpl(DOM::DocumentImpl *, DOM::ElementImpl *, DOM::NodeImpl::Id attribute);
    virtual ~RemoveNodeAttributeCommandImpl();

    void doApply() Q_DECL_OVERRIDE;
    void doUnapply() Q_DECL_OVERRIDE;

    DOM::ElementImpl *element() const
    {
        return m_element;
    }
    DOM::NodeImpl::Id attribute() const
    {
        return m_attribute;
    }

private:
    DOM::ElementImpl *m_element;
    DOM::NodeImpl::Id m_attribute;
    DOM::DOMString m_oldValue;
};

//------------------------------------------------------------------------------------------
// RemoveNodeCommandImpl

class RemoveNodeCommandImpl : public EditCommandImpl
{
public:
    RemoveNodeCommandImpl(DOM::DocumentImpl *, DOM::NodeImpl *);
    virtual ~RemoveNodeCommandImpl();

    void doApply() Q_DECL_OVERRIDE;
    void doUnapply() Q_DECL_OVERRIDE;

    DOM::NodeImpl *node() const
    {
        return m_removeChild;
    }

private:
    DOM::NodeImpl *m_parent;
    DOM::NodeImpl *m_removeChild;
    DOM::NodeImpl *m_refChild;
};

//------------------------------------------------------------------------------------------
// RemoveNodeAndPruneCommandImpl

class RemoveNodeAndPruneCommandImpl : public CompositeEditCommandImpl
{
public:
    RemoveNodeAndPruneCommandImpl(DOM::DocumentImpl *, DOM::NodeImpl *pruneNode, DOM::NodeImpl *stopNode = nullptr);
    virtual ~RemoveNodeAndPruneCommandImpl();

    void doApply() Q_DECL_OVERRIDE;

    DOM::NodeImpl *pruneNode() const
    {
        return m_pruneNode;
    }
    DOM::NodeImpl *stopNode() const
    {
        return m_stopNode;
    }

private:
    DOM::NodeImpl *m_pruneNode;
    DOM::NodeImpl *m_stopNode;
};

//------------------------------------------------------------------------------------------
// RemoveNodePreservingChildrenCommandImpl

class RemoveNodePreservingChildrenCommandImpl : public CompositeEditCommandImpl
{
public:
    RemoveNodePreservingChildrenCommandImpl(DOM::DocumentImpl *, DOM::NodeImpl *);
    virtual ~RemoveNodePreservingChildrenCommandImpl();

    void doApply() Q_DECL_OVERRIDE;

    DOM::NodeImpl *node() const
    {
        return m_node;
    }

private:
    DOM::NodeImpl *m_node;
};

//------------------------------------------------------------------------------------------
// SetNodeAttributeCommandImpl

class SetNodeAttributeCommandImpl : public EditCommandImpl
{
public:
    SetNodeAttributeCommandImpl(DOM::DocumentImpl *, DOM::ElementImpl *, DOM::NodeImpl::Id attribute, const DOM::DOMString &value);
    virtual ~SetNodeAttributeCommandImpl();

    void doApply() Q_DECL_OVERRIDE;
    void doUnapply() Q_DECL_OVERRIDE;

    DOM::ElementImpl *element() const
    {
        return m_element;
    }
    DOM::NodeImpl::Id attribute() const
    {
        return m_attribute;
    }
    DOM::DOMString value() const
    {
        return m_value;
    }

private:
    DOM::ElementImpl *m_element;
    DOM::NodeImpl::Id m_attribute;
    DOM::DOMString m_value;
    DOM::DOMString m_oldValue;
};

//------------------------------------------------------------------------------------------
// SplitTextNodeCommandImpl

class SplitTextNodeCommandImpl : public EditCommandImpl
{
public:
    SplitTextNodeCommandImpl(DOM::DocumentImpl *, DOM::TextImpl *, long);
    virtual ~SplitTextNodeCommandImpl();

    void doApply() Q_DECL_OVERRIDE;
    void doUnapply() Q_DECL_OVERRIDE;

    DOM::TextImpl *node() const
    {
        return m_text2;
    }
    long offset() const
    {
        return m_offset;
    }

private:
    DOM::TextImpl *m_text1;
    DOM::TextImpl *m_text2;
    unsigned long m_offset;
};

//------------------------------------------------------------------------------------------
// TypingCommandImpl

class TypingCommandImpl : public CompositeEditCommandImpl
{
public:
    TypingCommandImpl(DOM::DocumentImpl *document);
    virtual ~TypingCommandImpl();

    void doApply() Q_DECL_OVERRIDE;

    bool openForMoreTyping() const
    {
        return m_openForMoreTyping;
    }
    void closeTyping()
    {
        m_openForMoreTyping = false;
    }

    void insertText(const DOM::DOMString &text);
    void insertNewline();
    void deleteKeyPressed();

    bool isTypingCommand() const Q_DECL_OVERRIDE
    {
        return true;
    }

    static void deleteKeyPressed0(DocumentImpl *document);
    static void insertNewline0(DocumentImpl *document);
    static void insertText0(DocumentImpl *document, const DOMString &text);

private:
    void issueCommandForDeleteKey();
    void removeCommand(const PassRefPtr<EditCommandImpl>);
    void typingAddedToOpenCommand();

    bool m_openForMoreTyping;
};

//------------------------------------------------------------------------------------------
// InsertListCommandImpl

class InsertListCommandImpl : public CompositeEditCommandImpl
{
public:
    enum Type { OrderedList, UnorderedList };

    InsertListCommandImpl(DOM::DocumentImpl *document, Type type);
    virtual ~InsertListCommandImpl();

    void doApply() Q_DECL_OVERRIDE;

    static void insertList(DocumentImpl *document, Type type);

private:
    Type m_listType;
};

//------------------------------------------------------------------------------------------

//------------------------------------------------------------------------------------------
// IndentOutdentCommandImpl

class IndentOutdentCommandImpl : public CompositeEditCommandImpl
{
public:
    enum Type { Indent, Outdent };

    IndentOutdentCommandImpl(DocumentImpl *document, Type type);
    virtual ~IndentOutdentCommandImpl();

    void doApply() Q_DECL_OVERRIDE;

private:
    void indent();
    void outdent();

    Type m_commandType;
};

//------------------------------------------------------------------------------------------

} // end namespace khtml

#endif

