/*
 * xmpp_discoitem.cpp
 * Copyright (C) 2003  Justin Karneges
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

#include "xmpp_discoitem.h"

#include <QtXml>

using namespace XMPP;

class XMPP::DiscoItemPrivate : public QSharedData {
public:
    DiscoItemPrivate() { action = DiscoItem::None; }

    Jid               jid;
    QString           name;
    QString           node;
    DiscoItem::Action action;

    Features              features;
    DiscoItem::Identities identities;
    QList<XData>          exts;
};

DiscoItem::DiscoItem() : d(new DiscoItemPrivate) { }

DiscoItem::DiscoItem(const DiscoItem &from) : d(new DiscoItemPrivate) { *this = from; }

DiscoItem &DiscoItem::operator=(const DiscoItem &from)
{
    d->jid        = from.d->jid;
    d->name       = from.d->name;
    d->node       = from.d->node;
    d->action     = from.d->action;
    d->features   = from.d->features;
    d->identities = from.d->identities;
    d->exts       = from.d->exts;

    return *this;
}

DiscoItem::~DiscoItem() { }

AgentItem DiscoItem::toAgentItem() const
{
    AgentItem ai;

    ai.setJid(jid());
    ai.setName(name());

    Identity id;
    if (!identities().isEmpty())
        id = identities().first();

    ai.setCategory(id.category);
    ai.setType(id.type);

    ai.setFeatures(d->features);

    return ai;
}

void DiscoItem::fromAgentItem(const AgentItem &ai)
{
    setJid(ai.jid());
    setName(ai.name());

    Identity id;
    id.category = ai.category();
    id.type     = ai.type();
    id.name     = ai.name();

    Identities idList;
    idList << id;

    setIdentities(idList);

    setFeatures(ai.features());
}

QString DiscoItem::capsHash(QCryptographicHash::Algorithm algo) const
{
    QStringList           prep;
    DiscoItem::Identities idents = d->identities;
    std::sort(idents.begin(), idents.end());

    for (const DiscoItem::Identity &id : idents) {
        prep << QString("%1/%2/%3/%4").arg(id.category, id.type, id.lang, id.name);
    }

    QStringList fl = d->features.list();
    std::sort(fl.begin(), fl.end());
    prep += fl;

    QMap<QString, XData> forms;
    for (const XData &xd : d->exts) {
        if (xd.registrarType().isEmpty()) {
            continue;
        }
        if (forms.contains(xd.registrarType())) {
            return QString(); // ill-formed
        }
        forms.insert(xd.registrarType(), xd);
    }
    const auto &xds = forms.values();
    for (const XData &xd : xds) {
        prep << xd.registrarType();
        QMap<QString, QStringList> values;
        for (const XData::Field &f : xd.fields()) {
            if (f.var() == QLatin1String("FORM_TYPE")) {
                continue;
            }
            if (values.contains(f.var())) {
                return QString(); // ill-formed
            }
            QStringList v = f.value();
            if (v.isEmpty()) {
                continue; // maybe it's media-element but xep-115 (1.5) and xep-232 (0.3) are not clear about that.
            }
            std::sort(v.begin(), v.end());
            values[f.var()] = v;
        }
        QMap<QString, QStringList>::ConstIterator it = values.constBegin();
        for (; it != values.constEnd(); ++it) {
            prep += it.key();
            prep += it.value();
        }
    }

    QByteArray ba = QString(prep.join(QLatin1String("<")) + QLatin1Char('<')).toUtf8();
    // qDebug() << "Server caps ver: " << (prep.join(QLatin1String("<")) + QLatin1Char('<'))
    //         << "Hash:" << QString::fromLatin1(QCryptographicHash::hash(ba, algo).toBase64());
    return QString::fromLatin1(QCryptographicHash::hash(ba, algo).toBase64());
}

DiscoItem DiscoItem::fromDiscoInfoResult(const QDomElement &q)
{
    DiscoItem item;

    item.setNode(q.attribute("node"));

    QStringList           features;
    DiscoItem::Identities identities;
    QList<XData>          extList;

    for (QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) {
        QDomElement e = n.toElement();
        if (e.isNull())
            continue;

        if (e.tagName() == "feature") {
            features << e.attribute("var");
        } else if (e.tagName() == "identity") {
            DiscoItem::Identity id;

            id.category = e.attribute("category");
            id.type     = e.attribute("type");
            id.lang     = e.attribute("lang");
            id.name     = e.attribute("name");

            identities.append(id);
        } else if (e.tagName() == QLatin1String("x") && e.namespaceURI() == QLatin1String("jabber:x:data")) {
            XData form;
            form.fromXml(e);
            extList.append(form);
        }
    }

    item.setFeatures(features);
    item.setIdentities(identities);
    item.setExtensions(extList);

    return item;
}

QDomElement DiscoItem::toDiscoInfoResult(QDomDocument *doc) const
{
    QDomElement q
        = doc->createElementNS(QLatin1String("http://jabber.org/protocol/disco#info"), QLatin1String("query"));
    q.setAttribute("node", d->node);

    for (const Identity &id : d->identities) {
        QDomElement idel = q.appendChild(doc->createElement(QLatin1String("identity"))).toElement();
        idel.setAttribute("category", id.category);
        idel.setAttribute("type", id.type);
        if (!id.lang.isEmpty()) {
            idel.setAttribute("lang", id.lang);
        }
        if (!id.name.isEmpty()) {
            idel.setAttribute("name", id.name);
        }
    }

    const QStringList &features = d->features.list();
    for (const QString &f : features) {
        QDomElement fel = q.appendChild(doc->createElement(QLatin1String("feature"))).toElement();
        fel.setAttribute("var", f);
    }

    for (const XData &f : d->exts) {
        q.appendChild(f.toXml(doc));
    }

    return q;
}

const Jid &DiscoItem::jid() const { return d->jid; }

void DiscoItem::setJid(const Jid &j) { d->jid = j; }

const QString &DiscoItem::name() const { return d->name; }

void DiscoItem::setName(const QString &n) { d->name = n; }

const QString &DiscoItem::node() const { return d->node; }

void DiscoItem::setNode(const QString &n) { d->node = n; }

DiscoItem::Action DiscoItem::action() const { return d->action; }

void DiscoItem::setAction(Action a) { d->action = a; }

const Features &DiscoItem::features() const { return d->features; }

void DiscoItem::setFeatures(const Features &f) { d->features = f; }

const DiscoItem::Identities &DiscoItem::identities() const { return d->identities; }

void DiscoItem::setIdentities(const Identities &i)
{
    d->identities = i;

    if (name().isEmpty() && i.count())
        setName(i.first().name);
}

const QList<XData> &DiscoItem::extensions() const { return d->exts; }

XData DiscoItem::findExtension(XData::Type xdataType, const QString &formType) const
{
    for (const auto &x : d->exts) {
        if (x.type() == xdataType && (formType.isEmpty() || x.registrarType() == formType)) {
            return x;
        }
    }
    return XData(XData::Data_Invalid);
}

void DiscoItem::setExtensions(const QList<XData> &extlist) { d->exts = extlist; }

XData DiscoItem::registeredExtension(const QString &ns) const
{
    for (const XData &xd : d->exts) {
        if (xd.registrarType() == ns) {
            return xd;
        }
    }
    return XData();
}

DiscoItem::Action DiscoItem::string2action(const QString &s)
{
    Action a;

    if (s == "update")
        a = Update;
    else if (s == "remove")
        a = Remove;
    else
        a = None;

    return a;
}

QString DiscoItem::action2string(const Action a)
{
    QString s;

    if (a == Update)
        s = "update";
    else if (a == Remove)
        s = "remove";
    else
        s = QString();

    return s;
}

bool XMPP::operator<(const DiscoItem::Identity &a, const DiscoItem::Identity &b)
{
    int r = a.category.compare(b.category);
    if (!r) {
        r = a.type.compare(b.type);
        if (!r) {
            r = a.lang.compare(b.lang);
            if (!r) {
                r = a.name.compare(b.name);
            }
        }
    }

    return r < 0;
}

bool DiscoItem::Identity::operator==(const DiscoItem::Identity &other) const
{
    return category == other.category && type == other.type && lang == other.lang && name == other.name;
}
