/*
 * sxesession.h - Sxe Session
 * Copyright (C) 2007  Joonas Govenius
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 */

#ifndef SXDESESSION_H
#define SXDESESSION_H

#define SXENS "http://jabber.org/protocol/sxe"
/*  ^^^^ make sure corresponds to NS used for parsing in iris/src/xmpp/xmpp-im/types.cpp ^^^^ */

#include <QObject>
#include <QList>
#include <QPointer>
#include <QDomNode>

#include "im.h"
#include "psiaccount.h"
#include "sxerecord.h"

namespace XMPP {
    class Client;
    class Jid;
    class Message;
}

using namespace XMPP;

class SxeManager;

/*! \brief Class for storing the record and the XML document for an established SXE session.*/
class SxeSession : public QObject {
    Q_OBJECT

        // Make SxeManager a friend class so it can emit peerJoined/Left session.
        friend class SxeManager;

    private:
        struct IncomingEdit {
            QString id;
            QDomElement xml;
        };

    public:
        /*! \brief Constructor.
        *  Creates a new session for the specified jid and session identifier.
        */
        SxeSession(SxeManager *manager, const Jid &target, const QString &session, const Jid &ownJid, bool groupChat, bool serverSupport, const QList<QString> &features);
        /*! \brief Destructor.
        *  Emits sessionEnded()
        */
        ~SxeSession();

        /*! \brief Initializes the shared document. Only used if starting a new session; not when joining one. */
        void initializeDocument(const QDomDocument &doc);
        /*! \brief Processes the incoming SXE element and remembers its identifying information.*/
        void processIncomingSxeElement(const QDomElement &, const QString &id);
        /*! \brief Returns a const reference to the target document.*/
        const QDomDocument& document() const;
        /*! \brief Returns true if the target is a groupchat.*/
        bool groupChat() const;
        /*! \brief Returns true if the target is a groupchat.*/
        bool serverSupport() const;
        /*! \brief Returns the target contact's JID.*/
        const Jid target() const;
        /*! \brief Returns the session identifier.*/
        const QString session() const;
        /*! \brief Returns the JID used by the user in the session.*/
        const Jid ownJid() const;
        /*! \brief Returns the session identifier.*/
        const QList<QString> features() const;

        /*! \brief Starts queueing new edits to the document.
         *  Queueing should be started just before sending <document-begin/>.
         */
        QList<const SxeEdit*> startQueueing();
        /*! \brief Stop queueing new edits to the document and process the queued ones.
         *  Queueing should be stopped after sending <document-end/>.
         */
        void stopQueueing();

        /*! \brief Initializes the document with the prolog of \a doc if provided.
         * Should be used when <document-begin/> is received when joining.
         */
        void startImporting(const QDomDocument &doc = QDomDocument());
        /*! \brief Enters the normal editing mode.
         * Should be used when <document-end/> is received when joining.
         */
        void stopImporting();

        /*! \brief Add the given ID to the list of used IDs for <sxe/> elements.*/
        void addUsedSxeId(QString id);
        /*! \brief Return the list of used IDs for <sxe/> elements.*/
        QList<QString> usedSxeIds();

        void setUUIDPrefix(const QString uuidPrefix = QString());
        /*! \brief Returns a random UUID without enclosing { }. */
        static QString generateUUID();
        /*! \brief Returns the prolog of the document as a string. */
        static QString parseProlog(const QDomDocument &doc);

    public slots:
        /*! \brief Ends the session.*/
        void endSession();
        /*! \brief Inserts or moves the given node so that it is before the reference element.
         *  If the reference element is the null element the element is inserted as the first child of the parent.
         *  Returns \a node if the node was already in the document. Otherwise returns the created node.
         */
        const QDomNode insertNodeBefore(const QDomNode &node, const QDomNode &parent, const QDomNode &referenceNode = QDomNode());
        /*! \brief Inserts or moves the given node so that it is after the reference element.
         *  If the reference element is the null element the element is inserted as the last child of the parent.
         *  Returns \a node if the node was already in the document. Otherwise returns the created node.
         */
        const QDomNode insertNodeAfter(const QDomNode &node, const QDomNode &parent, const QDomNode &referenceNode = QDomNode());
        /*! \brief Removes the given element.*/
        void removeNode(const QDomNode &node);
        /*! \brief Sets the value of \a attribute of \a node to \a value. */
        void setAttribute(const QDomNode &node, const QString &attribute, const QString &value, int from = -1, int n = 0);
        /*! \brief Sets the value of \a node to \a value. */
        void setNodeValue(const QDomNode &node, const QString &value, int from = -1, int n = 0);

        /*! \brief Sends all queued edits.*/
        void flush();

    signals:
        /*! \brief used to pass the new <sxe/> elements to sxemanager.*/
        void newSxeElement(const QDomElement &element, const Jid &, bool groupChat);

        /*! \brief Emitted after each processed SXE element.*/
        void documentUpdated(bool remote);
        /*! \brief Emitted just before \a node is inserted.*/
        void nodeToBeAdded(const QDomNode &node, bool remote);
        /*! \brief Emitted after \a node is inserted.*/
        void nodeAdded(const QDomNode &node, bool remote);
        /*! \brief Emitted just before \a node is moved in the document tree due to processing of an SXE element.*/
        void nodeToBeMoved(const QDomNode &node, bool remote);
        /*! \brief Emitted after \a node has been moved in the document tree due to processing of an SXE element.*/
        void nodeMoved(const QDomNode &node, bool remote);
        /*! \brief Emitted just before \a node is removed from the document tree due to processing of an SXE element.*/
        void nodeToBeRemoved(const QDomNode &node, bool remote);
        /*! \brief Emitted after \a node has been removed from the document tree due to processing of an SXE element.*/
        void nodeRemoved(const QDomNode &node, bool remote);
        /*! \brief Emitted when the name of \a node has been changed. */
        // void nameChanged(const QDomNode &node, bool remote);
        /*! \brief Emitted just before the chdata of \a node is changed. */
        void chdataToBeChanged(const QDomNode &node, bool remote);
        /*! \brief Emitted when the chdata of \a node has been changed. */
        void chdataChanged(const QDomNode &node, bool remote);

        /*! \brief Signals that a peer joined the session.*/
        void peerJoinedSession(const Jid &);
        /*! \brief Signals that a peer left the session.*/
        void peerLeftSession(const Jid &);
        /*! \brief Signals that the session ended and the session is to be deleted.*/
        void sessionEnded(SxeSession*);

    private slots:
        /*! \brief Adds \a node to the document tree and emits the appropriate public signals. */
        void handleNodeToBeAdded(const QDomNode &node, bool remote);
        /*! \brief Moves \a node in the document tree and emits the appropriate public signals. */
        void handleNodeToBeMoved(const QDomNode &node, bool remote);
        /*! \brief Remove the record entry from the lookup tables and emit the appropriate public signals. */
        void handleNodeToBeRemoved(const QDomNode &node, bool remote);
        /*! \brief Add a node node to the lookup table. */
        // void addToLookup(const QDomNode &node, bool, const QString &rid);

    private:
        /*! \brief Inserts or moves a node according to it's record (parent and primary-weight). */
        void reposition(const QDomNode &node, bool remote);
        /*! \brief Remove the record associated with \a node from the lookup tables. */
        void removeRecord(const QDomNode &node);
        /*! \brief Remove the item with smaller secondary weight.
            Returns true iff \a meta1 was removed. */
        bool removeSmaller(SxeRecord* meta1, SxeRecord* meta2);
        /*! \brief Processes an incoming sxe element.*/
        bool processSxe(const QDomElement &sxe, const QString &id);
        /*! \brief Queues an outgoing edit to be sent when flushed.*/
        void queueOutgoingEdit(SxeEdit* edit);
        /*! \brief Creates the record of node with rid \a id. Returns a pointer to it. */
        SxeRecord* createRecord(const QString &id);
        /*! \brief Returns a pointer to the record of node with rid \a id. */
        SxeRecord* record(const QString &id);
        /*! \brief Returns a pointer to the record of \a node. */
        SxeRecord* record(const QDomNode &node) const;
        /*! \brief Generates SxeNewEdits for \a node and its children.
         *  Returns the created node. */
        QDomNode generateNewNode(const QDomNode node, const QString &parent, double primaryWeight);
        /*! \brief Generates SxeRemoveEdits for \a node and its children. */
        void generateRemoves(const QDomNode &node);
        /*! \brief Recursive helper method for arranging edits for the snapshot. */
        void arrangeEdits(QHash<QString, QString> &ridByParent, QList<const SxeEdit*> &output, const QString &iterator);
        /*! \brief Insert node with the given primaryWeight.
         *  Returns node if the node was already in the document. Otherwise returns the created node. */
        const QDomNode insertNode(const QDomNode &node, const QString &parentId, double primaryWeight);
        /*! \brief Returns a random UUID without enclosing { } and checks that it's not used as a rid.
            Necessary because you often generate the same UUID on two different processes on the same computer (non-windows). */
        QString generateUUIDForSession();

        /*! \brief The string identifying the session.*/
        QString session_;
        /*! \brief The target JID.*/
        Jid target_;
        /*! \brief The target JID.*/
        Jid ownJid_;
        /*! \brief The target XML document.*/
        QDomDocument document_;

        /*! \brief Hash used for rid -> SxeRecord* lookups.*/
        QHash<
                QString,
                SxeRecord*
             > recordByNodeId_;
        /*! \brief List of queued incoming sxe elements.*/
        QList<IncomingEdit> queuedIncomingEdits_;
        /*! \brief List of queued outgoing sxe elements.*/
        QList<QDomNode> queuedOutgoingEdits_;
        /*! \brief QDomDocument representing the the contents when queueing_ was set true.*/
        QList<SxeEdit*> snapshot_;
        /*! \brief True if the target is a groupchat.*/
        bool groupChat_;
        /*! \brief True if the session was not established with a server supporting SXE.*/
        bool serverSupport_;
        /*! \brief If true, new sxe elements are queued rather than processed.*/
        bool queueing_;
        /*! \brief True while initial contents of the document are being imported.*/
        bool importing_;
        /*! \brief A list of supported features for the session.*/
        QList<QString> features_;
         /*! \brief Identifiers for the <sxe/> elements that have been processed already.*/
        QList<QString> usedSxeIds_;
        /*! \brief A unique id is generated as "uuidPrefix.counter".*/
        QString uuidPrefix_;
        int uuidMaxPostfix_;
        /*! \brief The main DOM document.*/
        QDomDocument doc_;
};

#endif
