/*
 *  SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
 *  SPDX-FileCopyrightText: 2025 iaom <zhangpengfei@kylinos.cn>
 *
 *  SPDX-License-Identifier: LGPL-2.0-or-later
 */

#include "shadowed-rectangle.h"

#include <cmath>
#include <QQuickWindow>
#include <QSGRectangleNode>
#include <QSGRendererInterface>

#include "painted-rectangle-item.h"
#if QT_CONFIG(opengl) || QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#include "shadowed-rectangle-node.h"
#endif


BorderGroup::BorderGroup(QObject *parent)
    : QObject(parent)
{
}

qreal BorderGroup::width() const
{
    return m_width;
}

void BorderGroup::setWidth(qreal newWidth)
{
    if (newWidth == m_width) {
        return;
    }

    m_width = newWidth;
    Q_EMIT changed();
}

QColor BorderGroup::color() const
{
    return m_color;
}

void BorderGroup::setColor(const QColor &newColor)
{
    if (newColor == m_color) {
        return;
    }

    m_color = newColor;
    Q_EMIT changed();
}

ShadowGroup::ShadowGroup(QObject *parent)
    : QObject(parent)
{
}

qreal ShadowGroup::size() const
{
    return m_size;
}

void ShadowGroup::setSize(qreal newSize)
{
    if (newSize == m_size) {
        return;
    }

    m_size = newSize;
    Q_EMIT changed();
}

qreal ShadowGroup::xOffset() const
{
    return m_xOffset;
}

void ShadowGroup::setXOffset(qreal newXOffset)
{
    if (newXOffset == m_xOffset) {
        return;
    }

    m_xOffset = newXOffset;
    Q_EMIT changed();
}

qreal ShadowGroup::yOffset() const
{
    return m_yOffset;
}

void ShadowGroup::setYOffset(qreal newYOffset)
{
    if (newYOffset == m_yOffset) {
        return;
    }

    m_yOffset = newYOffset;
    Q_EMIT changed();
}

QColor ShadowGroup::color() const
{
    return m_color;
}

void ShadowGroup::setColor(const QColor &newColor)
{
    if (newColor == m_color) {
        return;
    }

    m_color = newColor;
    Q_EMIT changed();
}

CornersGroup::CornersGroup(QObject *parent)
    : QObject(parent)
{
}

qreal CornersGroup::topLeft() const
{
    return m_topLeft;
}

void CornersGroup::setTopLeft(qreal newTopLeft)
{
    if (newTopLeft == m_topLeft) {
        return;
    }

    m_topLeft = newTopLeft;
    Q_EMIT changed();
}

qreal CornersGroup::topRight() const
{
    return m_topRight;
}

void CornersGroup::setTopRight(qreal newTopRight)
{
    if (newTopRight == m_topRight) {
        return;
    }

    m_topRight = newTopRight;
    Q_EMIT changed();
}

qreal CornersGroup::bottomLeft() const
{
    return m_bottomLeft;
}

void CornersGroup::setBottomLeft(qreal newBottomLeft)
{
    if (newBottomLeft == m_bottomLeft) {
        return;
    }

    m_bottomLeft = newBottomLeft;
    Q_EMIT changed();
}

qreal CornersGroup::bottomRight() const
{
    return m_bottomRight;
}

void CornersGroup::setBottomRight(qreal newBottomRight)
{
    if (newBottomRight == m_bottomRight) {
        return;
    }

    m_bottomRight = newBottomRight;
    Q_EMIT changed();
}

QVector4D CornersGroup::toVector4D(float all) const
{
    return QVector4D{m_bottomRight < 0.0 ? all : m_bottomRight,
                     m_topRight < 0.0 ? all : m_topRight,
                     m_bottomLeft < 0.0 ? all : m_bottomLeft,
                     m_topLeft < 0.0 ? all : m_topLeft};
}

ShadowedRectangle::ShadowedRectangle(QQuickItem *parentItem)
    : QQuickItem(parentItem)
    , m_border(std::make_unique<BorderGroup>())
    , m_shadow(std::make_unique<ShadowGroup>())
    , m_corners(std::make_unique<CornersGroup>())
{
    setFlag(QQuickItem::ItemHasContents, true);

    connect(m_border.get(), &BorderGroup::changed, this, &ShadowedRectangle::update);
    connect(m_shadow.get(), &ShadowGroup::changed, this, &ShadowedRectangle::update);
    connect(m_corners.get(), &CornersGroup::changed, this, &ShadowedRectangle::update);
    //workaround
    connect(this, &QQuickItem::activeFocusOnTabChanged, this, &ShadowedRectangle::activeFocusOnTabChanged1);
}

ShadowedRectangle::~ShadowedRectangle()
{
}

BorderGroup *ShadowedRectangle::border() const
{
    return m_border.get();
}

ShadowGroup *ShadowedRectangle::shadow() const
{
    return m_shadow.get();
}

CornersGroup *ShadowedRectangle::corners() const
{
    return m_corners.get();
}

qreal ShadowedRectangle::radius() const
{
    return m_radius;
}

void ShadowedRectangle::setRadius(qreal newRadius)
{
    if (newRadius == m_radius) {
        return;
    }

    m_radius = newRadius;
    if (!isSoftwareRendering()) {
        update();
    }
    Q_EMIT radiusChanged();
}

QColor ShadowedRectangle::color() const
{
    return m_color;
}

void ShadowedRectangle::setColor(const QColor &newColor)
{
    if (newColor == m_color) {
        return;
    }

    m_color = newColor;
    if (!isSoftwareRendering()) {
        update();
    }
    Q_EMIT colorChanged();
}

bool ShadowedRectangle::pureColor() const
{
    return m_pureColor;
}

void ShadowedRectangle::setPureColor(bool pureColor)
{
    if (pureColor == m_pureColor) {
        return;
    }

    m_pureColor = pureColor;
    if (!isSoftwareRendering()) {
        update();
    }
    Q_EMIT pureColorChanged();
}

QColor ShadowedRectangle::startColor() const
{
    return m_startColor;
}

void ShadowedRectangle::setStartColor(const QColor& startColor)
{
    if (startColor == m_startColor) {
        return;
    }

    m_startColor = startColor;
    if (!isSoftwareRendering()) {
        update();
    }
    Q_EMIT startColorChanged();
}

QColor ShadowedRectangle::endColor() const
{
    return m_endColor;
}

void ShadowedRectangle::setEndColor(const QColor& endColor)
{
    if (endColor == m_endColor) {
        return;
    }

    m_endColor = endColor;
    if (!isSoftwareRendering()) {
        update();
    }
    Q_EMIT endColorChanged();
}

qreal ShadowedRectangle::angle() const
{
    return m_angle;
}

void ShadowedRectangle::setAngle(qreal newAngle)
{
    auto deg = newAngle / 180.0 * M_PI;
    if (m_angle == deg) {
        return;
    }

    m_angle = deg;
    if (!isSoftwareRendering()) {
        update();
    }
    Q_EMIT angleChanged();
}

ShadowedRectangle::RenderType ShadowedRectangle::renderType() const
{
    return m_renderType;
}

void ShadowedRectangle::setRenderType(RenderType renderType)
{
    if (renderType == m_renderType) {
        return;
    }
    m_renderType = renderType;
    update();
    Q_EMIT renderTypeChanged();
}

void ShadowedRectangle::componentComplete()
{
    QQuickItem::componentComplete();

    checkSoftwareItem();
}

bool ShadowedRectangle::isSoftwareRendering() const
{
    return (window() && window()->rendererInterface()->graphicsApi() == QSGRendererInterface::Software) || m_renderType == RenderType::Software;
}

PaintedRectangleItem *ShadowedRectangle::softwareItem() const
{
    return m_softwareItem;
}

void ShadowedRectangle::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
{
    if (change == QQuickItem::ItemSceneChange && value.window) {
        checkSoftwareItem();
        // TODO: only conditionally emit?
        Q_EMIT softwareRenderingChanged();
    }

    QQuickItem::itemChange(change, value);
}

QSGNode *ShadowedRectangle::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *data)
{
    Q_UNUSED(data);

#if QT_CONFIG(opengl) || QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
    if (boundingRect().isEmpty()) {
        delete node;
        return nullptr;
    }

    auto shadowNode = static_cast<ShadowedRectangleNode *>(node);

    if (!shadowNode) {
        shadowNode = new ShadowedRectangleNode{};

        if (m_renderType == RenderType::LowQuality) {
            shadowNode->setShaderType(ShadowedRectangleMaterial::ShaderType::LowPower);
        }
    }

    shadowNode->setBorderEnabled(m_border->isEnabled());
    shadowNode->setRect(boundingRect());
    shadowNode->setSize(m_shadow->size());
    shadowNode->setRadius(m_corners->toVector4D(m_radius));
    shadowNode->setOffset(QVector2D{static_cast<float>(m_shadow->xOffset()), static_cast<float>(m_shadow->yOffset())});
    shadowNode->setColor(m_color);
    shadowNode->setPureColor(m_pureColor);
    shadowNode->setStartColor(m_startColor);
    shadowNode->setEndColor(m_endColor);
    shadowNode->setAngle(m_angle);
    shadowNode->setShadowColor(m_shadow->color());
    shadowNode->setBorderWidth(m_border->width());
    shadowNode->setBorderColor(m_border->color());
    shadowNode->updateGeometry();
    return shadowNode;
#else
    Q_UNUSED(node)
    return nullptr;
#endif
}

void ShadowedRectangle::checkSoftwareItem()
{
    if (!m_softwareItem && isSoftwareRendering()) {
        m_softwareItem = new PaintedRectangleItem{this};
        // The software item is added as a "normal" child item, this means it
        // will be part of the normal item sort order. Since there is no way to
        // control the ordering of children, just make sure to have a very low Z
        // value for the child, to force it to be the lowest item.
        m_softwareItem->setZ(-99.0);

        auto updateItem = [this]() {
            auto borderWidth = m_border->width();
            auto rect = boundingRect();
            m_softwareItem->setSize(rect.size());
            m_softwareItem->setColor(m_color);
            m_softwareItem->setRadius(m_corners->toVector4D(m_radius));
            m_softwareItem->setPureColor(m_pureColor);
            m_softwareItem->setStartColor(m_startColor);
            m_softwareItem->setEndColor(m_endColor);
            m_softwareItem->setAngle(m_angle);
            m_softwareItem->setBorderWidth(borderWidth);
            m_softwareItem->setBorderColor(m_border->color());
        };

        updateItem();

        connect(this, &ShadowedRectangle::widthChanged, m_softwareItem, updateItem);
        connect(this, &ShadowedRectangle::heightChanged, m_softwareItem, updateItem);
        connect(this, &ShadowedRectangle::colorChanged, m_softwareItem, updateItem);
        connect(this, &ShadowedRectangle::radiusChanged, m_softwareItem, updateItem);
        connect(this, &ShadowedRectangle::pureColorChanged, m_softwareItem, updateItem);
        connect(this, &ShadowedRectangle::startColorChanged, m_softwareItem, updateItem);
        connect(this, &ShadowedRectangle::endColorChanged, m_softwareItem, updateItem);
        connect(this, &ShadowedRectangle::angleChanged, m_softwareItem, updateItem);
        connect(m_border.get(), &BorderGroup::changed, m_softwareItem, updateItem);
        connect(m_corners.get(), &CornersGroup::changed, m_softwareItem, updateItem);

        setFlag(QQuickItem::ItemHasContents, false);
    }
}