/*
    SPDX-FileCopyrightText: 2022 Vlad Zahorodnii <vlad.zahorodnii@kde.org>

    SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "scene/itemrenderer_qpainter.h"
#include "core/rendertarget.h"
#include "core/renderviewport.h"
#include "effect/effect.h"
#include "scene/decorationitem.h"
#include "scene/imageitem.h"
#include "scene/ninepatch.h"
#include "scene/qpainter/atlas.h"
#include "scene/qpainter/texture.h"
#include "scene/surfaceitem.h"
#include "scene/workspacescene.h"
#include "window.h"

#include <QPainter>

namespace KWin
{

ItemRendererQPainter::ItemRendererQPainter()
    : m_painter(std::make_unique<QPainter>())
{
}

ItemRendererQPainter::~ItemRendererQPainter()
{
}

std::unique_ptr<Texture> ItemRendererQPainter::createTexture(GraphicsBuffer *buffer, const std::shared_ptr<SyncReleasePoint> &releasePoint)
{
    return BufferTextureQPainter::create(buffer);
}

std::unique_ptr<Texture> ItemRendererQPainter::createTexture(const QImage &image)
{
    return ImageTextureQPainter::create(image);
}

std::unique_ptr<NinePatch> ItemRendererQPainter::createNinePatch(const QImage &image)
{
    // TODO: Nine patch is noop because only drop-shadows use it. Look into cleaning this up.
    return nullptr;
}

std::unique_ptr<NinePatch> ItemRendererQPainter::createNinePatch(const QImage &topLeftPatch,
                                                                 const QImage &topPatch,
                                                                 const QImage &topRightPatch,
                                                                 const QImage &rightPatch,
                                                                 const QImage &bottomRightPatch,
                                                                 const QImage &bottomPatch,
                                                                 const QImage &bottomLeftPatch,
                                                                 const QImage &leftPatch)
{
    // TODO: Nine patch is noop because only drop-shadows use it. Look into cleaning this up.
    return nullptr;
}

std::unique_ptr<Atlas> ItemRendererQPainter::createAtlas(const QList<QImage> &sprites)
{
    return AtlasQPainter::create(sprites);
}

QPainter *ItemRendererQPainter::painter() const
{
    return m_painter.get();
}

void ItemRendererQPainter::beginFrame(const RenderTarget &renderTarget, const RenderViewport &viewport)
{
    QImage *buffer = renderTarget.image();
    m_painter->begin(buffer);
    m_painter->setWindow(viewport.renderRect().toRect());
}

void ItemRendererQPainter::endFrame()
{
    m_painter->end();
}

void ItemRendererQPainter::renderBackground(const RenderTarget &renderTarget, const RenderViewport &viewport, const Region &deviceRegion)
{
    m_painter->setCompositionMode(QPainter::CompositionMode_Source);
    const Region clipped = deviceRegion & renderTarget.transformedRect();
    for (const Rect &rect : clipped.rects()) {
        m_painter->fillRect(viewport.mapFromDeviceCoordinates(rect), Qt::transparent);
    }
    m_painter->setCompositionMode(QPainter::CompositionMode_SourceOver);
}

void ItemRendererQPainter::renderItem(const RenderTarget &renderTarget, const RenderViewport &viewport, Item *item, int mask, const Region &deviceRegion, const WindowPaintData &data, const std::function<bool(Item *)> &filter, const std::function<bool(Item *)> &holeFilter)
{
    Region effectiveRegion = deviceRegion;

    if (!(mask & (Scene::PAINT_WINDOW_TRANSFORMED | Scene::PAINT_SCREEN_TRANSFORMED))) {
        const Rect boundingRect = viewport.mapToRenderTarget(item->mapToScene(item->boundingRect())).roundedOut();
        effectiveRegion &= boundingRect;
    }

    if (effectiveRegion.isEmpty()) {
        return;
    }

    m_painter->save();
    m_painter->setClipRegion(QRegion(viewport.mapFromDeviceCoordinatesAligned(effectiveRegion)));
    m_painter->setClipping(true);
    m_painter->setOpacity(data.opacity());

    if (mask & Scene::PAINT_WINDOW_TRANSFORMED) {
        m_painter->translate(data.xTranslation(), data.yTranslation());
        m_painter->scale(data.xScale(), data.yScale());
    }

    renderItem(m_painter.get(), item, filter);

    m_painter->restore();
}

void ItemRendererQPainter::renderItem(QPainter *painter, Item *item, const std::function<bool(Item *)> &filter) const
{
    if (filter && filter(item)) {
        return;
    }
    const QList<Item *> sortedChildItems = item->sortedChildItems();

    painter->save();
    painter->translate(item->position());
    painter->setOpacity(painter->opacity() * item->opacity());

    for (Item *childItem : sortedChildItems) {
        if (childItem->z() >= 0) {
            break;
        }
        if (childItem->explicitVisible()) {
            renderItem(painter, childItem, filter);
        }
    }

    item->preprocess();
    if (auto surfaceItem = qobject_cast<SurfaceItem *>(item)) {
        renderSurfaceItem(painter, surfaceItem);
    } else if (auto decorationItem = qobject_cast<DecorationItem *>(item)) {
        renderDecorationItem(painter, decorationItem);
    } else if (auto imageItem = qobject_cast<ImageItem *>(item)) {
        renderImageItem(painter, imageItem);
    }

    for (Item *childItem : sortedChildItems) {
        if (childItem->z() < 0) {
            continue;
        }
        if (childItem->explicitVisible()) {
            renderItem(painter, childItem, filter);
        }
    }

    painter->restore();
}

void ItemRendererQPainter::renderSurfaceItem(QPainter *painter, SurfaceItem *surfaceItem) const
{
    const auto surfaceTexture = static_cast<TextureQPainter *>(surfaceItem->texture());
    if (!surfaceTexture || surfaceTexture->nativeImage().isNull()) {
        return;
    }

    const OutputTransform surfaceToBufferTransform = surfaceItem->bufferTransform();
    const QSizeF transformedSize = surfaceToBufferTransform.map(surfaceItem->destinationSize());

    painter->save();
    switch (surfaceToBufferTransform.kind()) {
    case OutputTransform::Normal:
        break;
    case OutputTransform::Rotate90:
        painter->translate(transformedSize.height(), 0);
        painter->rotate(90);
        break;
    case OutputTransform::Rotate180:
        painter->translate(transformedSize.width(), transformedSize.height());
        painter->rotate(180);
        break;
    case OutputTransform::Rotate270:
        painter->translate(0, transformedSize.width());
        painter->rotate(270);
        break;
    case OutputTransform::FlipX:
        painter->translate(transformedSize.width(), 0);
        painter->scale(-1, 1);
        break;
    case OutputTransform::FlipX90:
        painter->scale(-1, 1);
        painter->rotate(90);
        break;
    case OutputTransform::FlipX180:
        painter->translate(0, transformedSize.height());
        painter->scale(-1, 1);
        painter->rotate(180);
        break;
    case OutputTransform::FlipX270:
        painter->translate(transformedSize.height(), transformedSize.width());
        painter->scale(-1, 1);
        painter->rotate(270);
        break;
    }

    const RectF sourceBox = surfaceItem->bufferSourceBox();
    const qreal xSourceBoxScale = sourceBox.width() / transformedSize.width();
    const qreal ySourceBoxScale = sourceBox.height() / transformedSize.height();

    const RegionF shape = surfaceItem->shape();
    for (const RectF &rect : shape.rects()) {
        const QRectF target = surfaceToBufferTransform.map(rect, surfaceItem->size());
        const QRectF source(sourceBox.x() + target.x() * xSourceBoxScale,
                            sourceBox.y() + target.y() * ySourceBoxScale,
                            target.width() * xSourceBoxScale,
                            target.height() * ySourceBoxScale);

        painter->drawImage(target, surfaceTexture->nativeImage(), source);
    }

    painter->restore();
}

void ItemRendererQPainter::renderDecorationItem(QPainter *painter, DecorationItem *decorationItem) const
{
    const auto atlas = static_cast<const AtlasQPainter *>(decorationItem->atlas());
    RectF dtr, dlr, drr, dbr;
    decorationItem->window()->layoutDecorationRects(dlr, dtr, drr, dbr);

    painter->drawImage(dtr, atlas->image(uint(DecorationRenderer::DecorationPart::Top)));
    painter->drawImage(dlr, atlas->image(uint(DecorationRenderer::DecorationPart::Left)));
    painter->drawImage(drr, atlas->image(uint(DecorationRenderer::DecorationPart::Right)));
    painter->drawImage(dbr, atlas->image(uint(DecorationRenderer::DecorationPart::Bottom)));
}

void ItemRendererQPainter::renderImageItem(QPainter *painter, ImageItem *imageItem) const
{
    painter->drawImage(imageItem->rect(), imageItem->image());
}

} // namespace KWin
