/****************************************************************************
**
** Copyright (C) 2017 Jolla Ltd, author: <giulio.camuffo@jollamobile.com>
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtWaylandCompositor module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qwaylandview.h"
#include "qwaylandview_p.h"
#include "qwaylandsurface.h"
#include <QtWaylandCompositor/QWaylandSeat>
#include <QtWaylandCompositor/QWaylandCompositor>

#include <QtWaylandCompositor/private/qwaylandsurface_p.h>
#include <QtWaylandCompositor/private/qwaylandoutput_p.h>

#include <QtCore/QMutex>

QT_BEGIN_NAMESPACE

void QWaylandViewPrivate::markSurfaceAsDestroyed(QWaylandSurface *surface)
{
    Q_Q(QWaylandView);
    Q_ASSERT(surface == this->surface);

    setSurface(nullptr);
    QPointer<QWaylandView> deleteGuard(q);
    emit q->surfaceDestroyed();
    if (!deleteGuard.isNull())
        clearFrontBuffer();
}

/*!
 * \qmltype WaylandView
 * \inqmlmodule QtWayland.Compositor
 * \since 5.8
 * \brief Represents a view of a surface on an output.
 *
 * The WaylandView corresponds to the presentation of a surface on a specific
 * output, managing the buffers that contain the contents to be rendered.
 * You can have several views into the same surface.
 */

/*!
 * \class QWaylandView
 * \inmodule QtWaylandCompositor
 * \since 5.8
 * \brief The QWaylandView class represents a view of a surface on an output.
 *
 * The QWaylandView corresponds to the presentation of a surface on a specific
 * output, managing the buffers that contain the contents to be rendered.
 * You can have several views into the same surface.
 */

/*!
 * Constructs a QWaylandView with the given \a renderObject and \a parent.
 */
QWaylandView::QWaylandView(QObject *renderObject, QObject *parent)
    : QObject(*new QWaylandViewPrivate(),parent)
{
    d_func()->renderObject = renderObject;
}

/*!
 * Destroys the QWaylandView.
 */
QWaylandView::~QWaylandView()
{
    Q_D(QWaylandView);
    if (d->surface) {
        if (d->output)
            QWaylandOutputPrivate::get(d->output)->removeView(this, d->surface);

        QWaylandSurfacePrivate::get(d->surface)->derefView(this);
    }

}

/*!
* \internal
*  Didn't we decide to remove this property?
*/
QObject *QWaylandView::renderObject() const
{
    Q_D(const QWaylandView);
    return d->renderObject;
}

/*!
 * \qmlproperty WaylandSurface QtWaylandCompositor::WaylandView::surface
 *
 * This property holds the surface viewed by this WaylandView.
 */

/*!
 * \property QWaylandView::surface
 *
 * This property holds the surface viewed by this QWaylandView.
 */
QWaylandSurface *QWaylandView::surface() const
{
    Q_D(const QWaylandView);
    return d->surface;
}


void QWaylandViewPrivate::setSurface(QWaylandSurface *newSurface)
{
    Q_Q(QWaylandView);
    if (surface) {
        QWaylandSurfacePrivate::get(surface)->derefView(q);
        if (output)
            QWaylandOutputPrivate::get(output)->removeView(q, surface);
    }

    surface = newSurface;

    nextBuffer = QWaylandBufferRef();
    nextBufferCommitted = false;
    nextDamage = QRegion();

    if (surface) {
        QWaylandSurfacePrivate::get(surface)->refView(q);
        if (output)
            QWaylandOutputPrivate::get(output)->addView(q, surface);
    }
}

void QWaylandViewPrivate::clearFrontBuffer()
{
    if (!bufferLocked) {
        currentBuffer = QWaylandBufferRef();
        currentDamage = QRegion();
    }
}

void QWaylandView::setSurface(QWaylandSurface *newSurface)
{
    Q_D(QWaylandView);
    if (d->surface == newSurface)
        return;

    d->setSurface(newSurface);
    d->clearFrontBuffer();
    emit surfaceChanged();
}

/*!
 * \qmlproperty WaylandOutput QtWaylandCompositor::WaylandView::output
 *
 * This property holds the output on which this view displays its surface.
 */

/*!
 * \property QWaylandView::output
 *
 * This property holds the output on which this view displays its surface.
 */
QWaylandOutput *QWaylandView::output() const
{
    Q_D(const QWaylandView);
    return d->output;
}

void QWaylandView::setOutput(QWaylandOutput *newOutput)
{
    Q_D(QWaylandView);
    if (d->output == newOutput)
        return;

    if (d->output && d->surface)
        QWaylandOutputPrivate::get(d->output)->removeView(this, d->surface);

    d->output = newOutput;

    if (d->output && d->surface)
        QWaylandOutputPrivate::get(d->output)->addView(this, d->surface);

    emit outputChanged();
}

/*!
 * This function is called when a new \a buffer is committed to this view's surface.
 * \a damage contains the region that is different from the current buffer, i.e. the
 * region that needs to be updated.
 * The new \a buffer will become current on the next call to advance().
 *
 * Subclasses that reimplement this function \e must call the base implementation.
 */
void QWaylandView::bufferCommitted(const QWaylandBufferRef &buffer, const QRegion &damage)
{
    Q_D(QWaylandView);
    QMutexLocker locker(&d->bufferMutex);
    d->nextBuffer = buffer;
    d->nextDamage = damage;
    d->nextBufferCommitted = true;
}

/*!
 * Updates the current buffer and damage region to the latest version committed by the client.
 * Returns true if new content was committed since the previous call to advance().
 * Otherwise returns false.
 *
 * \sa currentBuffer(), currentDamage()
 */
bool QWaylandView::advance()
{
    Q_D(QWaylandView);

    if (!d->nextBufferCommitted && !d->forceAdvanceSucceed)
        return false;

    if (d->bufferLocked)
        return false;

    if (d->surface && d->surface->primaryView() == this) {
        const auto views = d->surface->views();
        for (QWaylandView *view : views) {
            if (view != this && view->allowDiscardFrontBuffer() && view->d_func()->currentBuffer == d->currentBuffer)
                view->discardCurrentBuffer();
        }
    }

    QMutexLocker locker(&d->bufferMutex);
    d->forceAdvanceSucceed = false;
    d->nextBufferCommitted = false;
    d->currentBuffer = d->nextBuffer;
    d->currentDamage = d->nextDamage;
    return true;
}

/*!
 * Force the view to discard its current buffer, to allow it to be reused on the client side.
 */
void QWaylandView::discardCurrentBuffer()
{
    Q_D(QWaylandView);
    QMutexLocker locker(&d->bufferMutex);
    d->currentBuffer = QWaylandBufferRef();
    d->forceAdvanceSucceed = true;
}

/*!
 * Returns a reference to this view's current buffer.
 */
QWaylandBufferRef QWaylandView::currentBuffer()
{
    Q_D(QWaylandView);
    QMutexLocker locker(&d->bufferMutex);
    return d->currentBuffer;
}

/*!
 * Returns the current damage region of this view.
 */
QRegion QWaylandView::currentDamage()
{
    Q_D(QWaylandView);
    QMutexLocker locker(&d->bufferMutex);
    return d->currentDamage;
}

/*!
 * \qmlproperty bool QtWaylandCompositor::WaylandView::bufferLocked
 *
 * This property holds whether the view's buffer is currently locked. When
 * the buffer is locked, advance() will not advance to the next buffer and
 * returns \c false.
 *
 * The default is \c false.
 */

/*!
 * \property QWaylandView::bufferLocked
 *
 * This property holds whether the view's buffer is currently locked. When
 * the buffer is locked, advance() will not advance to the next buffer
 * and returns \c false.
 *
 * The default is \c false.
 */
bool QWaylandView::isBufferLocked() const
{
    Q_D(const QWaylandView);
    return d->bufferLocked;
}

void QWaylandView::setBufferLocked(bool locked)
{
    Q_D(QWaylandView);
    if (d->bufferLocked == locked)
        return;
    d->bufferLocked = locked;
    emit bufferLockedChanged();
}
/*!
 * \qmlproperty bool QtWaylandCompositor::WaylandView::allowDiscardFrontBuffer
 *
 * By default, the view locks the current buffer until advance() is called. Set this property
 * to true to allow Qt to release the buffer when the primary view is no longer using it.
 *
 * This can be used to avoid the situation where a secondary view that updates on a lower
 * frequency will throttle the frame rate of the client application.
 */

/*!
 * \property QWaylandView::allowDiscardFrontBuffer
 *
 * By default, the view locks the current buffer until advance() is called. Set this property
 * to \c true to allow Qt to release the buffer when the primary view is no longer using it.
 *
 * This can be used to avoid the situation where a secondary view that updates on a lower
 * frequency will throttle the frame rate of the client application.
 */
bool QWaylandView::allowDiscardFrontBuffer() const
{
    Q_D(const QWaylandView);
    return d->allowDiscardFrontBuffer;
}

void QWaylandView::setAllowDiscardFrontBuffer(bool discard)
{
    Q_D(QWaylandView);
    if (d->allowDiscardFrontBuffer == discard)
        return;
    d->allowDiscardFrontBuffer = discard;
    emit allowDiscardFrontBufferChanged();
}

/*!
 * Makes this QWaylandView the primary view for the surface.
 *
 * It has no effect if this QWaylandView is not holding any QWaylandSurface
 *
 * \sa QWaylandSurface::primaryView
 */
void QWaylandView::setPrimary()
{
    Q_D(QWaylandView);
    if (d->surface)
        d->surface->setPrimaryView(this);
    else
        qWarning("Calling setPrimary() on a QWaylandView without a surface has no effect.");
}

/*!
 * Returns true if this QWaylandView is the primary view for the QWaylandSurface
 *
 * \sa QWaylandSurface::primaryView
 */
bool QWaylandView::isPrimary() const
{
    Q_D(const QWaylandView);
    return d->surface && d->surface->primaryView() == this;
}

/*!
 * Returns the Wayland surface resource for this QWaylandView.
 */
struct wl_resource *QWaylandView::surfaceResource() const
{
    Q_D(const QWaylandView);
    if (!d->surface)
        return nullptr;
    return d->surface->resource();
}

QT_END_NAMESPACE
