{#
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
SPDX-FileCopyrightText: 2024 Arjen Hiemstra <ahiemstra@heimr.nl>

This is the template that is used to generate property class implementations.

REUSE-IgnoreStart
#}
// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
// SPDX-FileCopyrightText: 2024 Arjen Hiemstra <ahiemstra@heimr.nl>
//
// This file is automatically generated from property.cpp.j2.
// To regenerate, run `tools/propertygenerator/generate_properties.py`.
{# REUSE-IgnoreEnd #}

#include "{{ type_name }}.h"

#include <QRegularExpression>

#include "PropertiesTypes.h"
#include "QDataStreamExtras.h"

using namespace Union::Properties;
using namespace Qt::StringLiterals;

class Union::Properties::{{ type_name }}Private
{
public:
{% for property in properties %}
{% if property.is_group %}
    std::unique_ptr<{{ property.type }}> {{ property.name }};
{% else %}
    std::optional<{{ property.type }}> {{ property.name }};
{% endif %}
{% endfor %}
{% if extra_code and "private" in extra_code %}
{{ extra_code["private"] | render }}
{% endif %}
};

{{ type_name }}::{{ type_name }}()
    : d(std::make_unique<{{ type_name }}Private>())
{
}

{{ type_name }}::{{ type_name }}(const {{ type_name }} &other)
    : d(std::make_unique<{{ type_name }}Private>())
{
{% for property in properties %}
{% if property.is_group %}
    d->{{ property.name }} = std::make_unique<{{ property.type }}>();
    *(d->{{ property.name }}) = *(other.d->{{ property.name }});
{% else %}
    d->{{ property.name }} = other.d->{{ property.name }};
{% endif %}
{% endfor %}
}

{{ type_name }}::{{ type_name }}({{ type_name }} &&other)
    : d(std::move(other.d))
{
}

{{ type_name }}::~{{ type_name}}() = default;

{{ type_name }} &{{ type_name }}::operator=(const {{ type_name }} &other)
{
    if (this != &other) {
{% for property in properties %}
{% if property.is_group %}
        *(d->{{ property.name }}) = *(other.d->{{ property.name }});
{% else %}
        d->{{ property.name }} = other.d->{{ property.name }};
{% endif %}
{% endfor %}
    }
    return *this;
}

{{ type_name }} &{{ type_name }}::operator=({{ type_name }} &&other)
{
    std::swap(d, other.d);
    return *this;
}

{% for property in properties %}
{% if property.is_group %}
{{ property.type }} *{{ type_name }}::{{ property.name }}() const
{
    return d->{{ property.name }}.get();
}

void {{ type_name }}::set{{ property.name | ucfirst }}(std::unique_ptr<{{ property.type }}> &&newValue)
{
    d->{{ property.name }} = std::move(newValue);
}

{% else %}
std::optional<{{ property.type }}> {{ type_name }}::{{ property.name }}() const
{
    return d->{{ property.name }};
}

void {{ type_name }}::set{{ property.name | ucfirst }}(const std::optional<{{ property.type }}> &newValue)
{
    if (newValue == d->{{ property.name }}) {
        return;
    }

    d->{{ property.name }} = newValue;
}

{% endif %}
{% endfor %}
bool {{ type_name }}::hasAnyValue() const
{
{% for property in properties %}
{% if property.is_group %}
    if (d->{{ property.name }} && d->{{ property.name }}->hasAnyValue()) {
        return true;
    }
{% else %}
    if (d->{{ property.name }}.has_value()) {
        return true;
    }
{% endif %}
{% endfor %}
    return false;
}

bool {{ type_name }}::isEmpty() const
{
    if (!hasAnyValue()) {
        return true;
    }

{% for property in properties %}
{% if property.is_group %}
    if (d->{{ property.name }} && !d->{{ property.name }}->isEmpty()) {
        return false;
    }
{% else %}
    if (d->{{ property.name }}.has_value() && d->{{ property.name }}.value() != emptyValue<{{ property.type }}>()) {
        return false;
    }
{% endif %}
{% endfor %}

    return true;
}

QString {{ type_name }}::toString(int indentation, ToStringFlags flags) const
{
    if (!hasAnyValue()) {
        return u"(empty)"_s;
    }

    const bool multiline = flags & ToStringFlag::MultiLine;
    const bool types = flags & ToStringFlag::Types;

    QString result;
    QTextStream out(&result);

    constexpr auto indent = [](int amount, bool multiline, bool first) {
        if (multiline) {
            return QByteArray(amount, ' ');
        } else if (!first) {
            return QByteArray(", ");
        } else {
            return QByteArray(" ");
        }
    };

    const QByteArray maybeNewLine = multiline ? "\n" : "";
    const QByteArray empty = "(empty)";

    if (types) {
        out << "{{ type_name }}(" << maybeNewLine;
    } else if (indentation > 0) {
        out << maybeNewLine;
    }

{% for property in properties %}
    out << indent(indentation, multiline, {{ "true" if property == properties[0] else "false" }}) << "{{ property.name }}: ";
    if (d->{{ property.name }}) {
{% if property.is_group %}
        out << d->{{ property.name }}->toString(indentation + 2, flags);
{% elif property.type in ("QFont", "QUrl", "Union::Color") %}
        out << d->{{ property.name }}->toString() << maybeNewLine;
{% elif property.type in ("QImage") %}
        auto image = d->{{ property.name }}.value();
        out << "QImage(fmt=" << image.format() << ", width=" << image.width() << ", height=" << image.height() << ")" << maybeNewLine;
{% elif property.type in ("std::filesystem::path") %}
        out << d->{{ property.name }}->c_str() << maybeNewLine;
{% else %}
        out << d->{{ property.name }}.value() << maybeNewLine;
{% endif %}
    } else {
        out << empty << maybeNewLine;
    }
{% endfor %}

    if (types) {
        out << indent(indentation - 2, multiline, true) << ")";
    }
    out << maybeNewLine;

    out.flush();

    return result;
}

void {{ type_name }}::resolveProperties(const {{ type_name }} *source, {{ type_name }} *destination)
{
    if (!source || !destination) {
        return;
    }

{% for property in properties %}
{% if property.is_group %}
    if (source->d->{{ property.name }}) {
        if (!destination->d->{{ property.name }}) {
            destination->d->{{ property.name }} = std::make_unique<{{ property.type }}>();
        }
        {{ property.type }}::resolveProperties(source->d->{{ property.name }}.get(), destination->d->{{ property.name }}.get());
    }
{% else %}
    if (!destination->d->{{ property.name }}.has_value()) {
        destination->d->{{ property.name }} = source->d->{{ property.name }};
    }
{% endif %}
{% endfor %}
}

std::unique_ptr<{{ type_name }}> {{ type_name }}::empty()
{
    auto result = std::make_unique<{{ type_name }}>();
{% for property in properties %}
{% if property.is_group %}
    result->d->{{ property.name }} = {{ property.type }}::empty();
{% else %}
    result->d->{{ property.name }} = emptyValue<{{ property.type }}>();
{% endif %}
{% endfor %}
    return result;
}
{% if extra_code and "global" in extra_code %}

{{ extra_code["global"] | render }}
{% endif %}

bool Union::Properties::operator==(const {{ type_name }} &left, const {{ type_name}} &right)
{
{% for property in properties %}
{% if property.is_group %}
    if (left.{{ property.name }}() && right.{{ property.name }}()) {
        if (*(left.{{ property.name }}()) != *(right.{{ property.name }}())) {
            return false;
        }
    } else if (left.{{ property.name }}() != right.{{ property.name }}()) {
        return false;
    }
{% else %}
    if (left.{{ property.name }}() != right.{{ property.name }}()) {
        return false;
    }
{% endif %}
{% endfor %}
    return true;
}

QDebug operator<<(QDebug debug, Union::Properties::{{ type_name }} *type)
{
    QDebugStateSaver saver(debug);
    debug.nospace() << qPrintable(type->toString(0, ToStringFlag::Types));
    return debug;
}

QDataStream &operator<<(QDataStream &stream, const Union::Properties::{{ type_name }} *type)
{
{% for property in properties %}
{% if property.is_group %}
    {
        auto data = type->{{ property.name }}();
        stream << bool(data);
        if (data) {
            stream << data;
        }
    }
{% else %}
    stream << type->{{ property.name }}();
{% endif %}
{% endfor %}
    return stream;
}

QDataStream &operator>>(QDataStream &stream, std::unique_ptr<Union::Properties::{{ type_name }}> &type)
{
{% for property in properties %}
{% if property.is_group %}
    {
        bool hasData;
        stream >> hasData;

        if (hasData) {
            auto data = std::make_unique<{{ property.type }}>();
            stream >> data;
            type->set{{ property.name | ucfirst }}(std::move(data));
        }
    }
{% else %}
    {
        std::optional<{{ property.type }}> data;
        stream >> data;
        type->set{{ property.name | ucfirst }}(data);
    }
{% endif %}
{% endfor %}

    return stream;
}
