/*
    SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
    SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>

    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "surface_interface.h"
#include "surface_interface_p.h"
#include "buffer_interface.h"
#include "clientconnection.h"
#include "compositor_interface.h"
#include "display.h"
#include "idleinhibit_v1_interface_p.h"
#include "pointerconstraints_v1_interface_p.h"
#include "region_interface_p.h"
#include "subcompositor_interface.h"
#include "subsurface_interface_p.h"
#include "surfacerole_p.h"
#include "utils.h"
// std
#include <algorithm>

namespace KWaylandServer
{

QList<SurfaceInterface *> SurfaceInterfacePrivate::surfaces;

KWaylandFrameCallback::KWaylandFrameCallback(wl_resource *resource, SurfaceInterface *surface)
    : QtWaylandServer::wl_callback(resource)
    , surface(surface)
{
}

void KWaylandFrameCallback::destroy()
{
    wl_resource_destroy(resource()->handle);
}

void KWaylandFrameCallback::callback_destroy_resource(Resource *)
{
    if (surface) {
        SurfaceInterfacePrivate *surfacePrivate = SurfaceInterfacePrivate::get(surface);
        surfacePrivate->current.frameCallbacks.removeOne(this);
        surfacePrivate->pending.frameCallbacks.removeOne(this);
        surfacePrivate->cached.frameCallbacks.removeOne(this);
    }
    delete this;
}

SurfaceInterfacePrivate::SurfaceInterfacePrivate(SurfaceInterface *q)
    : q(q)
{
    surfaces.append(q);
}

SurfaceInterfacePrivate::~SurfaceInterfacePrivate()
{
    // Need a copy to avoid hitting invalidated iterators in the for loop.
    const QList<KWaylandFrameCallback *> currentFrameCallbacks = current.frameCallbacks;
    for (KWaylandFrameCallback *frameCallback : currentFrameCallbacks) {
        frameCallback->destroy();
    }

    const QList<KWaylandFrameCallback *> pendingFrameCallbacks = pending.frameCallbacks;
    for (KWaylandFrameCallback *frameCallback : pendingFrameCallbacks) {
        frameCallback->destroy();
    }

    const QList<KWaylandFrameCallback *> cachedFrameCallbacks = cached.frameCallbacks;
    for (KWaylandFrameCallback *frameCallback : cachedFrameCallbacks) {
        frameCallback->destroy();
    }

    if (current.buffer) {
        current.buffer->unref();
    }
    surfaces.removeOne(q);
}

void SurfaceInterfacePrivate::addChild(SubSurfaceInterface *child)
{
    // protocol is not precise on how to handle the addition of new sub surfaces
    pending.children.append(child);
    cached.children.append(child);
    current.children.append(child);
    child->surface()->setOutputs(outputs);
    Q_EMIT q->childSubSurfaceAdded(child);
    Q_EMIT q->childSubSurfacesChanged();
}

void SurfaceInterfacePrivate::removeChild(SubSurfaceInterface *child)
{
    // protocol is not precise on how to handle the addition of new sub surfaces
    pending.children.removeAll(child);
    cached.children.removeAll(child);
    current.children.removeAll(child);
    Q_EMIT q->childSubSurfaceRemoved(child);
    Q_EMIT q->childSubSurfacesChanged();
}

bool SurfaceInterfacePrivate::raiseChild(SubSurfaceInterface *subsurface, SurfaceInterface *sibling)
{
    auto it = std::find(pending.children.begin(), pending.children.end(), subsurface);
    if (it == pending.children.end()) {
        return false;
    }
    if (pending.children.count() == 1) {
        // nothing to do
        return true;
    }
    if (sibling == q) {
        // it's to the parent, so needs to become last item
        pending.children.erase(it);
        pending.children.append(subsurface);
        pending.childrenChanged = true;
        return true;
    }
    if (!sibling->subSurface()) {
        // not a sub surface
        return false;
    }
    auto siblingIt = std::find(pending.children.begin(), pending.children.end(), sibling->subSurface());
    if (siblingIt == pending.children.end() || siblingIt == it) {
        // not a sibling
        return false;
    }
    auto value = (*it);
    pending.children.erase(it);
    // find the iterator again
    siblingIt = std::find(pending.children.begin(), pending.children.end(), sibling->subSurface());
    pending.children.insert(++siblingIt, value);
    pending.childrenChanged = true;
    return true;
}

bool SurfaceInterfacePrivate::lowerChild(SubSurfaceInterface *subsurface, SurfaceInterface *sibling)
{
    auto it = std::find(pending.children.begin(), pending.children.end(), subsurface);
    if (it == pending.children.end()) {
        return false;
    }
    if (pending.children.count() == 1) {
        // nothing to do
        return true;
    }
    if (sibling == q) {
        // it's to the parent, so needs to become first item
        auto value = *it;
        pending.children.erase(it);
        pending.children.prepend(value);
        pending.childrenChanged = true;
        return true;
    }
    if (!sibling->subSurface()) {
        // not a sub surface
        return false;
    }
    auto siblingIt = std::find(pending.children.begin(), pending.children.end(), sibling->subSurface());
    if (siblingIt == pending.children.end() || siblingIt == it) {
        // not a sibling
        return false;
    }
    auto value = (*it);
    pending.children.erase(it);
    // find the iterator again
    siblingIt = std::find(pending.children.begin(), pending.children.end(), sibling->subSurface());
    pending.children.insert(siblingIt, value);
    pending.childrenChanged = true;
    return true;
}

void SurfaceInterfacePrivate::setShadow(const QPointer<ShadowInterface> &shadow)
{
    pending.shadow = shadow;
    pending.shadowIsSet = true;
}

void SurfaceInterfacePrivate::setBlur(const QPointer<BlurInterface> &blur)
{
    pending.blur = blur;
    pending.blurIsSet = true;
}

void SurfaceInterfacePrivate::setSlide(const QPointer<SlideInterface> &slide)
{
    pending.slide = slide;
    pending.slideIsSet = true;
}

void SurfaceInterfacePrivate::setContrast(const QPointer<ContrastInterface> &contrast)
{
    pending.contrast = contrast;
    pending.contrastIsSet = true;
}

void SurfaceInterfacePrivate::installPointerConstraint(LockedPointerV1Interface *lock)
{
    Q_ASSERT(!lockedPointer);
    Q_ASSERT(!confinedPointer);

    lockedPointer = lock;

    auto cleanUp = [this]() {
        lockedPointer = nullptr;
        QObject::disconnect(constrainsOneShotConnection);
        constrainsOneShotConnection = QMetaObject::Connection();
        QObject::disconnect(constrainsUnboundConnection);
        constrainsUnboundConnection = QMetaObject::Connection();
        Q_EMIT q->pointerConstraintsChanged();
    };

    if (lock->lifeTime() == LockedPointerV1Interface::LifeTime::OneShot) {
        constrainsOneShotConnection = QObject::connect(lock, &LockedPointerV1Interface::lockedChanged, q,
            [this, cleanUp] {
                if (lockedPointer->isLocked()) {
                    return;
                }
                cleanUp();
            }
        );
    }
    constrainsUnboundConnection = QObject::connect(lock, &LockedPointerV1Interface::destroyed, q, cleanUp);
    Q_EMIT q->pointerConstraintsChanged();
}

void SurfaceInterfacePrivate::installPointerConstraint(ConfinedPointerV1Interface *confinement)
{
    Q_ASSERT(!lockedPointer);
    Q_ASSERT(!confinedPointer);

    confinedPointer = confinement;

    auto cleanUp = [this]() {
        confinedPointer = nullptr;
        QObject::disconnect(constrainsOneShotConnection);
        constrainsOneShotConnection = QMetaObject::Connection();
        QObject::disconnect(constrainsUnboundConnection);
        constrainsUnboundConnection = QMetaObject::Connection();
        Q_EMIT q->pointerConstraintsChanged();
    };

    if (confinement->lifeTime() == ConfinedPointerV1Interface::LifeTime::OneShot) {
        constrainsOneShotConnection = QObject::connect(confinement, &ConfinedPointerV1Interface::confinedChanged, q,
            [this, cleanUp] {
                if (confinedPointer->isConfined()) {
                    return;
                }
                cleanUp();
            }
        );
    }
    constrainsUnboundConnection = QObject::connect(confinement, &ConfinedPointerV1Interface::destroyed, q, cleanUp);
    Q_EMIT q->pointerConstraintsChanged();
}

void SurfaceInterfacePrivate::installIdleInhibitor(IdleInhibitorV1Interface *inhibitor)
{
    idleInhibitors << inhibitor;
    QObject::connect(inhibitor, &IdleInhibitorV1Interface::destroyed, q,
        [this, inhibitor] {
            idleInhibitors.removeOne(inhibitor);
            if (idleInhibitors.isEmpty()) {
                Q_EMIT q->inhibitsIdleChanged();
            }
        }
    );
    if (idleInhibitors.count() == 1) {
        Q_EMIT q->inhibitsIdleChanged();
    }
}

void SurfaceInterfacePrivate::surface_destroy_resource(Resource *)
{
    Q_EMIT q->aboutToBeDestroyed();
    delete q;
}

void SurfaceInterfacePrivate::surface_destroy(Resource *resource)
{
    wl_resource_destroy(resource->handle);
}

void SurfaceInterfacePrivate::surface_attach(Resource *resource, struct ::wl_resource *buffer, int32_t x, int32_t y)
{
    Q_UNUSED(resource)
    pending.bufferIsSet = true;
    pending.offset = QPoint(x, y);
    if (!buffer) {
        // got a null buffer, deletes content in next frame
        pending.buffer = nullptr;
        pending.damage = QRegion();
        pending.bufferDamage = QRegion();
        return;
    }
    pending.buffer = BufferInterface::get(compositor->display(), buffer);
    QObject::connect(pending.buffer, &BufferInterface::aboutToBeDestroyed, q, &SurfaceInterface::handleBufferRemoved,  Qt::UniqueConnection);
}

void SurfaceInterfacePrivate::surface_damage(Resource *, int32_t x, int32_t y, int32_t width, int32_t height)
{
    pending.damage |= QRect(x, y, width, height);
}

void SurfaceInterfacePrivate::surface_frame(Resource *resource, uint32_t callback)
{
    wl_resource *callbackResource = wl_resource_create(resource->client(), &wl_callback_interface,
                                                       /* version */ 1, callback);
    if (!callbackResource) {
        wl_resource_post_no_memory(resource->handle);
        return;
    }
    pending.frameCallbacks.append(new KWaylandFrameCallback(callbackResource, q));
}

void SurfaceInterfacePrivate::surface_set_opaque_region(Resource *resource, struct ::wl_resource *region)
{
    Q_UNUSED(resource)
    RegionInterface *r = RegionInterface::get(region);
    pending.opaque = r ? r->region() : QRegion();
    pending.opaqueIsSet = true;

}

void SurfaceInterfacePrivate::surface_set_input_region(Resource *resource, struct ::wl_resource *region)
{
    Q_UNUSED(resource)
    RegionInterface *r = RegionInterface::get(region);
    pending.input = r ? r->region() : infiniteRegion();
    pending.inputIsSet = true;
}

void SurfaceInterfacePrivate::surface_commit(Resource *resource)
{
    Q_UNUSED(resource)
    commit();
}

void SurfaceInterfacePrivate::surface_set_buffer_transform(Resource *resource, int32_t transform)
{
    if (transform < 0 || transform > WL_OUTPUT_TRANSFORM_FLIPPED_270) {
        wl_resource_post_error(resource->handle, error_invalid_transform,
                               "buffer transform must be a valid transform (%d specified)", transform);
        return;
    }
    pending.bufferTransform = OutputInterface::Transform(transform);
    pending.bufferTransformIsSet = true;
}

void SurfaceInterfacePrivate::surface_set_buffer_scale(Resource *resource, int32_t scale)
{
    if (scale < 1) {
        wl_resource_post_error(resource->handle, error_invalid_scale,
                               "buffer scale must be at least one (%d specified)", scale);
        return;
    }
    pending.bufferScale = scale;
    pending.bufferScaleIsSet = true;
}

void SurfaceInterfacePrivate::surface_damage_buffer(Resource *resource, int32_t x, int32_t y, int32_t width, int32_t height)
{
    Q_UNUSED(resource)
    pending.bufferDamage |= QRect(x, y, width, height);
}

SurfaceInterface::SurfaceInterface(CompositorInterface *compositor, wl_resource *resource)
    : QObject(compositor)
    , d(new SurfaceInterfacePrivate(this))
{
    d->compositor = compositor;
    d->init(resource);
    d->client = compositor->display()->getConnection(d->resource()->client());
}

SurfaceInterface::~SurfaceInterface()
{
}

uint32_t SurfaceInterface::id() const
{
    return wl_resource_get_id(resource());
}

ClientConnection *SurfaceInterface::client() const
{
    return d->client;
}

wl_resource *SurfaceInterface::resource() const
{
    return d->resource()->handle;
}

CompositorInterface *SurfaceInterface::compositor() const
{
    return d->compositor;
}

QList<SurfaceInterface *> SurfaceInterface::surfaces()
{
    return SurfaceInterfacePrivate::surfaces;
}

void SurfaceInterface::frameRendered(quint32 msec)
{
    // notify all callbacks
    const bool needsFlush = !d->current.frameCallbacks.isEmpty();
    while (!d->current.frameCallbacks.isEmpty()) {
        KWaylandFrameCallback *frameCallback = d->current.frameCallbacks.takeFirst();
        frameCallback->send_done(msec);
        frameCallback->destroy();
    }
    for (auto it = d->current.children.constBegin(); it != d->current.children.constEnd(); ++it) {
        (*it)->surface()->frameRendered(msec);
    }
    if (needsFlush)  {
        client()->flush();
    }
}

bool SurfaceInterface::hasFrameCallbacks() const
{
    return !d->current.frameCallbacks.isEmpty();
}

QMatrix4x4 SurfaceInterfacePrivate::buildSurfaceToBufferMatrix(const State *state)
{
    // The order of transforms is reversed, i.e. the viewport transform is the first one.

    QMatrix4x4 surfaceToBufferMatrix;

    if (!state->buffer) {
        return surfaceToBufferMatrix;
    }

    surfaceToBufferMatrix.scale(state->bufferScale, state->bufferScale);

    switch (state->bufferTransform) {
    case OutputInterface::Transform::Normal:
    case OutputInterface::Transform::Flipped:
        break;
    case OutputInterface::Transform::Rotated90:
    case OutputInterface::Transform::Flipped90:
        surfaceToBufferMatrix.translate(0, state->buffer->height() / state->bufferScale);
        surfaceToBufferMatrix.rotate(-90, 0, 0, 1);
        break;
    case OutputInterface::Transform::Rotated180:
    case OutputInterface::Transform::Flipped180:
        surfaceToBufferMatrix.translate(state->buffer->width() / state->bufferScale,
                                        state->buffer->height() / state->bufferScale);
        surfaceToBufferMatrix.rotate(-180, 0, 0, 1);
        break;
    case OutputInterface::Transform::Rotated270:
    case OutputInterface::Transform::Flipped270:
        surfaceToBufferMatrix.translate(state->buffer->width() / state->bufferScale, 0);
        surfaceToBufferMatrix.rotate(-270, 0, 0, 1);
        break;
    }

    switch (state->bufferTransform) {
    case OutputInterface::Transform::Flipped:
    case OutputInterface::Transform::Flipped180:
        surfaceToBufferMatrix.translate(state->buffer->width() / state->bufferScale, 0);
        surfaceToBufferMatrix.scale(-1, 1);
        break;
    case OutputInterface::Transform::Flipped90:
    case OutputInterface::Transform::Flipped270:
        surfaceToBufferMatrix.translate(state->buffer->height() / state->bufferScale, 0);
        surfaceToBufferMatrix.scale(-1, 1);
        break;
    default:
        break;
    }

    if (state->sourceGeometry.isValid()) {
        surfaceToBufferMatrix.translate(state->sourceGeometry.x(), state->sourceGeometry.y());
        surfaceToBufferMatrix.scale(state->sourceGeometry.width() / state->size.width(),
                                    state->sourceGeometry.height() / state->size.height());
    }

    return surfaceToBufferMatrix;
}

void SurfaceInterfacePrivate::swapStates(State *source, State *target, bool emitChanged)
{
    const bool bufferChanged = source->bufferIsSet;
    const bool opaqueRegionChanged = source->opaqueIsSet;
    const bool inputRegionChanged = source->inputIsSet;
    const bool scaleFactorChanged = source->bufferScaleIsSet && (target->bufferScale != source->bufferScale);
    const bool transformChanged = source->bufferTransformIsSet && (target->bufferTransform != source->bufferTransform);
    const bool shadowChanged = source->shadowIsSet;
    const bool blurChanged = source->blurIsSet;
    const bool contrastChanged = source->contrastIsSet;
    const bool slideChanged = source->slideIsSet;
    const bool childrenChanged = source->childrenChanged;
    const bool visibilityChanged = bufferChanged && (bool(source->buffer) != bool(target->buffer));
    const QSize oldSize = target->size;
    const QSize oldBufferSize = bufferSize;
    const QMatrix4x4 oldSurfaceToBufferMatrix = surfaceToBufferMatrix;
    const QRegion oldInputRegion = inputRegion;
    if (bufferChanged) {
        // TODO: is the reffing correct for subsurfaces?
        if (target->buffer) {
            if (emitChanged) {
                target->buffer->unref();
            } else {
                target->buffer = nullptr;
            }
        }
        if (source->buffer) {
            if (emitChanged) {
                source->buffer->ref();
            }
        }
        target->buffer = source->buffer;
        target->offset = source->offset;
        target->damage = source->damage;
        target->bufferDamage = source->bufferDamage;
        target->bufferIsSet = source->bufferIsSet;
    }
    if (source->sourceGeometryIsSet) {
        target->sourceGeometry = source->sourceGeometry;
        target->sourceGeometryIsSet = true;
    }
    if (source->destinationSizeIsSet) {
        target->destinationSize = source->destinationSize;
        target->destinationSizeIsSet = true;
    }
    if (childrenChanged) {
        target->childrenChanged = source->childrenChanged;
        target->children = source->children;
    }
    target->frameCallbacks.append(source->frameCallbacks);

    if (shadowChanged) {
        target->shadow = source->shadow;
        target->shadowIsSet = true;
    }
    if (blurChanged) {
        target->blur = source->blur;
        target->blurIsSet = true;
    }
    if (contrastChanged) {
        target->contrast = source->contrast;
        target->contrastIsSet = true;
    }
    if (slideChanged) {
        target->slide = source->slide;
        target->slideIsSet = true;
    }
    if (inputRegionChanged) {
        target->input = source->input;
        target->inputIsSet = true;
    }
    if (opaqueRegionChanged) {
        target->opaque = source->opaque;
        target->opaqueIsSet = true;
    }
    if (scaleFactorChanged) {
        target->bufferScale = source->bufferScale;
        target->bufferScaleIsSet = true;
    }
    if (transformChanged) {
        target->bufferTransform = source->bufferTransform;
        target->bufferTransformIsSet = true;
    }
    if (lockedPointer) {
        auto lockedPointerPrivate = LockedPointerV1InterfacePrivate::get(lockedPointer);
        lockedPointerPrivate->commit();
    }
    if (confinedPointer) {
        auto confinedPointerPrivate = ConfinedPointerV1InterfacePrivate::get(confinedPointer);
        confinedPointerPrivate->commit();
    }

    *source = State{};
    source->children = target->children;

    if (!emitChanged) {
        return;
    }
    // TODO: Refactor the state management code because it gets more clumsy.
    if (target->buffer) {
        bufferSize = target->buffer->size();
        if (target->destinationSize.isValid()) {
            target->size = target->destinationSize;
        } else if (target->sourceGeometry.isValid()) {
            target->size = target->sourceGeometry.size().toSize();
        } else {
            target->size = target->buffer->size() / target->bufferScale;
            switch (target->bufferTransform) {
            case OutputInterface::Transform::Rotated90:
            case OutputInterface::Transform::Rotated270:
            case OutputInterface::Transform::Flipped90:
            case OutputInterface::Transform::Flipped270:
                target->size.transpose();
                break;
            case OutputInterface::Transform::Normal:
            case OutputInterface::Transform::Rotated180:
            case OutputInterface::Transform::Flipped:
            case OutputInterface::Transform::Flipped180:
                break;
            }
        }
    } else {
        target->size = QSize();
        bufferSize = QSize();
    }
    surfaceToBufferMatrix = buildSurfaceToBufferMatrix(target);
    bufferToSurfaceMatrix = surfaceToBufferMatrix.inverted();
    inputRegion = target->input & QRect(QPoint(0, 0), target->size);
    if (opaqueRegionChanged) {
        Q_EMIT q->opaqueChanged(target->opaque);
    }
    if (oldInputRegion != inputRegion) {
        Q_EMIT q->inputChanged(inputRegion);
    }
    if (scaleFactorChanged) {
        Q_EMIT q->bufferScaleChanged(target->bufferScale);
    }
    if (transformChanged) {
        Q_EMIT q->bufferTransformChanged(target->bufferTransform);
    }
    if (visibilityChanged) {
        if (target->buffer) {
            subSurfaceIsMapped = true;
            Q_EMIT q->mapped();
        } else {
            subSurfaceIsMapped = false;
            Q_EMIT q->unmapped();
        }
    }
    if (bufferChanged) {
        if (target->buffer && (!target->damage.isEmpty() || !target->bufferDamage.isEmpty())) {
            const QRegion windowRegion = QRegion(0, 0, q->size().width(), q->size().height());
            const QRegion bufferDamage = q->mapFromBuffer(target->bufferDamage);
            target->damage = windowRegion.intersected(target->damage.united(bufferDamage));
            Q_EMIT q->damaged(target->damage);
        }
    }
    if (surfaceToBufferMatrix != oldSurfaceToBufferMatrix) {
        Q_EMIT q->surfaceToBufferMatrixChanged();
    }
    if (bufferSize != oldBufferSize) {
        Q_EMIT q->bufferSizeChanged();
    }
    if (target->size != oldSize) {
        Q_EMIT q->sizeChanged();
    }
    if (shadowChanged) {
        Q_EMIT q->shadowChanged();
    }
    if (blurChanged) {
        Q_EMIT q->blurChanged();
    }
    if (contrastChanged) {
        Q_EMIT q->contrastChanged();
    }
    if (slideChanged) {
        Q_EMIT q->slideOnShowHideChanged();
    }
    if (childrenChanged) {
        Q_EMIT q->childSubSurfacesChanged();
    }
}

void SurfaceInterfacePrivate::commit()
{
    if (!subSurface) {
        swapStates(&pending, &current, true);

        // The position of a sub-surface is applied when its parent is committed.
        const QList<SubSurfaceInterface *> children = current.children;
        for (SubSurfaceInterface *subsurface : children) {
            auto subsurfacePrivate = SubSurfaceInterfacePrivate::get(subsurface);
            subsurfacePrivate->parentCommit();
        }
    }

    if (role) {
        role->commit();
    }

    Q_EMIT q->committed();
}

QRegion SurfaceInterface::damage() const
{
    return d->current.damage;
}

QRegion SurfaceInterface::opaque() const
{
    return d->current.opaque;
}

QRegion SurfaceInterface::input() const
{
    return d->inputRegion;
}

qint32 SurfaceInterface::bufferScale() const
{
    return d->current.bufferScale;
}

OutputInterface::Transform SurfaceInterface::bufferTransform() const
{
    return d->current.bufferTransform;
}

BufferInterface *SurfaceInterface::buffer()
{
    return d->current.buffer;
}

QPoint SurfaceInterface::offset() const
{
    return d->current.offset;
}

SurfaceInterface *SurfaceInterface::get(wl_resource *native)
{
    if (auto surfacePrivate = resource_cast<SurfaceInterfacePrivate *>(native)) {
        return surfacePrivate->q;
    }
    return nullptr;
}

SurfaceInterface *SurfaceInterface::get(quint32 id, const ClientConnection *client)
{
    const QList<SurfaceInterface *> candidates = surfaces();
    for (SurfaceInterface *surface : candidates) {
        if (surface->client() == client && surface->id() == id) {
            return surface;
        }
    }
    return nullptr;
}

QList<SubSurfaceInterface *> SurfaceInterface::childSubSurfaces() const
{
    return d->current.children;
}

SubSurfaceInterface *SurfaceInterface::subSurface() const
{
    return d->subSurface;
}

QSize SurfaceInterface::size() const
{
    return d->current.size;
}

QRect SurfaceInterface::boundingRect() const
{
    QRect rect(QPoint(0, 0), size());

    const QList<SubSurfaceInterface *> subSurfaces = childSubSurfaces();
    for (const SubSurfaceInterface *subSurface : subSurfaces) {
        const SurfaceInterface *childSurface = subSurface->surface();
        rect |= childSurface->boundingRect().translated(subSurface->position());
    }

    return rect;
}

QPointer< ShadowInterface > SurfaceInterface::shadow() const
{
    return d->current.shadow;
}

QPointer< BlurInterface > SurfaceInterface::blur() const
{
    return d->current.blur;
}

QPointer< ContrastInterface > SurfaceInterface::contrast() const
{
    return d->current.contrast;
}

QPointer< SlideInterface > SurfaceInterface::slideOnShowHide() const
{
    return d->current.slide;
}

bool SurfaceInterface::isMapped() const
{
    if (d->subSurface) {
        // from spec:
        // "A sub-surface becomes mapped, when a non-NULL wl_buffer is applied and the parent surface is mapped."
        return d->subSurfaceIsMapped && d->subSurface->parentSurface() && d->subSurface->parentSurface()->isMapped();
    }
    return d->current.buffer != nullptr;
}

QVector<OutputInterface *> SurfaceInterface::outputs() const
{
    return d->outputs;
}

void SurfaceInterface::setOutputs(const QVector<OutputInterface *> &outputs)
{
    QVector<OutputInterface *> removedOutputs = d->outputs;
    for (auto it = outputs.constBegin(), end = outputs.constEnd(); it != end; ++it) {
        const auto o = *it;
        removedOutputs.removeOne(o);
    }
    for (auto it = removedOutputs.constBegin(), end = removedOutputs.constEnd(); it != end; ++it) {
        const auto resources = (*it)->clientResources(client());
        for (wl_resource *outputResource : resources) {
            d->send_leave(outputResource);
        }
        disconnect(d->outputDestroyedConnections.take(*it));
        disconnect(d->outputBoundConnections.take(*it));
    }
    QVector<OutputInterface *> addedOutputsOutputs = outputs;
    for (auto it = d->outputs.constBegin(), end = d->outputs.constEnd(); it != end; ++it) {
        const auto o = *it;
        addedOutputsOutputs.removeOne(o);
    }
    for (auto it = addedOutputsOutputs.constBegin(), end = addedOutputsOutputs.constEnd(); it != end; ++it) {
        const auto o = *it;
        const auto resources = o->clientResources(client());
        for (wl_resource *outputResource : resources) {
            d->send_enter(outputResource);
        }
        d->outputDestroyedConnections[o] = connect(o, &OutputInterface::removed, this, [this, o] {
            auto outputs = d->outputs;
            if (outputs.removeOne(o)) {
                setOutputs(outputs);
            }});

        Q_ASSERT(!d->outputBoundConnections.contains(o));
        d->outputBoundConnections[o] = connect(o, &OutputInterface::bound, this, [this](ClientConnection *c, wl_resource *outputResource) {
            if (c != client()) {
                return;
            }
            d->send_enter(outputResource);
        });
    }

    d->outputs = outputs;
    for (auto child : d->current.children) {
        child->surface()->setOutputs(outputs);
    }
}

SurfaceInterface *SurfaceInterface::surfaceAt(const QPointF &position)
{
    if (!isMapped()) {
        return nullptr;
    }
    // go from top to bottom. Top most child is last in list
    QListIterator<SubSurfaceInterface *> it(d->current.children);
    it.toBack();
    while (it.hasPrevious()) {
        const auto &current = it.previous();
        auto surface = current->surface();
        if (auto s = surface->surfaceAt(position - current->position())) {
            return s;
        }
    }
    // check whether the geometry contains the pos
    if (!size().isEmpty() && QRectF(QPoint(0, 0), size()).contains(position)) {
        return this;
    }
    return nullptr;
}

SurfaceInterface *SurfaceInterface::inputSurfaceAt(const QPointF &position)
{
    // TODO: Most of this is very similar to SurfaceInterface::surfaceAt
    //       Is there a way to reduce the code duplication?
    if (!isMapped()) {
        return nullptr;
    }
    // go from top to bottom. Top most child is last in list
    QListIterator<SubSurfaceInterface *> it(d->current.children);
    it.toBack();
    while (it.hasPrevious()) {
        const auto &current = it.previous();
        auto surface = current->surface();
        if (auto s = surface->inputSurfaceAt(position - current->position())) {
            return s;
        }
    }
    // check whether the geometry and input region contain the pos
    if (!size().isEmpty() && QRectF(QPoint(0, 0), size()).contains(position) &&
            input().contains(position.toPoint())) {
        return this;
    }
    return nullptr;
}

LockedPointerV1Interface *SurfaceInterface::lockedPointer() const
{
    return d->lockedPointer;
}

ConfinedPointerV1Interface *SurfaceInterface::confinedPointer() const
{
    return d->confinedPointer;
}

bool SurfaceInterface::inhibitsIdle() const
{
    return !d->idleInhibitors.isEmpty();
}

void SurfaceInterface::setDataProxy(SurfaceInterface *surface)
{
    d->dataProxy = surface;
}

SurfaceInterface* SurfaceInterface::dataProxy() const
{
    return d->dataProxy;
}

QPointF SurfaceInterface::mapToBuffer(const QPointF &point) const
{
    return d->surfaceToBufferMatrix.map(point);
}

QPointF SurfaceInterface::mapFromBuffer(const QPointF &point) const
{
    return d->bufferToSurfaceMatrix.map(point);
}

static QRegion map_helper(const QMatrix4x4 &matrix, const QRegion &region)
{
    QRegion result;
    for (const QRect &rect : region) {
        result += matrix.mapRect(rect);
    }
    return result;
}

QRegion SurfaceInterface::mapToBuffer(const QRegion &region) const
{
    return map_helper(d->surfaceToBufferMatrix, region);
}

QRegion SurfaceInterface::mapFromBuffer(const QRegion &region) const
{
    return map_helper(d->bufferToSurfaceMatrix, region);
}

QMatrix4x4 SurfaceInterface::surfaceToBufferMatrix() const
{
    return d->surfaceToBufferMatrix;
}

void SurfaceInterface::handleBufferRemoved(BufferInterface *buffer)
{
    if (d->pending.buffer == buffer) {
        d->pending.buffer = nullptr;
    }
    if (d->cached.buffer == buffer) {
        d->cached.buffer = nullptr;
    }
    if (d->current.buffer == buffer) {
        d->current.buffer->unref();
        d->current.buffer = nullptr;
    }
}

QPointF SurfaceInterface::mapToChild(SurfaceInterface *child, const QPointF &point) const
{
    QPointF local = point;
    SurfaceInterface *surface = child;

    while (true) {
        if (surface == this) {
            return local;
        }

        SubSurfaceInterface *subsurface = surface->subSurface();
        if (Q_UNLIKELY(!subsurface)) {
            return QPointF();
        }

        local -= subsurface->position();
        surface = subsurface->parentSurface();
    }

    return QPointF();
}


QSize SurfaceInterface::bufferSize() const
{
    return d->bufferSize;
}

} // namespace KWaylandServer
