// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#include "imagetestingrenderer.h"
#include "imagetestingitem.h"
#include <QColor>
#include <QRandomGenerator>
#include <QTimer>
#include "qclineargradient.h"

ImageTestingRenderer::ImageTestingRenderer()
{
}

void ImageTestingRenderer::synchronize(QQuickCPainterItem *item)
{
    auto p = painter();
    ImageTestingItem *realItem = static_cast<ImageTestingItem*>(item);
    auto actions = realItem->m_actions;
    bool memDataUpdated = false;
    if (actions.testFlag(ImageTestingItem::Action::Generate)) {
        generateImage();
        memDataUpdated = true;
    }
    if (actions.testFlag(ImageTestingItem::Action::RemoveAll)) {
        int clearedImages = m_images.size();
        if (clearedImages > 0) {
            for (int i = 0; i < clearedImages; i++) {
                auto image = m_images.takeFirst().image;
                p->removeImage(image);
            }
            memDataUpdated = true;
        }
    }
    if (actions.testFlag(ImageTestingItem::Action::RemoveHidden)) {
        auto it = m_images.begin();
        while (it != m_images.end()) {
            if (!it->visible) {
                p->removeImage(it->image);
                it = m_images.erase(it);
            } else {
                it++;
            }
        }
        memDataUpdated = true;
    }
    if (actions.testFlag(ImageTestingItem::Action::ShowHidden)) {
        for (int i = 0; i < m_images.size(); i++)
            m_images[i].visible = true;
    }
    if (actions.testFlag(ImageTestingItem::Action::RemoveNewest)) {
        if (!m_images.isEmpty()) {
            auto image = m_images.takeLast().image;
            p->removeImage(image);
            memDataUpdated = true;
        }
    }
    if (actions.testFlag(ImageTestingItem::Action::HideNewest)) {
        for (int i = m_images.size()-1; i >= 0; i--) {
            if (m_images.at(i).visible) {
                m_images[i].visible = false;
                break;
            }
        }
    }
    if (actions.testFlag(ImageTestingItem::Action::CleanupResources)) {
        p->cleanupResources();
        memDataUpdated = true;
    }
    if (actions.testFlag(ImageTestingItem::Action::GenerateCanvas)) {
        m_newCanvasPending += 1;
    }
    if (actions.testFlag(ImageTestingItem::Action::ReregisterCanvases)) {
        m_reregisterPending = true;
    }
    if (actions.testFlag(ImageTestingItem::Action::UnregisterNewestCanvas)) {
        if (!m_canvases.isEmpty()) {
            p->removeImage(m_canvases.last().image);
            m_canvases.last().image = {}; // now QCImage::isNull() == true
        }
    }
    if (actions.testFlag(ImageTestingItem::Action::RemoveNewestCanvas)) {
        if (!m_canvases.isEmpty()) {
            auto canvas = m_canvases.takeLast().canvas;
            p->destroyCanvas(canvas);
        }
    }

    m_showGradient = realItem->m_showGradient;
    m_animateGradient = realItem->m_animateGradient;

    // Update data values for the item
    realItem->m_dataAmount = p->activeImageMemoryUsage();
    realItem->dataAmountChanged();
    realItem->m_imageAmount = p->activeImageCount();
    realItem->imageAmountChanged();
    if (memDataUpdated) {
        // This is needed for cache amounts to update from the item side
        QTimer::singleShot(0, realItem, [realItem]{realItem->update();});
    }
    realItem->m_actions = {};
}

void ImageTestingRenderer::prePaint(QCPainter *painter)
{
    if (m_reregisterPending) {
        m_reregisterPending = false;
        for (auto &canvas : m_canvases) {
            if (canvas.image.isNull())
                canvas.image = painter->addImage(canvas.canvas);
        }
    }

    while (m_newCanvasPending > 0) {
        const int canvasWidth = 1024;
        const int canvasHeight = 1024;
        QCOffscreenCanvas canvas = painter->createCanvas({ canvasWidth, canvasHeight });
        if (canvas.isNull())
            qFatal("createCanvas() failed");

        CanvasData canvasData;
        canvasData.canvas = canvas;
        canvasData.image = painter->addImage(canvas);
        m_canvases << canvasData;

        auto *rand = QRandomGenerator::global();
        beginCanvasPainting(canvas);
        painter->setFillStyle(qRgba(rand->generate() % 255, rand->generate() % 255, rand->generate() % 255, 255));
        painter->fillRect({ 0, 0, canvasWidth, canvasHeight });
        QFont font;
        font.setPixelSize(40);
        painter->setFont(font);
        painter->setFillStyle(QColorConstants::Black);
        painter->fillText(QString::asprintf("This is an offscreen canvas of pixel size %dx%d", canvasWidth, canvasHeight), 50, 500);
        painter->fillText(QString::asprintf("using ca. %d KB", canvasData.image.size() / 1024), 50, 600);
        endCanvasPainting();

        m_newCanvasPending -= 1;
    }
}

void ImageTestingRenderer::paint(QCPainter *painter)
{
    const float w = width();
    const float h = height();
    // Render all visible images
    int imageAndCanvasCount = m_images.size() + m_canvases.size();
    const float imageW = w * 0.5;
    const float imageH = h * 0.5;
    int index = 0;
    painter->setTextAlign(QCPainter::TextAlign::Center);
    painter->setTextBaseline(QCPainter::TextBaseline::Top);
    QFont font;
    float fontSize = imageH * 0.08;
    font.setPixelSize(fontSize);
    painter->setFont(font);

    auto calculateRect = [=](int index) {
        const float movPos = (float(index + 0.5) / imageAndCanvasCount - 0.5);
        const float movX = imageW * movPos;
        const float movY = imageH * movPos;
        QRectF pos(w * 0.5 - imageW * 0.5 + movX,
                    h * 0.5 - imageH * 0.5 + movY,
                    imageW, imageH);
        return pos;
    };

    for (auto &image : std::as_const(m_images)) {
        if (image.visible) {
            const QRectF pos = calculateRect(index);
            painter->drawImage(image.image, pos);
            painter->fillText(QStringLiteral("IMAGE INDEX: %1").arg(QString::number(index)), pos);
        }
        index++;
    }
    for (auto &canvas : std::as_const(m_canvases)) {
        const QRectF pos = calculateRect(index);
        painter->setStrokeStyle(QColorConstants::Red);
        painter->setLineWidth(4);
        painter->strokeRect(pos);
        painter->drawImage(canvas.image, pos);
        painter->fillText(QStringLiteral("CANVAS INDEX: %1").arg(QString::number(index - m_images.size())), pos);
        index++;
    }

    static int anim = 0;
    if (m_showGradient) {
        float gradW = w * 0.8;
        float gradH = h * 0.05;
        float gradX = (w - gradW) * 0.5;
        float gradY = 0;
        QRectF rect(gradX, gradY, gradW, gradH);
        QCLinearGradient g(rect.x(), rect.y(), rect.x()+rect.width(), rect.y());
        g.setColorAt(0.0f, QColorConstants::Black);
        if (m_animateGradient)
            anim++;
        float gradPos = 0.5f + 0.5f * qSin(0.005 * anim);
        g.setColorAt(gradPos, QColorConstants::Red);
        g.setColorAt(1.0f, QColorConstants::Black);
        painter->setFillStyle(g);
        painter->fillRect(rect);
    }
}

void ImageTestingRenderer::generateImage()
{
    QImage image(1024, 1024, QImage::Format_RGBA8888);
    image.fill(QColorConstants::Black);
    auto *rand =  QRandomGenerator::global();
    int g = rand->generate() % 255;
    int b = rand->generate() % 255;
    int r = 0;
    for (int y = 0; y < image.height(); y++) {
        if (y % 10 == 0)
            r = rand->generate() % 255;
        for (int x = 0; x < image.width(); x++)
            image.setPixel(x, y, qRgba(r, g, b, 100));
    }
    ImageData imageData;
    imageData.image = painter()->addImage(image);
    m_images << imageData;
}
