// 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`.

#include "LayoutProperty.h"

#include <QRegularExpression>

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

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

class Union::Properties::LayoutPropertyPrivate
{
public:
    std::unique_ptr<AlignmentProperty> alignment;
    std::optional<qreal> width;
    std::optional<qreal> height;
    std::optional<qreal> spacing;
    std::unique_ptr<SizeProperty> padding;
    std::unique_ptr<SizeProperty> inset;
    std::unique_ptr<SizeProperty> margins;
};

LayoutProperty::LayoutProperty()
    : d(std::make_unique<LayoutPropertyPrivate>())
{
}

LayoutProperty::LayoutProperty(const LayoutProperty &other)
    : d(std::make_unique<LayoutPropertyPrivate>())
{
    d->alignment = std::make_unique<AlignmentProperty>();
    *(d->alignment) = *(other.d->alignment);
    d->width = other.d->width;
    d->height = other.d->height;
    d->spacing = other.d->spacing;
    d->padding = std::make_unique<SizeProperty>();
    *(d->padding) = *(other.d->padding);
    d->inset = std::make_unique<SizeProperty>();
    *(d->inset) = *(other.d->inset);
    d->margins = std::make_unique<SizeProperty>();
    *(d->margins) = *(other.d->margins);
}

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

LayoutProperty::~LayoutProperty() = default;

LayoutProperty &LayoutProperty::operator=(const LayoutProperty &other)
{
    if (this != &other) {
        *(d->alignment) = *(other.d->alignment);
        d->width = other.d->width;
        d->height = other.d->height;
        d->spacing = other.d->spacing;
        *(d->padding) = *(other.d->padding);
        *(d->inset) = *(other.d->inset);
        *(d->margins) = *(other.d->margins);
    }
    return *this;
}

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

AlignmentProperty *LayoutProperty::alignment() const
{
    return d->alignment.get();
}

void LayoutProperty::setAlignment(std::unique_ptr<AlignmentProperty> &&newValue)
{
    d->alignment = std::move(newValue);
}

std::optional<qreal> LayoutProperty::width() const
{
    return d->width;
}

void LayoutProperty::setWidth(const std::optional<qreal> &newValue)
{
    if (newValue == d->width) {
        return;
    }

    d->width = newValue;
}

std::optional<qreal> LayoutProperty::height() const
{
    return d->height;
}

void LayoutProperty::setHeight(const std::optional<qreal> &newValue)
{
    if (newValue == d->height) {
        return;
    }

    d->height = newValue;
}

std::optional<qreal> LayoutProperty::spacing() const
{
    return d->spacing;
}

void LayoutProperty::setSpacing(const std::optional<qreal> &newValue)
{
    if (newValue == d->spacing) {
        return;
    }

    d->spacing = newValue;
}

SizeProperty *LayoutProperty::padding() const
{
    return d->padding.get();
}

void LayoutProperty::setPadding(std::unique_ptr<SizeProperty> &&newValue)
{
    d->padding = std::move(newValue);
}

SizeProperty *LayoutProperty::inset() const
{
    return d->inset.get();
}

void LayoutProperty::setInset(std::unique_ptr<SizeProperty> &&newValue)
{
    d->inset = std::move(newValue);
}

SizeProperty *LayoutProperty::margins() const
{
    return d->margins.get();
}

void LayoutProperty::setMargins(std::unique_ptr<SizeProperty> &&newValue)
{
    d->margins = std::move(newValue);
}

bool LayoutProperty::hasAnyValue() const
{
    if (d->alignment && d->alignment->hasAnyValue()) {
        return true;
    }
    if (d->width.has_value()) {
        return true;
    }
    if (d->height.has_value()) {
        return true;
    }
    if (d->spacing.has_value()) {
        return true;
    }
    if (d->padding && d->padding->hasAnyValue()) {
        return true;
    }
    if (d->inset && d->inset->hasAnyValue()) {
        return true;
    }
    if (d->margins && d->margins->hasAnyValue()) {
        return true;
    }
    return false;
}

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

    if (d->alignment && !d->alignment->isEmpty()) {
        return false;
    }
    if (d->width.has_value() && d->width.value() != emptyValue<qreal>()) {
        return false;
    }
    if (d->height.has_value() && d->height.value() != emptyValue<qreal>()) {
        return false;
    }
    if (d->spacing.has_value() && d->spacing.value() != emptyValue<qreal>()) {
        return false;
    }
    if (d->padding && !d->padding->isEmpty()) {
        return false;
    }
    if (d->inset && !d->inset->isEmpty()) {
        return false;
    }
    if (d->margins && !d->margins->isEmpty()) {
        return false;
    }

    return true;
}

QString LayoutProperty::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 << "LayoutProperty(" << maybeNewLine;
    } else if (indentation > 0) {
        out << maybeNewLine;
    }

    out << indent(indentation, multiline, true) << "alignment: ";
    if (d->alignment) {
        out << d->alignment->toString(indentation + 2, flags);
    } else {
        out << empty << maybeNewLine;
    }
    out << indent(indentation, multiline, false) << "width: ";
    if (d->width) {
        out << d->width.value() << maybeNewLine;
    } else {
        out << empty << maybeNewLine;
    }
    out << indent(indentation, multiline, false) << "height: ";
    if (d->height) {
        out << d->height.value() << maybeNewLine;
    } else {
        out << empty << maybeNewLine;
    }
    out << indent(indentation, multiline, false) << "spacing: ";
    if (d->spacing) {
        out << d->spacing.value() << maybeNewLine;
    } else {
        out << empty << maybeNewLine;
    }
    out << indent(indentation, multiline, false) << "padding: ";
    if (d->padding) {
        out << d->padding->toString(indentation + 2, flags);
    } else {
        out << empty << maybeNewLine;
    }
    out << indent(indentation, multiline, false) << "inset: ";
    if (d->inset) {
        out << d->inset->toString(indentation + 2, flags);
    } else {
        out << empty << maybeNewLine;
    }
    out << indent(indentation, multiline, false) << "margins: ";
    if (d->margins) {
        out << d->margins->toString(indentation + 2, flags);
    } else {
        out << empty << maybeNewLine;
    }

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

    out.flush();

    return result;
}

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

    if (source->d->alignment) {
        if (!destination->d->alignment) {
            destination->d->alignment = std::make_unique<AlignmentProperty>();
        }
        AlignmentProperty::resolveProperties(source->d->alignment.get(), destination->d->alignment.get());
    }
    if (!destination->d->width.has_value()) {
        destination->d->width = source->d->width;
    }
    if (!destination->d->height.has_value()) {
        destination->d->height = source->d->height;
    }
    if (!destination->d->spacing.has_value()) {
        destination->d->spacing = source->d->spacing;
    }
    if (source->d->padding) {
        if (!destination->d->padding) {
            destination->d->padding = std::make_unique<SizeProperty>();
        }
        SizeProperty::resolveProperties(source->d->padding.get(), destination->d->padding.get());
    }
    if (source->d->inset) {
        if (!destination->d->inset) {
            destination->d->inset = std::make_unique<SizeProperty>();
        }
        SizeProperty::resolveProperties(source->d->inset.get(), destination->d->inset.get());
    }
    if (source->d->margins) {
        if (!destination->d->margins) {
            destination->d->margins = std::make_unique<SizeProperty>();
        }
        SizeProperty::resolveProperties(source->d->margins.get(), destination->d->margins.get());
    }
}

std::unique_ptr<LayoutProperty> LayoutProperty::empty()
{
    auto result = std::make_unique<LayoutProperty>();
    result->d->alignment = AlignmentProperty::empty();
    result->d->width = emptyValue<qreal>();
    result->d->height = emptyValue<qreal>();
    result->d->spacing = emptyValue<qreal>();
    result->d->padding = SizeProperty::empty();
    result->d->inset = SizeProperty::empty();
    result->d->margins = SizeProperty::empty();
    return result;
}

bool Union::Properties::operator==(const LayoutProperty &left, const LayoutProperty &right)
{
    if (left.alignment() && right.alignment()) {
        if (*(left.alignment()) != *(right.alignment())) {
            return false;
        }
    } else if (left.alignment() != right.alignment()) {
        return false;
    }
    if (left.width() != right.width()) {
        return false;
    }
    if (left.height() != right.height()) {
        return false;
    }
    if (left.spacing() != right.spacing()) {
        return false;
    }
    if (left.padding() && right.padding()) {
        if (*(left.padding()) != *(right.padding())) {
            return false;
        }
    } else if (left.padding() != right.padding()) {
        return false;
    }
    if (left.inset() && right.inset()) {
        if (*(left.inset()) != *(right.inset())) {
            return false;
        }
    } else if (left.inset() != right.inset()) {
        return false;
    }
    if (left.margins() && right.margins()) {
        if (*(left.margins()) != *(right.margins())) {
            return false;
        }
    } else if (left.margins() != right.margins()) {
        return false;
    }
    return true;
}

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

QDataStream &operator<<(QDataStream &stream, const Union::Properties::LayoutProperty *type)
{
    {
        auto data = type->alignment();
        stream << bool(data);
        if (data) {
            stream << data;
        }
    }
    stream << type->width();
    stream << type->height();
    stream << type->spacing();
    {
        auto data = type->padding();
        stream << bool(data);
        if (data) {
            stream << data;
        }
    }
    {
        auto data = type->inset();
        stream << bool(data);
        if (data) {
            stream << data;
        }
    }
    {
        auto data = type->margins();
        stream << bool(data);
        if (data) {
            stream << data;
        }
    }
    return stream;
}

QDataStream &operator>>(QDataStream &stream, std::unique_ptr<Union::Properties::LayoutProperty> &type)
{
    {
        bool hasData;
        stream >> hasData;

        if (hasData) {
            auto data = std::make_unique<AlignmentProperty>();
            stream >> data;
            type->setAlignment(std::move(data));
        }
    }
    {
        std::optional<qreal> data;
        stream >> data;
        type->setWidth(data);
    }
    {
        std::optional<qreal> data;
        stream >> data;
        type->setHeight(data);
    }
    {
        std::optional<qreal> data;
        stream >> data;
        type->setSpacing(data);
    }
    {
        bool hasData;
        stream >> hasData;

        if (hasData) {
            auto data = std::make_unique<SizeProperty>();
            stream >> data;
            type->setPadding(std::move(data));
        }
    }
    {
        bool hasData;
        stream >> hasData;

        if (hasData) {
            auto data = std::make_unique<SizeProperty>();
            stream >> data;
            type->setInset(std::move(data));
        }
    }
    {
        bool hasData;
        stream >> hasData;

        if (hasData) {
            auto data = std::make_unique<SizeProperty>();
            stream >> data;
            type->setMargins(std::move(data));
        }
    }

    return stream;
}