// SPDX-FileCopyrightText: 2021 Linus Jahn <lnj@kaidan.im>
//
// SPDX-License-Identifier: LGPL-2.1-or-later

#include "QXmppDataFormBase.h"

#include "QXmppDataForm.h"

#include "StringLiterals.h"

#include <QDateTime>

/*!
    \class QXmppDataFormBase
    \inmodule QXmpp

    QXmppDataFormBase is an abstract class types that can be serialized to data
    forms.

    QXmppDataFormBase based types can easily be converted to QXmppDataForms, it
    is as simple as this:
    \code
    MyDataFormBase foo;
    QXmppDataForm dataForm(foo);
    \endcode

    To make this work, you will need to at least implement the toDataForm()
    method. For parsing your type you should also create a static creator
    method, like this:
    \code
    static std::optional<MyType> fromDataForm(const QXmppDataForm &);
    \endcode

    \since QXmpp 1.5
*/

/*! Serializes all fields to a QXmppDataForm. */
QXmppDataForm QXmppDataFormBase::toDataForm() const
{
    QXmppDataForm form(QXmppDataForm::Form);

    // add FORM_TYPE
    serializeNullable(form, QXmppDataForm::Field::HiddenField, u"FORM_TYPE"_s, formType());

    // manual serialization parts
    serializeForm(form);

    return form;
}

/*!
    Parses the QXmppDataForm. Returns true on success.

    \a output and \a form.
*/
bool QXmppDataFormBase::fromDataForm(const QXmppDataForm &form, QXmppDataFormBase &output)
{
    output.parseForm(form);
    return true;
}

/*!
    \fn QString QXmppDataFormBase::formType() const

    Returns the 'FORM_TYPE' value of the parsed form.
*/

/*!
    \fn void QXmppDataFormBase::parseForm(const QXmppDataForm &form)

    This is called when a QXmppDataForm is parsed. You can parse all values from
    the given \a form and its fields.
*/

/*!
    \fn void QXmppDataFormBase::serializeForm(QXmppDataForm &form) const

    This is called when the object is serialized to a QXmppDataForm. You need to
    serialize all fields and values into \a form.
*/

/*!
    \fn std::optional<quint32> QXmppDataFormBase::parseUInt(const QVariant &variant)

    Parses an unsigned int from a QVariant (QString) \a variant. Returns
    std::nullopt if no number could be parsed.
*/

/*!
    \fn std::optional<quint64> QXmppDataFormBase::parseULongLong(const QVariant &variant)

    Parses an unsigned long long from a QVariant (QString) \a variant. Returns
    std::nullopt if no number could be parsed.
*/

/*!
    \fn std::optional<bool> QXmppDataFormBase::parseBool(const QVariant &variant)

    Returns the contained boolean value if the QVariant \a variant contains a bool.
*/

/*!
    \fn template <typename T> void QXmppDataFormBase::serializeValue(QXmppDataForm &form, QXmppDataForm::Field::Type type, const QString &name, const T &value)

    Adds a new field to \a form with the given field \a type, field \a name and \a value.
*/

/*!
    \fn template <typename T> void QXmppDataFormBase::serializeNullable(QXmppDataForm &form, QXmppDataForm::Field::Type type, QStringView name, const T &value)

    Adds a new field of \a type and \a name to \a form if \c {!value.isNull()} for \a value.
*/

/*!
    \fn template <typename T> void QXmppDataFormBase::serializeEmptyable(QXmppDataForm &form, QXmppDataForm::Field::Type type, QStringView name, const T &value)

    Adds a new field of \a type and \a name to \a form if \c {!value.isEmpty()} for \a value.
*/

/*!
    \fn template <typename T, typename ValueConverter> void QXmppDataFormBase::serializeOptional(QXmppDataForm &form, QXmppDataForm::Field::Type type, QStringView name, const std::optional<T> &optional, ValueConverter convert)

    Adds a new field of \a type and \a name to \a form if \c {optional.has_value()} for
    \a optional, applying \a convert to the contained value first.
*/

/*!
    \fn template <typename T> void QXmppDataFormBase::serializeOptionalNumber(QXmppDataForm &form, QXmppDataForm::Field::Type type, QStringView name, std::optional<T> optional)

    Adds a new field of \a type and \a name to \a form if \c {optional.has_value()} for
    \a optional, converting the contained value to QString using QString::number().
*/

/*!
    Adds a new field to the form if the passed QDateTime is valid and formats it
    as ISO timestamp and always uses UTC.

    \a name, \a type, \a datetime, and \a form.
*/
void QXmppDataFormBase::serializeDatetime(QXmppDataForm &form, const QString &name, const QDateTime &datetime, QXmppDataForm::Field::Type type)
{
    if (datetime.isValid()) {
        serializeValue(form, type, name, datetime.toUTC().toString(Qt::ISODate));
    }
}

/*!
    \class QXmppExtensibleDataFormBase
    \inmodule QXmpp

    This class is used for parsing a QXmppDataForm in an extensible way with
    inheritance and keeping additional unknown fields.

    When inheriting you need to reimplement parseField(), serializeForm() and
    formType(). Also you should add a static parsing function (e.g.
    QXmppPubSubMetadata::fromDataForm()).

    \since QXmpp 1.5
*/

class QXmppExtensibleDataFormBasePrivate : public QSharedData
{
public:
    QList<QXmppDataForm::Field> unknownFields;
};

QXmppExtensibleDataFormBase::QXmppExtensibleDataFormBase()
    : d(new QXmppExtensibleDataFormBasePrivate)
{
}

QXmppExtensibleDataFormBase::QXmppExtensibleDataFormBase(const QXmppExtensibleDataFormBase &) = default;
QXmppExtensibleDataFormBase::QXmppExtensibleDataFormBase(QXmppExtensibleDataFormBase &&) = default;
QXmppExtensibleDataFormBase::~QXmppExtensibleDataFormBase() = default;
QXmppExtensibleDataFormBase &QXmppExtensibleDataFormBase::operator=(const QXmppExtensibleDataFormBase &) = default;
QXmppExtensibleDataFormBase &QXmppExtensibleDataFormBase::operator=(QXmppExtensibleDataFormBase &&) = default;

/*! Returns all fields that couldn't be parsed. */
QList<QXmppDataForm::Field> QXmppExtensibleDataFormBase::unknownFields() const
{
    return d->unknownFields;
}

/*!
    Sets all additional fields to be serialized.

    \a unknownFields.
*/
void QXmppExtensibleDataFormBase::setUnknownFields(const QList<QXmppDataForm::Field> &unknownFields)
{
    d->unknownFields = unknownFields;
}

void QXmppExtensibleDataFormBase::parseForm(const QXmppDataForm &form)
{
    const auto fields = form.fields();
    for (const auto &field : fields) {
        // FORM_TYPE fields are not saved (override this function to save them)
        if (!parseField(field) &&
            !(field.type() == QXmppDataForm::Field::HiddenField &&
              field.key() == u"FORM_TYPE")) {
            d->unknownFields << field;
        }
    }
}

void QXmppExtensibleDataFormBase::serializeForm(QXmppDataForm &form) const
{
    form.setFields(std::as_const(form).fields() + d->unknownFields);
}

/*!
    Returns true if a field has been parsed.

    Should be reimplemented to do actual parsing. All fields that can't be
    parsed end up as unknownFields().
*/
bool QXmppExtensibleDataFormBase::parseField(const QXmppDataForm::Field &)
{
    return false;
}
