/*
 * Copyright (C) 2013-2016 Canonical Ltd.
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License version 3, as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
 * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// local
#include "application.h"
#include "session.h"
#include "mirsurfaceitem.h"
#include "logging.h"
#include "tracepoints.h" // generated from tracepoints.tp
#include "timestamp.h"

// common
#include <debughelpers.h>

// Qt
#include <QDebug>
#include <QGuiApplication>
#include <QMutexLocker>
#include <QQmlEngine>
#include <QQuickWindow>
#include <QScreen>
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
#include <private/qquickwindow_p.h>
#include <private/qsgdefaultrendercontext_p.h>
#endif
#include <private/qsgdefaultinternalimagenode_p.h>
#include <QTimer>
#include <QSGTextureProvider>
#include <QRunnable>

namespace qtmir {

namespace {

class MirSurfaceItemReleaseResourcesJob : public QRunnable
{
public:
    MirSurfaceItemReleaseResourcesJob() : textureProvider(nullptr) {}
    void run() {
        delete textureProvider;
        textureProvider = nullptr;
    }
    QObject *textureProvider;
};

} // namespace {

class SurfaceItemTextureProvider : public QSGTextureProvider
{
    Q_OBJECT
public:
    SurfaceItemTextureProvider(const QSharedPointer<QSGTexture> texture)
        : t(texture)
    {
    }

    QSGTexture *texture() const override {
        return t.data();
    }

    void releaseTexture() {
        QMutexLocker releaseLocker(&lock);
        t.reset();
    }

    void setTexture(const QSharedPointer<QSGTexture>& newTexture) {
        t = newTexture;
        if (t) {
            t->setFiltering(m_smooth ? QSGTexture::Linear : QSGTexture::Nearest);
        }

        Q_EMIT textureChanged();
    }

    QMutex lock;

public Q_SLOTS:
    void setSmooth(bool smooth) {
        m_smooth = smooth;
        if (t) {
            t->setFiltering(m_smooth ? QSGTexture::Linear : QSGTexture::Nearest);
        }
    }

private:
    QSharedPointer<QSGTexture> t;
    bool m_smooth;
};

MirSurfaceItem::MirSurfaceItem(QQuickItem *parent)
    : MirSurfaceItemInterface(parent)
    , m_surface(nullptr)
    , m_window(nullptr)
    , m_textureProvider(nullptr)
    , m_lastTouchEvent(nullptr)
    , m_lastFrameNumberRendered(nullptr)
    , m_surfaceWidth(0)
    , m_surfaceHeight(0)
    , m_orientationAngle(nullptr)
    , m_consumesInput(false)
    , m_fillMode(Stretch)
{
    qCDebug(QTMIR_SURFACES) << "MirSurfaceItem::MirSurfaceItem";

    setSmooth(true);
    setFlag(QQuickItem::ItemHasContents, true); //so scene graph will render this item

    m_updateMirSurfaceSizeTimer.setSingleShot(true);
    m_updateMirSurfaceSizeTimer.setInterval(1);
    connect(&m_updateMirSurfaceSizeTimer, &QTimer::timeout, this, &MirSurfaceItem::updateMirSurfaceSize);

    connect(this, &QQuickItem::activeFocusChanged, this, &MirSurfaceItem::updateMirSurfaceActiveFocus);
    connect(this, &QQuickItem::visibleChanged, this, &MirSurfaceItem::updateMirSurfaceExposure);
    connect(this, &QQuickItem::windowChanged, this, &MirSurfaceItem::onWindowChanged);
}

MirSurfaceItem::~MirSurfaceItem()
{
    qCDebug(QTMIR_SURFACES) << "MirSurfaceItem::~MirSurfaceItem - this=" << this;

    setSurface(nullptr);

    delete m_lastTouchEvent;
    delete m_lastFrameNumberRendered;
    delete m_orientationAngle;

    // Belongs to the scene graph thread. Can't delete here.
    // Scene graph should call MirSurfaceItem::releaseResources() or invalidateSceneGraph()
    // delete m_textureProvider;
}

Mir::Type MirSurfaceItem::type() const
{
    if (m_surface) {
        return m_surface->type();
    } else {
        return Mir::UnknownType;
    }
}

Mir::OrientationAngle MirSurfaceItem::orientationAngle() const
{
    if (m_orientationAngle) {
        Q_ASSERT(!m_surface);
        return *m_orientationAngle;
    } else if (m_surface) {
        return m_surface->orientationAngle();
    } else {
        return Mir::Angle0;
    }
}

void MirSurfaceItem::setOrientationAngle(Mir::OrientationAngle angle)
{
    qCDebug(QTMIR_SURFACES, "MirSurfaceItem::setOrientationAngle(%d)", angle);

    if (m_surface) {
        Q_ASSERT(!m_orientationAngle);
        m_surface->setOrientationAngle(angle);
    } else if (!m_orientationAngle) {
        m_orientationAngle = new Mir::OrientationAngle;
        *m_orientationAngle = angle;
        Q_EMIT orientationAngleChanged(angle);
    } else if (*m_orientationAngle != angle) {
        *m_orientationAngle = angle;
        Q_EMIT orientationAngleChanged(angle);
    }
}

QString MirSurfaceItem::name() const
{
    if (m_surface) {
        return m_surface->name();
    } else {
        return QString();
    }
}

bool MirSurfaceItem::live() const
{
    return m_surface && m_surface->live();
}

Mir::ShellChrome MirSurfaceItem::shellChrome() const
{
    return m_surface ? m_surface->shellChrome() : Mir::NormalChrome;
}

// Called from the rendering (scene graph) thread
QSGTextureProvider *MirSurfaceItem::textureProvider() const
{
    QMutexLocker mutexLocker(const_cast<QMutex*>(&m_mutex));
    const_cast<MirSurfaceItem *>(this)->ensureTextureProvider();
    return m_textureProvider;
}

void MirSurfaceItem::ensureTextureProvider()
{
    if (!m_surface) {
        return;
    }

    const qintptr userId = (qintptr)window();
    if (!userId) return;

    if (!m_textureProvider) {
        m_textureProvider = new SurfaceItemTextureProvider(m_surface->texture(userId));
        connect(this, &QQuickItem::smoothChanged, m_textureProvider, &SurfaceItemTextureProvider::setSmooth);
        m_textureProvider->setSmooth(smooth());

    // Check that the item is indeed using the texture from the MirSurface it currently holds
    // If until now we were drawing a MirSurface "A" and it replaced with a MirSurface "B",
    // we will still hold the texture from "A" until the first time we're asked to draw "B".
    // That's the moment when we finally discard the texture from "A" and get the one from "B".
    //
    // Also note that m_surface->weakTexture() will return null if m_surface->texture() was never
    // called before.
    } else if (!m_textureProvider->texture() || m_textureProvider->texture() != m_surface->weakTexture(userId)) {
        m_textureProvider->setTexture(m_surface->texture(userId));
    }
}

QSGNode *MirSurfaceItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)    // called by render thread
{
    QMutexLocker mutexLocker(&m_mutex);

    if (!m_surface) {
        if (m_textureProvider) {
            m_textureProvider->releaseTexture();
        }
        delete oldNode;
        return nullptr;
    }

    ensureTextureProvider();
    QMutexLocker textureMutexLocker(m_surface->textureMutex());
    QMutexLocker providerLocker(&m_textureProvider->lock);

    const qintptr userId = (qintptr)window();
    auto subtextures = m_surface->updateTexture(userId);

    if (!userId || !m_textureProvider->texture() || subtextures.empty()) {
        delete oldNode;
        return nullptr;
    }

#if MIR_SERVER_VERSION < MIR_VERSION_NUMBER(2, 20, 0)
    if (m_surface->numBuffersReadyForCompositor(userId) > 0) {
        QTimer::singleShot(0, this, &MirSurfaceItem::update);
    }
#endif

    QSGDefaultInternalImageNode *node = static_cast<QSGDefaultInternalImageNode*>(oldNode);
    if (!node) {
        qWarning() << "New node";
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
        QSGRenderContext *rc = QQuickWindowPrivate::get(window())->context;
        node = new QSGDefaultInternalImageNode(static_cast<QSGDefaultRenderContext *>(rc));
#else
        node = new QSGDefaultInternalImageNode;
#endif
        node->setMipmapFiltering(QSGTexture::None);
        node->setHorizontalWrapMode(QSGTexture::ClampToEdge);
        node->setVerticalWrapMode(QSGTexture::ClampToEdge);
    } else {
        if (!m_lastFrameNumberRendered  || (*m_lastFrameNumberRendered != m_surface->currentFrameNumber(userId))) {
            node->markDirty(QSGNode::DirtyMaterial);
        }
    }
    auto texture = m_textureProvider->texture();
    node->setTexture(texture);

    // If we have no subtextures at all, drop all other childnodes
    if (subtextures.size() - 1 <= 0) {
        if (node->childCount() > 0) {
            qWarning() << " dropping all childnodes";
            while (node->lastChild()) {
                QSGNode* lastChild = node->lastChild();
                node->removeChildNode(lastChild);
                delete lastChild;
            }
        }
    } else {
        // - 1 since first subtexture is main texture
        bool isEqual = (long int)subtextures.size() - 1 == (long int)node->childCount();
        if (!isEqual) {
            qWarning() << "Not equal, recreating childnodes," << (long int)subtextures.size() << "vs" << (long int)node->childCount();
            if (node->childCount() > 0) {
                qWarning() << " dropping all childnodes";
                while (node->lastChild()) {
                    QSGNode* lastChild = node->lastChild();
                    node->removeChildNode(lastChild);
                    delete lastChild;
                }
            }
        }

        // Meh, this is not great code
        int first = 0;
        for (auto sub : subtextures) {
            first++;
            // Skip first subtexture as this is the main texture
            if (first < 2)
                continue;

            // Atempt at some caching, we only recrate the nodes if the amount of childnodes
            // does not match subtextures, this could be better
            QSGDefaultInternalImageNode *subnode;
            if (isEqual) {
                subnode = static_cast<QSGDefaultInternalImageNode*>(node->childAtIndex(first - 2));
                subnode->markDirty(QSGNode::DirtyMaterial);
            } else {
        #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
                QSGRenderContext *rc = QQuickWindowPrivate::get(window())->context;
                subnode = new QSGDefaultInternalImageNode(static_cast<QSGDefaultRenderContext *>(rc));
        #else
                subnode = new QSGDefaultInternalImageNode;
        #endif
                subnode->setMipmapFiltering(QSGTexture::None);
                subnode->setHorizontalWrapMode(QSGTexture::ClampToEdge);
                subnode->setVerticalWrapMode(QSGTexture::ClampToEdge);
            }

            // texture is memory managed by mirsubsufrace's shared buffer
            subnode->setTexture(sub.texture.data());

            subnode->setSubSourceRect(QRectF(0, 0, 1, 1));
            subnode->setTargetRect(sub.extent);
            subnode->setInnerTargetRect(sub.extent);
            subnode->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest);
            subnode->setAntialiasing(antialiasing());
            subnode->update();

            if (!isEqual)
                node->appendChildNode(subnode);
        }
    }
    
    if (m_fillMode == PadOrCrop) {
        const QSize &textureSize = texture->textureSize();

        QRectF targetRect;
        targetRect.setWidth(qMin(width(), static_cast<qreal>(textureSize.width())));
        targetRect.setHeight(qMin(height(), static_cast<qreal>(textureSize.height())));

        // x/y from extent is offset
        auto r = subtextures[0].extent;
        targetRect.setX(r.x());
        targetRect.setY(r.y());

        qreal u = targetRect.width() / textureSize.width();
        qreal v = targetRect.height() / textureSize.height();
        node->setSubSourceRect(QRectF(0, 0, u, v));

        node->setTargetRect(targetRect);
        node->setInnerTargetRect(targetRect);
    } else {
        // Stretch
        const QSize &textureSize = texture->textureSize();
        qWarning() << "Streethcing" << height() << textureSize.height();
        node->setSubSourceRect(QRectF(0, 0, 1, 1));

        auto r = subtextures[0].extent;
        auto offset_x = r.x();
        auto offset_y = r.y();
        node->setTargetRect(QRectF(offset_x, offset_y, width(), height()));
        node->setInnerTargetRect(QRectF(offset_x, offset_y, width(), height()));
    }

    node->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest);
    node->setAntialiasing(antialiasing());

    node->update();

    if (!m_lastFrameNumberRendered) {
        m_lastFrameNumberRendered = new unsigned int;
    }
    *m_lastFrameNumberRendered = m_surface->currentFrameNumber(userId);

    // Keep a reference to current subtextures for the scene graph not to crash
    m_subtextures = subtextures;

    return node;
}

void MirSurfaceItem::mousePressEvent(QMouseEvent *event)
{
    auto mousePos = event->localPos().toPoint();
    if (m_consumesInput && m_surface && m_surface->live() && m_surface->inputAreaContains(mousePos)) {
        m_surface->mousePressEvent(event);
    } else {
        event->ignore();
    }
}

void MirSurfaceItem::mouseMoveEvent(QMouseEvent *event)
{
    if (m_consumesInput && m_surface && m_surface->live()) {
        m_surface->mouseMoveEvent(event);
    } else {
        event->ignore();
    }
}

void MirSurfaceItem::mouseReleaseEvent(QMouseEvent *event)
{
    if (m_consumesInput && m_surface && m_surface->live()) {
        m_surface->mouseReleaseEvent(event);
    } else {
        event->ignore();
    }
}

void MirSurfaceItem::wheelEvent(QWheelEvent *event)
{
    if (m_consumesInput && m_surface && m_surface->live()) {
        m_surface->wheelEvent(event);
    } else {
        event->ignore();
    }
}

void MirSurfaceItem::hoverEnterEvent(QHoverEvent *event)
{
    if (m_consumesInput && m_surface && m_surface->live()) {
        m_surface->hoverEnterEvent(event);
    } else {
        event->ignore();
    }
}

void MirSurfaceItem::hoverLeaveEvent(QHoverEvent *event)
{
    if (m_consumesInput && m_surface && m_surface->live()) {
        m_surface->hoverLeaveEvent(event);
    } else {
        event->ignore();
    }
}

void MirSurfaceItem::hoverMoveEvent(QHoverEvent *event)
{
    // WORKAROUND for https://github.com/ubports/ubuntu-touch/issues/787
    // This is a improved workaround that allows "mouse" hover events to work correctly by
    // ignoring hover move events with no timestamp as these are bogus synthesized touch events
    if (m_consumesInput && m_surface && m_surface->live() && event->timestamp() != 0) {
        m_surface->hoverMoveEvent(event);
    } else {
        event->ignore();
    }
}

void MirSurfaceItem::keyPressEvent(QKeyEvent *event)
{
    if (m_consumesInput && m_surface && m_surface->live()) {
        m_surface->keyPressEvent(event);
    } else {
        event->ignore();
    }
}

void MirSurfaceItem::keyReleaseEvent(QKeyEvent *event)
{
    if (m_consumesInput && m_surface && m_surface->live()) {
        m_surface->keyReleaseEvent(event);
    } else {
        event->ignore();
    }
}

QString MirSurfaceItem::appId() const
{
    if (m_surface) {
        return m_surface->appId();
    } else {
        return QStringLiteral("-");
    }
}

void MirSurfaceItem::endCurrentTouchSequence(ulong timestamp)
{
    Q_ASSERT(m_lastTouchEvent);
    Q_ASSERT(m_lastTouchEvent->type != QEvent::TouchEnd);
    Q_ASSERT(m_lastTouchEvent->touchPoints.count() > 0);

    TouchEvent touchEvent = *m_lastTouchEvent;
    touchEvent.timestamp = timestamp;

    // Remove all already released touch points
    int i = 0;
    while (i < touchEvent.touchPoints.count()) {
        if (touchEvent.touchPoints[i].state() == Qt::TouchPointReleased) {
            touchEvent.touchPoints.removeAt(i);
        } else {
            ++i;
        }
    }

    // And release the others one by one as Mir expects one press/release per event
    while (touchEvent.touchPoints.count() > 0) {
        touchEvent.touchPoints[0].setState(Qt::TouchPointReleased);

        touchEvent.updateTouchPointStatesAndType();

        m_surface->touchEvent(touchEvent.modifiers, touchEvent.touchPoints,
                               touchEvent.touchPointStates, touchEvent.timestamp);

        *m_lastTouchEvent = touchEvent;

        touchEvent.touchPoints.removeAt(0);
    }
}

void MirSurfaceItem::validateAndDeliverTouchEvent(int eventType,
            ulong timestamp,
            Qt::KeyboardModifiers mods,
            const QList<QTouchEvent::TouchPoint> &touchPoints,
            Qt::TouchPointStates touchPointStates)
{
    if (eventType == QEvent::TouchBegin && m_lastTouchEvent && m_lastTouchEvent->type != QEvent::TouchEnd) {
        qCWarning(QTMIR_SURFACES) << qPrintable(QStringLiteral("MirSurfaceItem(%1) - Got a QEvent::TouchBegin while "
            "there's still an active/unfinished touch sequence.").arg(appId()));
        // Qt forgot to end the last touch sequence. Let's do it ourselves.
        endCurrentTouchSequence(timestamp);
    }

    m_surface->touchEvent(mods, touchPoints, touchPointStates, timestamp);

    if (!m_lastTouchEvent) {
        m_lastTouchEvent = new TouchEvent;
    }
    m_lastTouchEvent->type = eventType;
    m_lastTouchEvent->timestamp = timestamp;
    m_lastTouchEvent->touchPoints = touchPoints;
    m_lastTouchEvent->touchPointStates = touchPointStates;

    tracepoint(qtmir, touchEventConsume_end, uncompressTimestamp<ulong>(timestamp).count());
}

void MirSurfaceItem::touchEvent(QTouchEvent *event)
{
    tracepoint(qtmir, touchEventConsume_start, uncompressTimestamp<ulong>(event->timestamp()).count());

    bool accepted = processTouchEvent(event->type(),
            event->timestamp(),
            event->modifiers(),
            event->touchPoints(),
            event->touchPointStates());
    event->setAccepted(accepted);
}

bool MirSurfaceItem::processTouchEvent(
        int eventType,
        ulong timestamp,
        Qt::KeyboardModifiers mods,
        const QList<QTouchEvent::TouchPoint> &touchPoints,
        Qt::TouchPointStates touchPointStates)
{
    if (!m_consumesInput || !m_surface || !m_surface->live()) {
        return false;
    }

    if (eventType == QEvent::TouchBegin && !hasTouchInsideInputRegion(touchPoints)) {
        return false;
    }

    validateAndDeliverTouchEvent(eventType, timestamp, mods, touchPoints, touchPointStates);

    return true;
}

bool MirSurfaceItem::hasTouchInsideInputRegion(const QList<QTouchEvent::TouchPoint> &touchPoints)
{
    for (int i = 0; i < touchPoints.count(); ++i) {
        QPoint pos = touchPoints.at(i).pos().toPoint();
        if (m_surface->inputAreaContains(pos)) {
            return true;
        }
    }
    return false;
}

Mir::State MirSurfaceItem::surfaceState() const
{
    if (m_surface) {
        return m_surface->state();
    } else {
        return Mir::UnknownState;
    }
}

void MirSurfaceItem::scheduleMirSurfaceSizeUpdate()
{
    if (!m_updateMirSurfaceSizeTimer.isActive()) {
        m_updateMirSurfaceSizeTimer.start();
    }
}

void MirSurfaceItem::updateMirSurfaceSize()
{
    if (!m_surface || !m_surface->live() || (m_surfaceWidth <= 0 && m_surfaceHeight <= 0)) {
        return;
    }

    // If one dimension is not set, fallback to the current value
    int width = m_surfaceWidth > 0 ? m_surfaceWidth : m_surface->size().width();
    int height = m_surfaceHeight > 0 ? m_surfaceHeight : m_surface->size().height();

    m_surface->resize(width, height);
}

void MirSurfaceItem::updateMirSurfaceExposure()
{
    if (!m_surface || !m_surface->live()) {
        return;
    }

    m_surface->setViewExposure((qintptr)this, isVisible());
}

void MirSurfaceItem::updateMirSurfaceActiveFocus()
{
    if (m_surface && m_surface->live()) {
        m_surface->setViewActiveFocus(qintptr(this), m_consumesInput && hasActiveFocus());
    }
}

void MirSurfaceItem::invalidateSceneGraph()
{
    delete m_textureProvider;
    m_textureProvider = nullptr;
}

void MirSurfaceItem::TouchEvent::updateTouchPointStatesAndType()
{
    touchPointStates = Qt::TouchPointStates{};
    for (int i = 0; i < touchPoints.count(); ++i) {
        touchPointStates |= touchPoints.at(i).state();
    }

    if (touchPointStates == Qt::TouchPointReleased) {
        type = QEvent::TouchEnd;
    } else if (touchPointStates == Qt::TouchPointPressed) {
        type = QEvent::TouchBegin;
    } else {
        type = QEvent::TouchUpdate;
    }
}

bool MirSurfaceItem::consumesInput() const
{
    return m_consumesInput;
}

void MirSurfaceItem::setConsumesInput(bool value)
{
    if (m_consumesInput == value) {
        return;
    }

    m_consumesInput = value;
    if (m_consumesInput) {
        setAcceptedMouseButtons(Qt::LeftButton | Qt::MiddleButton | Qt::RightButton |
            Qt::ExtraButton1 | Qt::ExtraButton2 | Qt::ExtraButton3 | Qt::ExtraButton4 |
            Qt::ExtraButton5 | Qt::ExtraButton6 | Qt::ExtraButton7 | Qt::ExtraButton8 |
            Qt::ExtraButton9 | Qt::ExtraButton10 | Qt::ExtraButton11 |
            Qt::ExtraButton12 | Qt::ExtraButton13);
        setAcceptHoverEvents(true);
    } else {
        setAcceptedMouseButtons(Qt::NoButton);
        setAcceptHoverEvents(false);
    }

    updateMirSurfaceActiveFocus();
    Q_EMIT consumesInputChanged(value);
}

lomiri::shell::application::MirSurfaceInterface* MirSurfaceItem::surface() const
{
    return m_surface;
}

void MirSurfaceItem::setSurface(lomiri::shell::application::MirSurfaceInterface *lomiriShellSurface)
{
    QMutexLocker mutexLocker(&m_mutex);

    auto surface = static_cast<qtmir::MirSurfaceInterface*>(lomiriShellSurface);
    qCDebug(QTMIR_SURFACES).nospace() << "MirSurfaceItem::setSurface surface=" << surface;

    if (surface == m_surface) {
        return;
    }

    if (m_surface) {
        disconnect(m_surface, nullptr, this, nullptr);
        m_surface->unregisterView((qintptr)this);
        unsetCursor();
    }

    m_surface = surface;

    if (m_surface) {
        m_surface->registerView((qintptr)this);

        // When a new mir frame gets posted we notify the QML engine that this item needs redrawing,
        // schedules call to updatePaintNode() from the rendering thread
        connect(m_surface, &MirSurfaceInterface::framesPosted, this, &QQuickItem::update);

        connect(m_surface, &MirSurfaceInterface::stateChanged, this, &MirSurfaceItem::surfaceStateChanged);
        connect(m_surface, &MirSurfaceInterface::liveChanged, this, &MirSurfaceItem::liveChanged);
        connect(m_surface, &MirSurfaceInterface::sizeChanged, this, &MirSurfaceItem::onActualSurfaceSizeChanged);
        connect(m_surface, &MirSurfaceInterface::cursorChanged, this, &MirSurfaceItem::setCursor);
        connect(m_surface, &MirSurfaceInterface::shellChromeChanged, this, &MirSurfaceItem::shellChromeChanged);
        // Prevent compositor frame swapping to cause a crash due to invalid access
        connect(m_surface, &QObject::destroyed, this, [this](QObject*){
            setSurface(nullptr);
        }, Qt::DirectConnection);

        Q_EMIT typeChanged(m_surface->type());
        Q_EMIT liveChanged(true);
        Q_EMIT surfaceStateChanged(m_surface->state());

        updateMirSurfaceSize();
        setImplicitSize(m_surface->size().width(), m_surface->size().height());
        updateMirSurfaceExposure();

        // Qt::ArrowCursor is the default when no cursor has been explicitly set, so no point forwarding it.
        if (m_surface->cursor().shape() != Qt::ArrowCursor) {
            setCursor(m_surface->cursor());
        }

        if (m_orientationAngle) {
            m_surface->setOrientationAngle(*m_orientationAngle);
            connect(m_surface, &MirSurfaceInterface::orientationAngleChanged, this, &MirSurfaceItem::orientationAngleChanged);
            delete m_orientationAngle;
            m_orientationAngle = nullptr;
        } else {
            connect(m_surface, &MirSurfaceInterface::orientationAngleChanged, this, &MirSurfaceItem::orientationAngleChanged);
            Q_EMIT orientationAngleChanged(m_surface->orientationAngle());
        }

        updateMirSurfaceActiveFocus();
    }

    update();

    Q_EMIT surfaceChanged(m_surface);
}

void MirSurfaceItem::onCompositorSwappedBuffers()
{
    if (Q_LIKELY(m_surface)) {
        m_surface->onCompositorSwappedBuffers();
    }
}

void MirSurfaceItem::onWindowChanged(QQuickWindow *window)
{
    if (m_window) {
        disconnect(m_window, nullptr, this, nullptr);
    }
    m_window = window;
    if (m_window) {
        connect(m_window, &QQuickWindow::frameSwapped, this, &MirSurfaceItem::onCompositorSwappedBuffers,
                Qt::DirectConnection);
    }
}

void MirSurfaceItem::releaseResources()
{
    if (m_textureProvider) {
        Q_ASSERT(window());

        MirSurfaceItemReleaseResourcesJob *job = new MirSurfaceItemReleaseResourcesJob;
        job->textureProvider = m_textureProvider;
        m_textureProvider = nullptr;
        window()->scheduleRenderJob(job, QQuickWindow::AfterSynchronizingStage);
    }
}

int MirSurfaceItem::surfaceWidth() const
{
    return m_surfaceWidth;
}

void MirSurfaceItem::setSurfaceWidth(int value)
{
    if (value != m_surfaceWidth) {
        m_surfaceWidth = value;
        scheduleMirSurfaceSizeUpdate();
        Q_EMIT surfaceWidthChanged(value);
    }
}

void MirSurfaceItem::onActualSurfaceSizeChanged(QSize size)
{
    setImplicitSize(size.width(), size.height());
}

int MirSurfaceItem::surfaceHeight() const
{
    return m_surfaceHeight;
}

void MirSurfaceItem::setSurfaceHeight(int value)
{
    if (value != m_surfaceHeight) {
        m_surfaceHeight = value;
        scheduleMirSurfaceSizeUpdate();
        Q_EMIT surfaceHeightChanged(value);
    }
}

MirSurfaceItem::FillMode MirSurfaceItem::fillMode() const
{
    return m_fillMode;
}

void MirSurfaceItem::setFillMode(FillMode value)
{
    if (m_fillMode != value) {
        m_fillMode = value;
        Q_EMIT fillModeChanged(m_fillMode);
    }
}

} // namespace qtmir

#include "mirsurfaceitem.moc"
