/*
 * SPDX-FileCopyrightText: 2006 Boudewijn Rempt <boud@valdyas.org>
 * SPDX-FileCopyrightText: 2009 Lukas Tvrdy <lukast.dev@gmail.com>
 *
 *  SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "kis_qpainter_canvas.h"


#include <QPaintEvent>
#include <QPoint>
#include <QRect>
#include <QPainter>
#include <QImage>
#include <QBrush>
#include <QColor>
#include <QTime>
#include <QApplication>

#include <kis_debug.h>
#include <kis_config.h>

#include <KoColorProfile.h>
#include "kis_coordinates_converter.h"
#include <KoZoomHandler.h>
#include <KoToolManager.h>
#include <KoToolProxy.h>

#include <kis_image.h>
#include <kis_layer.h>

#include "KisViewManager.h"
#include "kis_canvas2.h"
#include "kis_prescaled_projection.h"
#include "kis_canvas_resource_provider.h"
#include "KisDocument.h"
#include "kis_selection_manager.h"
#include "kis_selection.h"
#include "kis_canvas_updates_compressor.h"
#include "kis_config_notifier.h"
#include "kis_group_layer.h"

#include <KoCanvasController.h>
#include <KisRepaintDebugger.h>
#include <KisDisplayConfig.h>

class KisQPainterCanvas::Private
{
public:
    KisPrescaledProjectionSP prescaledProjection;
    QBrush checkBrush;
    bool scrollCheckers;
    KisRepaintDebugger repaintDbg;
};

KisQPainterCanvas::KisQPainterCanvas(KisCanvas2 *canvas, KisCoordinatesConverter *coordinatesConverter, QWidget * parent)
        : QWidget(parent)
        , KisCanvasWidgetBase(canvas, coordinatesConverter)
        , m_d(new Private())
{
    setAutoFillBackground(true);
    setAcceptDrops(true);
    setFocusPolicy(Qt::StrongFocus);
    setAttribute(Qt::WA_InputMethodEnabled, true);
    setAttribute(Qt::WA_StaticContents);
    setAttribute(Qt::WA_OpaquePaintEvent);
#ifdef Q_OS_MACOS
    setAttribute(Qt::WA_AcceptTouchEvents, false);
#else
    setAttribute(Qt::WA_AcceptTouchEvents, true);
#endif
    connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
    slotConfigChanged();
}

KisQPainterCanvas::~KisQPainterCanvas()
{
    delete m_d;
}

void KisQPainterCanvas::setPrescaledProjection(KisPrescaledProjectionSP prescaledProjection)
{
    m_d->prescaledProjection = prescaledProjection;
}

void KisQPainterCanvas::paintEvent(QPaintEvent * ev)
{
    KisImageWSP image = canvas()->image();
    if (image == 0) return;

    setAutoFillBackground(false);

    QPainter gc(this);
    gc.setClipRegion(ev->region());

    KisCoordinatesConverter *converter = coordinatesConverter();

    gc.save();

    gc.setCompositionMode(QPainter::CompositionMode_Source);
    gc.fillRect(QRect(QPoint(0, 0), size()), borderColor());

    QTransform checkersTransform;
    QPointF brushOrigin;
    QPolygonF polygon;

    converter->getQPainterCheckersInfo(&checkersTransform, &brushOrigin, &polygon, m_d->scrollCheckers);
    gc.setPen(Qt::NoPen);
    gc.setBrush(m_d->checkBrush);
    gc.setBrushOrigin(brushOrigin);
    gc.setTransform(checkersTransform);
    gc.drawPolygon(polygon);

    drawImage(gc, ev->rect());

    gc.restore();

    drawDecorations(gc, ev->rect());

    gc.end();
    m_d->repaintDbg.paint(this, ev);
}

void KisQPainterCanvas::drawImage(QPainter & gc, const QRect &updateWidgetRect) const
{
    KisCoordinatesConverter *converter = coordinatesConverter();

    QTransform imageTransform = converter->viewportToWidgetTransform();
    gc.setTransform(imageTransform);
    gc.setRenderHint(QPainter::SmoothPixmapTransform, true);

    QRectF viewportRect = converter->widgetToViewport(updateWidgetRect);

    gc.setCompositionMode(QPainter::CompositionMode_SourceOver);
    gc.drawImage(viewportRect, m_d->prescaledProjection->prescaledQImage(),
                 viewportRect);
}

QVariant KisQPainterCanvas::inputMethodQuery(Qt::InputMethodQuery query) const
{
    return processInputMethodQuery(query);
}

void KisQPainterCanvas::inputMethodEvent(QInputMethodEvent *event)
{
    processInputMethodEvent(event);
}

void KisQPainterCanvas::focusInEvent(QFocusEvent *event)
{
    processFocusInEvent(event);
}

void KisQPainterCanvas::focusOutEvent(QFocusEvent *event)
{
    processFocusOutEvent(event);
}

void KisQPainterCanvas::hideEvent(QHideEvent *e)
{
    QWidget::hideEvent(e);
    notifyDecorationsWindowMinimized(true);
}

void KisQPainterCanvas::showEvent(QShowEvent *e)
{
    QWidget::showEvent(e);
    notifyDecorationsWindowMinimized(false);
}

void KisQPainterCanvas::channelSelectionChanged(const QBitArray &channelFlags)
{
    Q_ASSERT(m_d->prescaledProjection);
    m_d->prescaledProjection->setChannelFlags(channelFlags);
}

void KisQPainterCanvas::setDisplayConfig(const KisDisplayConfig &config)
{
    Q_ASSERT(m_d->prescaledProjection);
    m_d->prescaledProjection->setDisplayConfig(config);
}

void KisQPainterCanvas::setDisplayFilter(QSharedPointer<KisDisplayFilter> displayFilter)
{
        Q_ASSERT(m_d->prescaledProjection);
        m_d->prescaledProjection->setDisplayFilter(displayFilter);

        canvas()->startUpdateInPatches(canvas()->image()->bounds());
}

void KisQPainterCanvas::notifyImageColorSpaceChanged(const KoColorSpace *cs)
{
    Q_UNUSED(cs);
    // FIXME: on color space change the data is refetched multiple
    //        times by different actors!
    canvas()->startUpdateInPatches(canvas()->image()->bounds());
}

void KisQPainterCanvas::setWrapAroundViewingMode(bool value)
{
    Q_UNUSED(value);
    dbgKrita << "Wrap around viewing mode not implemented in QPainter Canvas.";
    return;
}

bool KisQPainterCanvas::wrapAroundViewingMode() const
{
    return false;
}

void KisQPainterCanvas::setWrapAroundViewingModeAxis(WrapAroundAxis value)
{
    Q_UNUSED(value);
    dbgKrita << "Wrap around viewing mode not implemented in QPainter Canvas.";
    return;
}

WrapAroundAxis KisQPainterCanvas::wrapAroundViewingModeAxis() const
{
    return WRAPAROUND_BOTH;
}

void KisQPainterCanvas::finishResizingImage(qint32 w, qint32 h)
{
    m_d->prescaledProjection->slotImageSizeChanged(w, h);
}

KisUpdateInfoSP KisQPainterCanvas::startUpdateCanvasProjection(const QRect & rc)
{
    return m_d->prescaledProjection->updateCache(rc);
}


QRect KisQPainterCanvas::updateCanvasProjection(KisUpdateInfoSP info)
{
    /**
    * It might happen that the canvas type is switched while the
    * update info is being stuck in the Qt's signals queue. Than a wrong
    * type of the info may come. So just check it here.
    */
    bool isPPUpdateInfo = dynamic_cast<KisPPUpdateInfo*>(info.data());
    if (isPPUpdateInfo) {
        m_d->prescaledProjection->recalculateCache(info);
        return info->dirtyViewportRect();
    } else {
        return QRect();
    }
}


void KisQPainterCanvas::resizeEvent(QResizeEvent *e)
{
    QSize size(e->size());
    if (size.width() <= 0) {
        size.setWidth(1);
    }
    if (size.height() <= 0) {
        size.setHeight(1);
    }

    m_d->prescaledProjection->notifyCanvasSizeChanged(size);
}

void KisQPainterCanvas::slotConfigChanged()
{
    KisConfig cfg(true);

    m_d->checkBrush = QBrush(createCheckersImage());
    m_d->scrollCheckers = cfg.scrollCheckers();
    notifyConfigChanged();
}

bool KisQPainterCanvas::callFocusNextPrevChild(bool next)
{
    return focusNextPrevChild(next);
}
