/*
 * pepmanager.cpp - Classes for PEP
 * Copyright (C) 2006  Remko Troncon
 *
 * 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 "pepmanager.h"

#include "iris/xmpp_client.h"
#include "iris/xmpp_pubsubitem.h"
#include "iris/xmpp_pubsubretraction.h"
#include "iris/xmpp_serverinfomanager.h"
#include "iris/xmpp_tasks.h"
#include "iris/xmpp_xmlcommon.h"

#include <QtDebug>

using namespace XMPP;

// TODO: Get affiliations upon startup, and only create nodes based on that.
// (subscriptions is not accurate, since one doesn't subscribe to the
// avatar data node)

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

/*
class PEPUnsubscribeTask : public Task
{
public:
    PEPUnsubscribeTask(Task* parent, const QString& jid, const QString& node) : Task(parent), jid_(jid) {
        iq_ = createIQ(doc(), "set", jid_, id());

        QDomElement pubsub = doc()->createElementNS("http://jabber.org/protocol/pubsub", "pubsub");
        iq_.appendChild(pubsub);

        QDomElement unsubscribe = doc()->createElement("unsubscribe");
        unsubscribe.setAttribute("node", node);
        unsubscribe.setAttribute("jid", client()->jid().bare());
        pubsub.appendChild(unsubscribe);
    }

    void onGo() {
        send(iq_);
    }

    bool take(const QDomElement& x) {
        if(!iqVerify(x, jid_, id()))
            return false;

        if(x.attribute("type") == "result") {
            setSuccess();
        }
        else {
            setError(x);
        }
        return true;
    }

private:
    QDomElement iq_;
    QString jid_;
};

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

class PEPSubscribeTask : public Task
{
public:
    PEPSubscribeTask(Task* parent, const QString& jid, const QString& node) : Task(parent), jid_(jid) {
        iq_ = createIQ(doc(), "set", jid_, id());

        QDomElement pubsub = doc()->createElementNS("http://jabber.org/protocol/pubsub", "pubsub");
        iq_.appendChild(pubsub);

        QDomElement subscribe = doc()->createElement("subscribe");
        subscribe.setAttribute("node", node);
        subscribe.setAttribute("jid", client()->jid().bare());
        pubsub.appendChild(subscribe);
    }

    void onGo() {
        send(iq_);
    }

    bool take(const QDomElement& x) {
        if(!iqVerify(x, jid_, id()))
            return false;

        if(x.attribute("type") == "result") {
            setSuccess();
        }
        else {
            setError(x);
        }
        return true;
    }

private:
    QDomElement iq_;
    QString jid_;
};*/

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

/*
class PEPCreateNodeTask : public Task
{
public:
    PEPCreateNodeTask(Task* parent, const QString& node) : Task(parent) {
        node_ = node;
        iq_ = createIQ(doc(), "set", "", id());

        QDomElement pubsub = doc()->createElementNS("http://jabber.org/protocol/pubsub", "pubsub");
        iq_.appendChild(pubsub);

        QDomElement subscribe = doc()->createElement("create");
        subscribe.setAttribute("node", node);
        pubsub.appendChild(subscribe);

        QDomElement configure = doc()->createElement("configure");
        pubsub.appendChild(configure);
    }

    const QString& node() const {
        return node_;
    }

    void onGo() {
        send(iq_);
    }

    bool take(const QDomElement& x) {
        if(!iqVerify(x, "", id()))
            return false;

        if(x.attribute("type") == "result") {
            setSuccess();
        }
        else {
            setError(x);
        }
        return true;
    }

private:
    QDomElement iq_;
    QString node_;
};*/

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

class PEPPublishTask : public Task {
public:
    PEPPublishTask(Task *parent, const QString &node, const PubSubItem &it, std::optional<PEPManager::Access> access,
                   bool persisteItems = false) : Task(parent), node_(node), item_(it)
    {
        iq_ = createIQ(doc(), "set", "", id());

        QDomElement pubsub = doc()->createElementNS("http://jabber.org/protocol/pubsub", "pubsub");
        iq_.appendChild(pubsub);

        QDomElement publish = doc()->createElement("publish");
        publish.setAttribute("node", node);
        pubsub.appendChild(publish);

        QDomElement item = doc()->createElement("item");
        if (!it.id().isEmpty()) {
            item.setAttribute("id", it.id());
        }
        publish.appendChild(item);

        if (access || persisteItems) {
            QDomElement conf = doc()->createElement("publish-options");
            XData       form(XData::Data_Submit);
            form.setRegistrarType(QLatin1String("http://jabber.org/protocol/pubsub#publish-options"));
            XMPP::XData::FieldList fields;
            if (access) {
                XMPP::XData::Field f;
                f.setVar(QLatin1String("pubsub#access_model"));
                if (*access == PEPManager::Access::Open) {
                    f.setValue({ QLatin1String("open") });
                } else if (access == PEPManager::Access::Presence) {
                    f.setValue({ QLatin1String("presence") });
                } else if (access == PEPManager::Access::Roster) {
                    f.setValue({ QLatin1String("roster") });
                } else if (access == PEPManager::Access::Authorize) {
                    f.setValue({ QLatin1String("authorize") });
                } else if (access == PEPManager::Access::Whitelist) {
                    f.setValue({ QLatin1String("whitelist") });
                }
                fields << f;
            }
            if (persisteItems) {
                XMPP::XData::Field f;
                f.setVar(QLatin1String("pubsub#persist_items"));
                f.setValue({ QLatin1String("true") });
                fields << f;
            }
            form.setFields(fields);
            auto conf_x = form.toXml(doc(), true);

            conf.appendChild(conf_x);
            pubsub.appendChild(conf);
        }

        item.appendChild(it.payload());
    }

    bool take(const QDomElement &x)
    {
        if (!iqVerify(x, "", id()))
            return false;

        if (x.attribute("type") == "result") {
            setSuccess();
        } else {
            setError(x);
        }
        return true;
    }

    void onGo() { send(iq_); }

    const PubSubItem &item() const { return item_; }

    const QString &node() const { return node_; }

private:
    QDomElement iq_;
    QString     node_;
    PubSubItem  item_;
};

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

class PEPRetractTask : public Task {
public:
    PEPRetractTask(Task *parent, const QString &node, const QString &itemId) :
        Task(parent), node_(node), itemId_(itemId)
    {
        iq_ = createIQ(doc(), "set", "", id());

        QDomElement pubsub = doc()->createElementNS("http://jabber.org/protocol/pubsub", "pubsub");
        iq_.appendChild(pubsub);

        QDomElement retract = doc()->createElement("retract");
        retract.setAttribute("node", node);
        retract.setAttribute("notify", "1");
        pubsub.appendChild(retract);

        QDomElement item = doc()->createElement("item");
        item.setAttribute("id", itemId);
        retract.appendChild(item);
    }

    bool take(const QDomElement &x)
    {
        if (!iqVerify(x, "", id()))
            return false;

        if (x.attribute("type") == "result") {
            setSuccess();
        } else {
            setError(x);
        }
        return true;
    }

    void onGo() { send(iq_); }

    const QString &node() const { return node_; }

private:
    QDomElement iq_;
    QString     node_;
    QString     itemId_;
};

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

/*
class GetPubSubSubscriptionsTask : public Task
{
public:
    GetPubSubSubscriptionsTask(Task* parent, const Jid& jid) : Task(parent), jid_(jid) {
        iq_ = createIQ(doc(), "get", jid_.bare(), id());

        QDomElement pubsub = doc()->createElement("pubsub");
        pubsub.setAttribute("xmlns", "http://jabber.org/protocol/pubsub");
        iq_.appendChild(pubsub);

        QDomElement subscriptions = doc()->createElement("subscriptions");
        pubsub.appendChild(subscriptions);
    }

    bool take(const QDomElement &x) {
        if(!iqVerify(x, jid_.bare(), id()))
            return false;

        if(x.attribute("type") == "result") {
            subscriptions_.clear();
            for(QDomNode m = x.firstChild(); !m.isNull(); m = m.nextSibling()) {
                QDomElement me = m.toElement();
                if (me.tagName() != "pubsub")
                    continue;

                for(QDomNode n = me.firstChild(); !n.isNull(); n = n.nextSibling()) {
                    QDomElement s = n.toElement();
                    if (s.tagName() != "subscriptions")
                        continue;

                    for(QDomNode sub_node = s.firstChild(); !sub_node.isNull(); sub_node = sub_node.nextSibling()) {
                        // TODO: Check if the jid is the right one (ours) ?
                        PubSubSubscription sub(sub_node.toElement());
                        subscriptions_ += sub;
                    }
                }
            }

            setSuccess();
            return true;
        }
        else {
            setError(x);
            return true;
        }
    }

    const Jid& jid() const {
        return jid_;
    }

    const QList<PubSubSubscription> subscriptions() const {
        return subscriptions_;
    }

    void onGo() {
        send(iq_);
    }

private:
    QDomElement iq_;
    Jid jid_;
    QList<PubSubSubscription> subscriptions_;
};*/

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

PEPManager::PEPManager(Client *client, ServerInfoManager *serverInfo) : client_(client), serverInfo_(serverInfo)
{
    connect(client_, SIGNAL(messageReceived(const Message &)), SLOT(messageReceived(const Message &)));
}

/*void PEPManager::setAvailable(bool a)
{
    if (available_ != a) {
        available_ = a;
        emit available(available_);
    }
}

void PEPManager::registerNode(const QString& node)
{
    if (nodes_.contains(node))
        return;

    if (available_) {
        createNode(node);
    }

    nodes_ += node;
}

void PEPManager::registerNodes(const QStringList& nodes)
{
    foreach(QString node, nodes) {
        registerNode(node);
    }
}

bool PEPManager::canPublish(const QString& node) const
{
    return ensured_nodes_.contains(node);
}

void PEPManager::saveSubscriptions()
{
}

void PEPManager::createNode(const QString& node)
{
    PEPCreateNodeTask* t = new PEPCreateNodeTask(client_->rootTask(),node);
    connect(t,SIGNAL(finished()),SLOT(createFinished()));
    t->go(true);
}

void PEPManager::subscribe(const QString& jid, const QString& ns)
{
    PEPSubscribeTask* t = new PEPSubscribeTask(client_->rootTask(),jid,ns);
    connect(t,SIGNAL(finished()),SLOT(subscribeFinished()));
    t->go(true);
}

void PEPManager::createFinished()
{
    PEPCreateNodeTask* task = (PEPCreateNodeTask*) sender();
    if (task->success() || task->statusCode() == 409) {
        if (task->statusCode() == 409)
            qWarning(QString("[%1] PEP Node already exists. Ignoring.").arg(client_->jid().full()));

        // Subscribe to our own nodes
        if (task->node() != "urn:xmpp:avatar:data")
            subscribe(client_->jid().bare(),task->node());

        // Notify
        ensured_nodes_ += task->node();
        emit ready(task->node());
    }
    else {
        qWarning(QString("[%3] PEP Create failed: '%1'
(%2)").arg(task->statusString()).arg(QString::number(task->statusCode())).arg(client_->jid().full()));
    }
}

void PEPManager::subscribeFinished()
{
    PEPSubscribeTask* task = (PEPSubscribeTask*) sender();
    if (task->success()) {
        //subscriptions_ += task->subscription();
        saveSubscriptions();
    }
    else {
        qWarning(QString("[%3] PEP Subscribe failed: '%1'
(%2)").arg(task->statusString()).arg(QString::number(task->statusCode())).arg(client_->jid().full()));
    }
}

void PEPManager::unsubscribe(const QString& jid, const QString& node)
{
    PEPUnsubscribeTask* t = new PEPUnsubscribeTask(client_->rootTask(),jid,node);
    connect(t,SIGNAL(finished()),SLOT(unsubscribeFinished()));
    t->go(true);
}

void PEPManager::unsubscribeFinished()
{
    PEPUnsubscribeTask* task = (PEPUnsubscribeTask*) sender();
    if (!task->success()) {
        qWarning(QString("[%3] PEP Unsubscribe failed: '%1'
(%2)").arg(task->statusString()).arg(QString::number(task->statusCode())).arg(client_->jid().full()));
    }

    // We're being conservative about unsubscribing. Remove subscription,
    // even if there was an error.
    //subscriptions_.remove(task->subscription());
    saveSubscriptions();
}*/

Task *PEPManager::publish(const QString &node, const PubSubItem &it, std::optional<Access> access, bool persisteItems)
{
    // if (!canPublish(node))
    //    return;
    if (!serverInfo_->hasPEP())
        return nullptr;

    PEPPublishTask *tp = new PEPPublishTask(client_->rootTask(), node, it, access, persisteItems);
    connect(tp, SIGNAL(finished()), SLOT(publishFinished()));
    tp->go(true);
    return tp;
}

void PEPManager::retract(const QString &node, const QString &id)
{
    if (!serverInfo_->hasPEP())
        return;

    PEPRetractTask *tp = new PEPRetractTask(client_->rootTask(), node, id);
    // FIXME: add notification of success/failure
    tp->go(true);
}

void PEPManager::disable(const QString &tagName, const QString &node, const QString &id)
{
    // disable by publishing an empty element
    QDomElement element = client_->rootTask()->doc()->createElementNS(node, tagName);

    publish(node, PubSubItem(id, element));
}

void PEPManager::publishFinished()
{
    PEPPublishTask *task = static_cast<PEPPublishTask *>(sender());
    if (task->success()) {
        emit publish_success(task->node(), task->item());
    } else {
        qWarning() << QString("[%3] PEP Publish failed: '%1' (%2)")
                          .arg(task->statusString(), QString::number(task->statusCode()), client_->jid().full());
        emit publish_error(task->node(), task->item());
    }
}

PEPGetTask *PEPManager::get(const Jid &jid, const QString &node, const QString &id)
{
    PEPGetTask *g = new PEPGetTask(client_->rootTask(), jid.bare(), node, id);
    connect(g, SIGNAL(finished()), SLOT(getFinished()));
    g->go(true);
    return g;
}

void PEPManager::messageReceived(const Message &m)
{
    if (m.type() != Message::Type::Error) {
        const auto &psrItems = m.pubsubRetractions();
        for (const PubSubRetraction &i : psrItems) {
            emit itemRetracted(m.from(), m.pubsubNode(), i);
        }
        const auto &psItems = m.pubsubItems();
        for (const PubSubItem &i : psItems) {
            emit itemPublished(m.from(), m.pubsubNode(), i);
        }
    }
}

/*void PEPManager::serverFeaturesChanged()
{
    if (!available_ && serverInfo_->hasPEP()) {
        GetPubSubSubscriptionsTask* task = new GetPubSubSubscriptionsTask(client_->rootTask(),client_->jid());
        connect(task,SIGNAL(finished()),SLOT(getSelfSubscriptionsTaskFinished()));
        task->go(true);
    }
    else if (available_ && !serverInfo_->hasPEP()) {
        ensured_nodes_.clear();
        setAvailable(false);
    }
}*/

void PEPManager::getFinished()
{
    PEPGetTask *task = static_cast<PEPGetTask *>(sender());
    if (task->success()) {
        // Act as if the item was published. This is a convenience
        // implementation, probably should be changed later.
        if (!task->items().isEmpty()) {
            emit itemPublished(task->jid(), task->node(), task->items().first());
        }
    } else {
        qWarning() << QString("PEP Get (jid=%1 node=%2) failed: '%3' (%4)")
                          .arg(task->jid(), task->node(), task->statusString(), QString::number(task->statusCode()));
    }
}

/*
void PEPManager::getSubscriptions(const Jid& jid)
{
    GetPubSubSubscriptionsTask* task = new GetPubSubSubscriptionsTask(client_->rootTask(),jid);
    connect(task,SIGNAL(finished()),SLOT(getSubscriptionsTaskFinished()));
    task->go(true);
}

void PEPManager::getSelfSubscriptionsTaskFinished()
{
    GetPubSubSubscriptionsTask* task = (GetPubSubSubscriptionsTask*) sender();
    if (task->success() || task->statusCode() == 404) {
        QStringList nodes = nodes_;
        foreach(PubSubSubscription s, task->subscriptions()) {
            if (nodes.contains(s.node())) {
                nodes.remove(s.node());
                ensured_nodes_ += s.node();
                emit ready(s.node());

                // Subscribe to our own nodes
                if (s.state() == PubSubSubscription::None && s.node() != "urn:xmpp:avatar:data") {
                    subscribe(client_->jid().bare(),s.node());
                }
            }
        }

        // Create remaining nodes
        foreach(QString node, nodes) {
            createNode(node);
        }
    }
    else {
        qWarning(QString("[%3] Error getting own subscriptions: '%1'
(%2)").arg(task->statusString()).arg(QString::number(task->statusCode())).arg(client_->jid().full()));
    }
}

void PEPManager::getSubscriptionsTaskFinished()
{
    GetPubSubSubscriptionsTask* task = (GetPubSubSubscriptionsTask*) sender();
    if (task->success()) {
        emit getSubscriptions_success(task->jid(), task->subscriptions());
    }
    else {
        emit getSubscriptions_error(task->jid(),task->statusCode(), task->statusString());
    }
}*/

PEPGetTask::PEPGetTask(Task *parent, const QString &jid, const QString &node, const QString &itemID) :
    Task(parent), jid_(jid), node_(node)
{
    iq_ = createIQ(doc(), "get", jid_, id());

    QDomElement pubsub = doc()->createElementNS("http://jabber.org/protocol/pubsub", "pubsub");
    iq_.appendChild(pubsub);

    QDomElement items = doc()->createElement("items");
    items.setAttribute("node", node);
    pubsub.appendChild(items);

    if (!itemID.isEmpty()) {
        QDomElement item = doc()->createElement("item");
        item.setAttribute("id", itemID);
        items.appendChild(item);
    }
}

void PEPGetTask::onGo() { send(iq_); }

bool PEPGetTask::take(const QDomElement &x)
{
    if (!iqVerify(x, jid_, id()))
        return false;

    if (x.attribute("type") == "result") {
        // FIXME Check namespace...
        QDomElement e = x.firstChildElement("pubsub");
        if (!e.isNull()) {
            QDomElement i = e.firstChildElement("items");
            if (!i.isNull()) {
                QString iname = "item";
                for (QDomElement e1 = i.firstChildElement(iname); !e1.isNull(); e1 = e1.nextSiblingElement(iname)) {
                    for (QDomElement e2 = e1.firstChildElement(); !e2.isNull(); e2 = e2.nextSiblingElement()) {
                        items_ += PubSubItem(e1.attribute("id"), e2);
                    }
                }
            }
        }
        setSuccess();
        return true;
    } else {
        setError(x);
        return true;
    }
}

const QList<PubSubItem> &PEPGetTask::items() const { return items_; }

const QString &PEPGetTask::jid() const { return jid_; }

const QString &PEPGetTask::node() const { return node_; }
