/*
 * sxemanager.cpp - Whiteboard manager
 * Copyright (C) 2006  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 program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

#include "sxemanager.h"

#include "common.h"
#include "psiaccount.h"
#include "psioptions.h"
#include "psipopup.h"
#include "xmpp_caps.h"

#include <QUrl>

#define ONETOONEPREFIXSELF "0"
#define ONETOONEPREFIXOTHER "1"

using namespace XMPP;

//----------------------------------------------------------------------------
// SxeManager
//----------------------------------------------------------------------------

SxeManager::SxeManager(Client *client, PsiAccount *pa) : client_(client)
{
    sxeId_ = QTime::currentTime().toString("z").toInt();

    pa_ = pa;

    connect(client_, SIGNAL(messageReceived(const Message &)), SLOT(messageReceived(const Message &)));
    connect(client_, SIGNAL(groupChatLeft(const Jid &)), SLOT(groupChatLeft(const Jid &)));
    // connect(client_, SIGNAL(groupChatJoined(const Jid &, const Jid &)), SLOT(groupChatJoined(const Jid &, const Jid
    // &)));

    negotiationTimer_.setSingleShot(true);
    negotiationTimer_.setInterval(120000);
    connect(&negotiationTimer_, SIGNAL(timeout()), SLOT(negotiationTimeout()));
}

// void SxeManager::addInvitationCallback(bool (*callback)(const Jid &peer, const QList<QString> &features)) {
//    invitationCallbacks_ += callback;
//}

void SxeManager::messageReceived(const Message &message)
{
    // only process messages that contain a <sxe/> with a nonempty
    // 'session' attribute and that are addressed to this particular account
    if (!message.sxe().attribute("session").isEmpty()) {

        // skip messages from self
        if (ownJids_.contains(message.from().full())) {
            qDebug("from self");
            return;
        }

        // Check if the <sxe/> contains a <negotiation/>
        if (message.sxe().elementsByTagName("negotiation").length() > 0) {
            processNegotiationMessage(message);
            // processNegotiationMessage() will also pass regular SXE edits in the message
            // to the session so we're done
            return;
        }

        // otherwise, try finding a matching session for the session if new one not negotiated
        SxeSession *w = findSession(message.sxe().attribute("session"));

        if (w) {
            // pass the message to the session if already established
            w->processIncomingSxeElement(message.sxe(), message.sxe().attribute("id"));
        } else {
            // otherwise record the session id as a "detected session"
            recordDetectedSession(message);
        }
    }
}

void SxeManager::recordDetectedSession(const Message &message)
{
    // check if a record of the session exists
    for (const DetectedSession &d : qAsConst(DetectedSessions_)) {
        if (d.session == message.sxe().attribute("session")
            && d.jid.compare(message.from(), message.type() != "groupchat"))
            return;
    }

    // store a record of a detected session
    DetectedSession detected;
    detected.session = message.sxe().attribute("session");
    if (message.type() == "groupchat")
        detected.jid = message.from().bare();
    else
        detected.jid = message.from();
    detected.time = QTime::currentTime();
    DetectedSessions_.append(detected);
}

void SxeManager::removeSession(SxeSession *session)
{
    sessions_.removeAll(session);

    // cancel possible negotiations
    const auto &negotiations = negotiations_.values(session->session());
    for (SxeNegotiation *negotiation : negotiations) {
        if (negotiation->target.compare(session->target(), true))
            abortNegotiation(negotiation);
    }

    // notify the target
    QDomDocument doc;
    QDomElement  sxe = doc.createElementNS(SXENS, "sxe");
    sxe.setAttribute("session", session->session());
    QDomElement negotiation = doc.createElementNS(SXENS, "negotiation");
    negotiation.appendChild(doc.createElementNS(SXENS, "left-session"));
    sxe.appendChild(negotiation);
    sendSxe(sxe, session->target(), session->groupChat());

    // delete the session
    // session->deleteLater();
}

bool SxeManager::processNegotiationAsParticipant(const QDomNode &negotiationElement, SxeNegotiation *negotiation,
                                                 QDomNode response)
{
    QDomDocument doc = QDomDocument();

    if (negotiationElement.nodeName() == "left-session") {

        if (negotiation->state == SxeNegotiation::Finished)
            emit negotiation->session->peerLeftSession(negotiation->peer);

    } else if (negotiationElement.nodeName() == "abort-negotiation") {

        if (negotiation->state < SxeNegotiation::HistoryOffered
            && negotiation->state != SxeNegotiation::DocumentBegan) {
            // Abort, as in delete session, if still establishing it and not trying to create a new groupchat session
            if (!(negotiation->groupChat && negotiation->peer.resource().isEmpty())) {
                removeNegotiation(negotiation);
                return false;
            }
        } else {
            // Just remove the "negotation" but keep the session
            negotiation->state = SxeNegotiation::Finished;
        }

    } else if (negotiationElement.nodeName() == "connect-request" && negotiation->state == SxeNegotiation::Finished) {

        // accept all <connect-request/>'s automatically
        // if currently not negotiating with someone else
        negotiation->state = SxeNegotiation::HistoryOffered;
        response.appendChild(doc.createElementNS(SXENS, "history-offer"));

    } else if ((negotiationElement.nodeName() == "accept-history"
                && negotiation->state == SxeNegotiation::HistoryOffered)
               || (negotiationElement.nodeName() == "accept-invitation"
                   && negotiation->state == SxeNegotiation::InvitationSent)) {

        // If this is a new session (negotiation->state == SxeNegotiation::HistoryOffered),
        // create a new SxeSession
        if (!negotiation->session) {
            negotiation->session = createSxeSession(negotiation->target, negotiation->sessionId, negotiation->ownJid,
                                                    negotiation->groupChat, negotiation->features);
            negotiation->session->setUUIDPrefix(ONETOONEPREFIXSELF);
            negotiation->session->initializeDocument(negotiation->initialDoc);
        }

        if (!negotiation->session) {
            // Creating a new session failed for some reason.
            abortNegotiation(negotiation);
            return false;
        }

        // Retrieve all the edits to the session so far and start queueing new edits
        QList<const SxeEdit *> snapshot = negotiation->session->startQueueing();

        // append <document-begin/>
        QDomElement documentBegin = doc.createElementNS(SXENS, "document-begin");
        response.appendChild(documentBegin);
        QString prolog = SxeSession::parseProlog(negotiation->session->document());
        if (!prolog.isEmpty()) {
            QUrl::toPercentEncoding(prolog);
            documentBegin.setAttribute("prolog", QString("data:text/xml,%1").arg(prolog));
        }
        if (!negotiation->groupChat) {
            // It's safe to give the other participant a prefix to use in 1-to-1 sessions
            documentBegin.setAttribute("available-uuid-prefix", ONETOONEPREFIXOTHER);
        }
        response.appendChild(documentBegin);

        // append all the SxeEdit's returned by startQueueing()
        for (const SxeEdit *e : snapshot) {
            response.appendChild(e->xml(doc));
        }

        // append <documend-end/>
        QDomElement documentEnd = doc.createElementNS(SXENS, "document-end");

        QString     usedIds;
        const auto &ids = negotiation->session->usedSxeIds();
        for (const QString &usedId : ids)
            usedIds += usedId + ";";
        if (usedIds.size() > 0)
            usedIds = usedIds.left(usedIds.size() - 1); // strip the last ";"
        documentEnd.setAttribute("used-sxe-ids", usedIds);

        response.appendChild(documentEnd);

        // Need to "flush" the sxe here before stopping queueing
        if (response.hasChildNodes()) {
            QDomElement sxe = doc.createElementNS(SXENS, "sxe");
            sxe.setAttribute("session", negotiation->sessionId);
            sxe.appendChild(response);
            sendSxe(sxe.toElement(), negotiation->peer, negotiation->groupChat);
            sxe.removeChild(response);
        }
        while (response.hasChildNodes())
            response.removeChild(response.firstChild());

        // we're all set and can stop queueing new edits to the session
        negotiation->state = SxeNegotiation::Finished;
        negotiation->session->stopQueueing();

        // signal that a peer joined
        emit negotiation->session->peerJoinedSession(negotiation->peer);

    } else if (negotiationElement.nodeName() == "decline-invitation"
               && negotiation->state == SxeNegotiation::InvitationSent) {

        QDomNodeList alternatives = negotiationElement.toElement().elementsByTagName("alternative-session");
        for (int i = 0; i < alternatives.size(); i++) {
            emit alternativeSession(negotiation->target, alternatives.at(i).toElement().text());
        }

        if (!negotiation->groupChat || alternatives.size() > 0) {
            abortNegotiation(negotiation);
            return false;
        }
    }

    return true;
}

bool SxeManager::processNegotiationAsJoiner(const QDomNode &negotiationElement, SxeNegotiation *negotiation,
                                            QDomNode response, const Message &message)
{
    QDomDocument doc = QDomDocument();

    if (negotiationElement.nodeName() == "abort-negotiation") {

        // Abort, as in delete session, if not trying to join a groupchat session
        if (!(negotiation->groupChat && negotiation->peer.resource().isEmpty())) {
            removeNegotiation(negotiation);
            return false;
        }

    } else if (negotiationElement.nodeName() == "invitation" && negotiation->state == SxeNegotiation::NotStarted) {

        // copy the feature strings to negotiation-features
        for (int k = 0; k < negotiationElement.childNodes().length(); k++) {
            if (negotiationElement.childNodes().at(k).nodeName() == "feature") {
                negotiation->features += negotiationElement.childNodes().at(k).toElement().text();
            }
        }

        // check if one of the invitation callbacks accepts the invitation.
        const QString sessionId = negotiation->sessionId;
        //        foreach(bool (*callback)(const Jid &peer, const QList<QString> &features), invitationCallbacks_) {
        bool result = false; // callback(negotiation->peer, negotiation->features);
        emit invitationCallback(negotiation->peer, negotiation->features, &result);
        if (!negotiations_.contains(
                sessionId,
                negotiation)) { // FIXME that's wrong. negotiation can be replaced with new one with the same address
            return false;
        }
        if (result) {
            response.appendChild(doc.createElementNS(SXENS, "accept-invitation"));
            negotiation->state = SxeNegotiation::InvitationAccepted;
            return true;
        }
        return false;
        //        }
        // othewise abort negotiation
        abortNegotiation(negotiation); // NEEDS REVIEW. CODE WILL NEVER BE EXECUTED
        return false;

    } else if (negotiationElement.nodeName() == "history-offer"
               && negotiation->state == SxeNegotiation::ConnectionRequested) {

        // accept the first <history-offer/> that arrives in response to a <connect-request/>
        negotiation->state = SxeNegotiation::HistoryAccepted;
        negotiation->peer  = message.from();
        response.appendChild(doc.createElementNS(SXENS, "accept-history"));

    } else if (negotiationElement.nodeName() == "document-begin"
               && (negotiation->state == SxeNegotiation::HistoryAccepted
                   || negotiation->state == SxeNegotiation::InvitationAccepted)) {

        // Create the new SxeSession
        if (!negotiation->session) {
            negotiation->session = createSxeSession(negotiation->target, negotiation->sessionId, negotiation->ownJid,
                                                    negotiation->groupChat, negotiation->features);

            // The offer may contain a UUID prefix reserved for us
            if (negotiationElement.toElement().hasAttribute("available-uuid-prefix"))
                negotiation->session->setUUIDPrefix(negotiationElement.toElement().attribute("available-uuid-prefix"));
        }

        if (negotiation->session) {
            // set the session to "importing" state which bypasses some version control
            QDomDocument doc;
            if (negotiationElement.toElement().hasAttribute("prolog")) {
                QString prolog = negotiationElement.toElement().attribute("prolog");
                if (prolog.startsWith("data:")) {
                    // Assuming non-base64
                    prolog = prolog.mid(prolog.indexOf(",") + 1);
                    QUrl::toPercentEncoding(prolog);
                    doc.setContent(prolog);
                }
            }

            negotiation->session->startImporting(doc);
            negotiation->state = SxeNegotiation::DocumentBegan;
        } else {
            // creating the session failed for some reason
            qDebug("Failed to create session.");
            abortNegotiation(negotiation);
            return false;
        }

    } else if (negotiationElement.nodeName() != "document-end" && negotiation->state == SxeNegotiation::DocumentBegan) {

        // pass the edit to the session
        QDomElement sxe = doc.createElementNS(SXENS, "sxe");
        sxe.setAttribute("session", negotiation->sessionId);
        sxe.appendChild(negotiationElement.cloneNode());
        negotiation->session->processIncomingSxeElement(sxe, QString());

    } else if (negotiationElement.nodeName() == "document-end" && negotiation->state == SxeNegotiation::DocumentBegan) {

        // The initial document has been received and we're done
        negotiation->state = SxeNegotiation::Finished;

        // Decode the 'used-sxe-ids' field
        const auto tmp = negotiationElement.toElement().attribute("used-sxe-ids").split(";");
        for (const QString &usedId : tmp)
            if (usedId.size() > 0)
                negotiation->session->addUsedSxeId(usedId);

        // Exit the "importing" state so that normal version control resumes
        negotiation->session->stopImporting();
    }

    return true;
}

QPointer<SxeSession> SxeManager::processNegotiationMessage(const Message &message)
{

    if (PsiOptions::instance()->getOption("options.messages.ignore-non-roster-contacts").toBool()
        && message.type() != "groupchat") {
        // Ignore the message if contact not in roster
        if (!pa_->find(message.from())) {
            qDebug("SXE invitation received from contact that is not in roster.");
            return nullptr;
        }
    }

    // Find or create a negotiation object
    SxeNegotiation *negotiation = findNegotiation(message.from(), message.sxe().attribute("session"));
    if (negotiation) {

        // Only accept further negotiation messages from the source we are already negotiationing with or if we've
        // requested connection to a groupchat session
        if (!negotiation->peer.compare(message.from())
            && !(negotiation->groupChat && negotiation->peer.resource().isEmpty())) {
            abortNegotiation(negotiation->sessionId, message.from(), true);
            return nullptr;
        }

    } else
        negotiation = createNegotiation(message);

    // Prepare the response <sxe/>
    QDomDocument doc;
    QDomElement  sxe = doc.createElementNS(SXENS, "sxe");
    sxe.setAttribute("session", negotiation->sessionId);
    QDomElement response = doc.createElementNS(SXENS, "negotiation");

    // Process each child of the <sxe/>
    QDomNode n;
    for (int i = 0; i < message.sxe().childNodes().count(); i++) {
        n = message.sxe().childNodes().at(i);

        // skip non-elements
        if (!n.isElement())
            continue;

        if (n.nodeName() == "negotiation") {

            // Process each child element of <negotiation/>
            for (int j = 0; j < n.childNodes().count(); j++) {

                if (negotiation->role == SxeNegotiation::Participant) {
                    if (!processNegotiationAsParticipant(n.childNodes().at(j), negotiation, response))
                        return nullptr;
                } else if (negotiation->role == SxeNegotiation::Joiner) {
                    if (!processNegotiationAsJoiner(n.childNodes().at(j), negotiation, response, message))
                        return nullptr;
                } else {
                    Q_ASSERT(false);
                }

                // Send any responses that were generated
                if (response.hasChildNodes()) {
                    sxe.appendChild(response);
                    sendSxe(sxe.cloneNode().toElement(), negotiation->peer, negotiation->groupChat);
                    sxe.removeChild(response);
                }

                while (response.hasChildNodes())
                    response.removeChild(response.firstChild());
            }

        } else if (negotiation->state == SxeNegotiation::Finished && negotiation->session) {

            // There should be no more children after <negotiation/>...
            qDebug("Children after <negotiation/> in <sxe/>.");
        }
    }

    // Cleanup:
    // Delete negotation objects that are no longer needed
    if (negotiation) {

        if (negotiation->state == SxeNegotiation::NotStarted) {

            removeNegotiation(negotiation);

        } else if (negotiation->state == SxeNegotiation::Finished) {

            // Save session for successful negotiations but delete the negotiation object
            SxeSession *sxesession = negotiation->session;
            if (sxesession) {
                if (!sessions_.contains(sxesession)) {

                    // store and emit a signal about the session only if it's new
                    sessions_.append(sxesession);
                    emit sessionNegotiated(sxesession);
                }
            }

            removeNegotiation(negotiation);

            // return a handle to the session
            return sxesession;
        }
    }
    return nullptr;
}

SxeManager::SxeNegotiation *SxeManager::findNegotiation(const Jid &jid, const QString &session)
{
    QList<SxeNegotiation *> negotiations = negotiations_.values(session);
    for (SxeNegotiation *negotiation : negotiations) {
        if (negotiation->state != SxeNegotiation::Aborted
            && negotiation->peer.compare(jid, negotiation->state != SxeNegotiation::ConnectionRequested))
            return negotiation;
    }

    return nullptr;
}

void SxeManager::removeNegotiation(SxeNegotiation *negotiation)
{
    negotiations_.remove(negotiation->sessionId, negotiation);
    delete negotiation;
}

SxeManager::SxeNegotiation *SxeManager::createNegotiation(SxeNegotiation::Role role, SxeNegotiation::State state,
                                                          const QString &sessionId, const Jid &target,
                                                          const Jid &ownJid, bool groupChat)
{
    SxeNegotiation *negotiation = new SxeNegotiation;
    negotiation->role           = role;
    negotiation->state          = state;
    negotiation->sessionId      = sessionId;
    negotiation->target         = target;
    negotiation->peer           = target;
    negotiation->ownJid         = ownJid;
    negotiation->groupChat      = groupChat;
    negotiation->session        = nullptr;

    negotiations_.insert(sessionId, negotiation);

    return negotiation;
}

SxeManager::SxeNegotiation *SxeManager::createNegotiation(const Message &message)
{

    // Create a new negotiation object

    SxeNegotiation *negotiation = new SxeNegotiation;

    negotiation->sessionId = message.sxe().attribute("session");
    negotiation->session   = findSession(negotiation->sessionId);

    negotiation->peer = message.from();

    if (negotiation->session) {

        // If negotiation exists, we're going to be the "server" for the negotiation
        negotiation->role      = SxeNegotiation::Participant;
        negotiation->state     = SxeNegotiation::Finished;
        negotiation->target    = negotiation->session->target();
        negotiation->groupChat = negotiation->session->groupChat();
        negotiation->ownJid    = negotiation->session->ownJid();
        negotiation->features  = negotiation->session->features();

    } else {

        // Otherwise we're joining a session
        negotiation->role  = SxeNegotiation::Joiner;
        negotiation->state = SxeNegotiation::NotStarted;

        if (message.type() == "groupchat") {

            // If we're being invited from a groupchat,
            // ownJid is determined based on the bare part of ownJids_

            negotiation->groupChat = true;
            for (const QString &j : qAsConst(ownJids_)) {
                if (message.from().bare() == j.left(j.indexOf("/"))) {
                    negotiation->ownJid = j;
                    break;
                }
            }
            // Also, the target is just the bare JID in a groupchat
            negotiation->target = message.from().bare();

        } else {

            negotiation->groupChat = false;
            negotiation->target    = negotiation->peer;
        }

        if (negotiation->ownJid.isEmpty())
            negotiation->ownJid = message.to();
    }

    // Reset the timeout
    negotiationTimer_.start();

    // Store the session
    negotiations_.insert(negotiation->sessionId, negotiation);

    return negotiation;
}

void SxeManager::joinSession(const Jid &target, const Jid &ownJid, bool groupChat, const QString &session)
{
    // Prepare the <connect-request/>
    QDomDocument doc;
    QDomElement  sxe = doc.createElementNS(SXENS, "sxe");
    sxe.setAttribute("session", session);
    QDomElement negotiationElement = doc.createElementNS(SXENS, "negotiation");
    QDomElement request            = doc.createElementNS(SXENS, "connect-request");
    negotiationElement.appendChild(request);
    sxe.appendChild(negotiationElement);

    // Create the negotiation object
    createNegotiation(SxeNegotiation::Joiner, SxeNegotiation::ConnectionRequested, session, target, ownJid, groupChat);

    sendSxe(sxe, target, groupChat);

    // Reset the timeout for negotiations
    negotiationTimer_.start();

    return;
}

void SxeManager::startNewSession(const Jid &target, const Jid &ownJid, bool groupChat, const QDomDocument &initialDoc,
                                 QList<QString> features)
{

    // check that the target supports SXE and all specified features
    if (!checkSupport(target, features)) {
        qDebug() << QString("Tried to start an SXE session with %1 but the client doesn't support all features.")
                        .arg(target.full())
                        .toLatin1();
        return;
    }

    // generate a session identifier
    QString session;
    do {
        session = SxeSession::generateUUID();
    } while (findSession(session));

    if (features.size() == 0) {
        // some features must be specified
        return;
    }

    // Prepare the <invitation/>
    QDomDocument doc;
    QDomElement  sxe = doc.createElementNS(SXENS, "sxe");
    sxe.setAttribute("session", session);
    QDomElement negotiationElement = doc.createElementNS(SXENS, "negotiation");
    QDomElement request            = doc.createElementNS(SXENS, "invitation");
    QDomElement feature            = doc.createElementNS(SXENS, "feature");
    for (const QString &f : qAsConst(features)) {
        feature = feature.cloneNode(false).toElement();
        feature.appendChild(doc.createTextNode(f));
        request.appendChild(feature);
    }
    negotiationElement.appendChild(request);
    sxe.appendChild(negotiationElement);

    // Create the negotiation object
    SxeNegotiation *negotiation = createNegotiation(SxeNegotiation::Participant, SxeNegotiation::InvitationSent,
                                                    session, target, ownJid, groupChat);
    negotiation->initialDoc     = initialDoc;
    negotiation->features       = features;

    sendSxe(sxe, target, groupChat);

    // Reset the timeout for negotiations
    negotiationTimer_.start();

    return;
}

void SxeManager::negotiationTimeout()
{
    const auto &negotiations = negotiations_.values();
    for (SxeNegotiation *negotiation : negotiations) {
        if (negotiation->role == SxeNegotiation::Participant && negotiation->state < SxeNegotiation::HistoryOffered
            && negotiation->state != SxeNegotiation::DocumentBegan) {
            if (negotiation->session)
                negotiation->session->endSession();
            abortNegotiation(negotiation->sessionId, negotiation->peer, negotiation->groupChat);
        }
        delete negotiation;
    }

    negotiations_.clear();
}

// #include <QTextStream>
void SxeManager::sendSxe(QDomElement sxe, const Jid &receiver, bool groupChat)
{

    SxeSession *session = qobject_cast<SxeSession *>(sender());

    // Add a unique id to each sent sxe element
    if (session) {
        QString id = session->generateUUIDForSession();
        sxe.setAttribute("id", id);
        session->addUsedSxeId(id);
    } else
        sxe.setAttribute("id", SxeSession::generateUUID());

    Message       m(receiver);
    QDomDocument *clientDoc = pa_->client()->doc();
    // QDomElement el = sxe.ownerDocument() == *clientDoc ? sxe : clientDoc->importNode(sxe, true).toElement();
    m.setSxe(clientDoc->importNode(sxe, true).toElement());
    if (groupChat && receiver.resource().isEmpty())
        m.setType("groupchat");

    if (client_->isActive()) {
        // send queued messages first
        while (!queuedMessages_.isEmpty()) {
            Message message = queuedMessages_.takeFirst();
            client_->sendMessage(message);
        }

        client_->sendMessage(m);
    } else {
        queuedMessages_.append(m);
    }
}

QList<QPointer<SxeSession>> SxeManager::findSession(const Jid &jid)
{
    // find if a session for the jid already exists
    QList<QPointer<SxeSession>> matching;
    for (const QPointer<SxeSession> &w : qAsConst(sessions_)) {
        // does the jid match?
        if (w->target().compare(jid)) {
            matching.append(w);
        }
    }
    return matching;
}

QPointer<SxeSession> SxeManager::findSession(const QString &session)
{
    // find if a session for the session already exists
    for (SxeSession *w : qAsConst(sessions_)) {
        // does the session match?
        if (w->session() == session)
            return w;
    }
    return nullptr;
}

QPointer<SxeSession> SxeManager::createSxeSession(const Jid &target, QString session, const Jid &ownJid, bool groupChat,
                                                  const QList<QString> &features)
{
    if (session.isEmpty() || !target.isValid())
        return nullptr;
    if (!ownJids_.contains(ownJid.full()))
        ownJids_.append(ownJid.full());
    // FIXME: detect serverside support
    bool serverSupport = false;
    // create the SxeSession
    QPointer<SxeSession> w = new SxeSession(this, target, session, ownJid, groupChat, serverSupport, features);
    // connect the signals
    connect(w, SIGNAL(newSxeElement(QDomElement, Jid, bool)), SLOT(sendSxe(const QDomElement &, const Jid &, bool)));
    connect(w, SIGNAL(sessionEnded(SxeSession *)), SLOT(removeSession(SxeSession *)));
    removeDetectedSession(w);
    return w;
    // Note: the session should be added to sessions_ once negotiation is finished
}

void SxeManager::abortNegotiation(QString session, const Jid &peer, bool groupChat)
{
    QDomDocument doc = QDomDocument();
    QDomElement  sxe = doc.createElementNS(SXENS, "sxe");
    sxe.setAttribute("session", session);
    QDomElement negotiationElement = doc.createElementNS(SXENS, "negotiation");
    negotiationElement.appendChild(doc.createElementNS(SXENS, "abort-negotiation"));
    sxe.appendChild(negotiationElement);
    sendSxe(sxe, peer, groupChat);
}

void SxeManager::abortNegotiation(SxeNegotiation *negotiation)
{
    abortNegotiation(negotiation->sessionId, negotiation->peer, negotiation->groupChat);

    removeNegotiation(negotiation);
}

void SxeManager::removeDetectedSession(SxeSession *session)
{
    for (int i = 0; i < DetectedSessions_.size(); i++) {
        DetectedSession detected = DetectedSessions_.at(i);
        // Remove the specified session from the list
        if (detected.session == session->session() && detected.jid.compare(session->target(), true))
            DetectedSessions_.removeAt(i);
        else if (detected.time.secsTo(QTime::currentTime()) > 1800)
            // Remove detected session that are old
            DetectedSessions_.removeAt(i);
    }
}

void SxeManager::groupChatLeft(const Jid &jid)
{
    for (int i = 0; i < ownJids_.size(); i++) {
        if (jid.bare() == ownJids_.at(i).left(ownJids_.at(i).indexOf("/")))
            ownJids_.removeAt(i);
    }
    QList<QPointer<SxeSession>> matching = findSession(jid);
    for (QPointer<SxeSession> w : matching)
        w->endSession();
}

void SxeManager::groupChatJoined(const Jid &, const Jid &ownJid)
{
    if (!ownJids_.contains(ownJid.full()))
        ownJids_.append(ownJid.full());
}

bool SxeManager::checkSupport(const Jid &jid, const QList<QString> &features)
{
    Features f = pa_->client()->capsManager()->features(jid);

    return f.test(SXENS) && f.test(features);
}
