// SPDX-FileCopyrightText: 2025 Prayag Jain <prayagjain2@gmail.com>
//
// SPDX-License-Identifier: GPL-3.0-or-later

#include "renderingcontext.hpp"

#include <QPoint>
#include <QScreen>

#include "applicationcontext.hpp"
#include "canvas/canvas.hpp"
#include "common/constants.hpp"
#include "common/renderitems.hpp"
#include "coordinatetransformer.hpp"
#include "data-structures/cachegrid.hpp"
#include "item/itemcache/itemcache.hpp"
#include "spatialcontext.hpp"

#include "drawy_debug.h"

RenderingContext::RenderingContext(ApplicationContext *context)
    : QObject{context}
    , m_applicationContext(context)
{
    m_canvas = new Canvas(m_applicationContext->parentWidget());
    m_cacheGrid = std::make_unique<CacheGrid>(100, QSize{500, 500});
    m_itemCache = std::make_unique<ItemCache>(m_applicationContext);
    canvasResized();

    connect(m_canvas, &Canvas::resizeEventCalled, this, &RenderingContext::canvasResized);
    connect(&m_frameTimer, &QTimer::timeout, m_canvas, [&]() {
        if (m_needsReRender) {
            Common::renderCanvas(m_applicationContext);
            m_needsReRender = false;

            Q_EMIT canvasRendered();
        }

        if (m_needsUpdate) {
            if (m_updateRegion.width() == 0 || m_updateRegion.height() == 0) {
                m_canvas->update();
            } else {
                m_canvas->update(m_updateRegion);
            }

            m_updateRegion.setWidth(0);
            m_updateRegion.setHeight(0);
            m_needsUpdate = false;

            Q_EMIT canvasUpdated();
        }
    });

    m_frameTimer.start(1000 / fps());
}

RenderingContext::~RenderingContext()
{
    qCDebug(DRAWY_LOG) << "Object deleted: RenderingContext";
}

Canvas *RenderingContext::canvas() const
{
    return m_canvas;
}

void RenderingContext::setCanvasBackground(const QColor &color)
{
    m_canvas->setCanvasBg(color);
    cacheGrid().markAllDirty();
    markForRender();
    markForUpdate();
}

CacheGrid &RenderingContext::cacheGrid() const
{
    return *m_cacheGrid;
}

ItemCache &RenderingContext::itemCache() const
{
    return *m_itemCache;
}

qreal RenderingContext::zoomFactor() const
{
    return m_zoomFactor;
}

void RenderingContext::zoomIn()
{
    updateZoomFactor(qMin(Common::zoomInLimit, m_zoomFactor * Common::zoomMultiplier));
}

bool RenderingContext::canZoomIn() const
{
    return m_zoomFactor < Common::zoomInLimit;
}

void RenderingContext::zoomOut()
{
    updateZoomFactor(qMax(Common::zoomOutLimit, m_zoomFactor / Common::zoomMultiplier));
}

bool RenderingContext::canZoomOut() const
{
    return m_zoomFactor > Common::zoomOutLimit;
}

void RenderingContext::updateZoomFactor(qreal newValue, QPoint center)
{
    const qreal oldZoomFactor = m_zoomFactor;
    m_zoomFactor = qBound(Common::zoomOutLimit, newValue, Common::zoomInLimit);

    QPointF offsetPos{m_applicationContext->spatialContext()->offsetPos()};

    if (center == QPoint{-1, -1}) {
        const QSize viewport{canvas()->dimensions() / oldZoomFactor};
        const int width{viewport.width()};
        const int height{viewport.height()};

        center.setX(offsetPos.toPoint().x() + width / 2);
        center.setY(offsetPos.toPoint().y() + height / 2);
    }

    offsetPos.setX(center.x() - (center.x() - offsetPos.x()) * oldZoomFactor / m_zoomFactor);
    offsetPos.setY(center.y() - (center.y() - offsetPos.y()) * oldZoomFactor / m_zoomFactor);

    cacheGrid().markAllDirty();
    itemCache().clear();
    m_applicationContext->spatialContext()->setOffsetPos(offsetPos);
    m_applicationContext->renderingContext()->markForRender();
    m_applicationContext->renderingContext()->markForUpdate();

    Q_EMIT zoomFactorChanged(m_zoomFactor);
}

void RenderingContext::setZoomFactor(qreal newValue)
{
    m_zoomFactor = qBound(Common::zoomOutLimit, newValue, Common::zoomInLimit);
    Q_EMIT zoomFactorChanged(m_zoomFactor);
}

int RenderingContext::fps() const
{
    const QScreen *screen{m_canvas->screen()};
    if (screen) {
        return static_cast<int>(screen->refreshRate());
    }

    return 60;
}

void RenderingContext::canvasResized()
{
    const QSize &cellSize{cacheGrid().cellSize()};
    const int width{m_canvas->dimensions().width()};
    const int height{m_canvas->dimensions().height()};
    const int cellW{cellSize.width()};
    const int cellH{cellSize.height()};
    const int rows{static_cast<int>(std::ceil(height / static_cast<double>(cellH)) + 1)};
    const int cols{static_cast<int>(std::ceil(width / static_cast<double>(cellW)) + 1)};

    cacheGrid().clear();
    cacheGrid().setSize(rows * cols * Common::viewportCacheMultiplier);

    itemCache().clear();

    markForRender();
    markForUpdate();
}

void RenderingContext::markForRender()
{
    m_needsReRender = true;
}

void RenderingContext::markForUpdate()
{
    m_needsUpdate = true;
}

void RenderingContext::markForUpdate(const QRect &region)
{
    m_needsUpdate = true;
    m_updateRegion = region;
}

void RenderingContext::reset()
{
    setZoomFactor(1.0);
    cacheGrid().markAllDirty();
    itemCache().clear();
}

#include "moc_renderingcontext.cpp"
