/****************************************************************************
**
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the QtOpenGL module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qgl.h"
#include <private/qt_x11_p.h>
#include <private/qpixmap_x11_p.h>
#include <private/qgl_p.h>
#include <private/qpaintengine_opengl_p.h>
#include "qgl_egl_p.h"
#include "qcolormap.h"
#include <QDebug>
#include <QPixmap>


QT_BEGIN_NAMESPACE


/*
    QGLTemporaryContext implementation
*/

class QGLTemporaryContextPrivate
{
public:
    bool initialized;
    Window window;
    EGLContext context;
    EGLSurface surface;
    EGLDisplay display;
};

QGLTemporaryContext::QGLTemporaryContext(bool, QWidget *)
    : d(new QGLTemporaryContextPrivate)
{
    d->initialized = false;
    d->window = 0;
    d->context = 0;
    d->surface = 0;
    int screen = 0;

    d->display = QEgl::display();

    EGLConfig config;
    int numConfigs = 0;
    EGLint attribs[] = {
        EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
#ifdef QT_OPENGL_ES_2
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
#endif
        EGL_NONE
    };

    eglChooseConfig(d->display, attribs, &config, 1, &numConfigs);
    if (!numConfigs) {
        qWarning("QGLTemporaryContext: No EGL configurations available.");
        return;
    }

    XVisualInfo visualInfo;
    XVisualInfo *vi;
    int numVisuals;
    EGLint id = 0;

    visualInfo.visualid = QEgl::getCompatibleVisualId(config);
    vi = XGetVisualInfo(X11->display, VisualIDMask, &visualInfo, &numVisuals);
    if (!vi || numVisuals < 1) {
        qWarning("QGLTemporaryContext: Unable to get X11 visual info id.");
        return;
    }

    d->window = XCreateWindow(X11->display, RootWindow(X11->display, screen),
                              0, 0, 1, 1, 0,
                              vi->depth, InputOutput, vi->visual,
                              0, 0);

    d->surface = eglCreateWindowSurface(d->display, config, (EGLNativeWindowType) d->window, NULL);

    if (d->surface == EGL_NO_SURFACE) {
        qWarning("QGLTemporaryContext: Error creating EGL surface.");
        XFree(vi);
        XDestroyWindow(X11->display, d->window);
        return;
    }

    EGLint contextAttribs[] = {
#ifdef QT_OPENGL_ES_2
        EGL_CONTEXT_CLIENT_VERSION, 2,
#endif
        EGL_NONE
    };
    d->context = eglCreateContext(d->display, config, 0, contextAttribs);
    if (d->context != EGL_NO_CONTEXT
        && eglMakeCurrent(d->display, d->surface, d->surface, d->context))
    {
        d->initialized = true;
    } else {
        qWarning("QGLTemporaryContext: Error creating EGL context.");
        eglDestroySurface(d->display, d->surface);
        XDestroyWindow(X11->display, d->window);
    }
    XFree(vi);
}

QGLTemporaryContext::~QGLTemporaryContext()
{
    if (d->initialized) {
        eglMakeCurrent(d->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
        eglDestroyContext(d->display, d->context);
        eglDestroySurface(d->display, d->surface);
        XDestroyWindow(X11->display, d->window);
    }
}

bool QGLFormat::hasOpenGLOverlays()
{
    return false;
}

// Chooses the EGL config and creates the EGL context
bool QGLContext::chooseContext(const QGLContext* shareContext)
{
    Q_D(QGLContext);

    if (!device())
        return false;

    int devType = device()->devType();

    QX11PixmapData *x11PixmapData = 0;
    if (devType == QInternal::Pixmap) {
        QPixmapData *pmd = static_cast<QPixmap*>(device())->data_ptr().data();
        if (pmd->classId() == QPixmapData::X11Class)
            x11PixmapData = static_cast<QX11PixmapData*>(pmd);
        else {
            // TODO: Replace the pixmap's data with a new QX11PixmapData
            qWarning("WARNING: Creating a QGLContext on a QPixmap is only supported for X11 pixmap backend");
            return false;
        }
    } else if ((devType != QInternal::Widget) && (devType != QInternal::Pbuffer)) {
        qWarning("WARNING: Creating a QGLContext not supported on device type %d", devType);
        return false;
    }

    // Only create the eglContext if we don't already have one:
    if (d->eglContext == 0) {
        d->eglContext = new QEglContext();
        d->ownsEglContext = true;
        d->eglContext->setApi(QEgl::OpenGL);

        // If the device is a widget with WA_TranslucentBackground set, make sure the glFormat
        // has the alpha channel option set:
        if (devType == QInternal::Widget) {
            QWidget* widget = static_cast<QWidget*>(device());
            if (widget->testAttribute(Qt::WA_TranslucentBackground))
                d->glFormat.setAlpha(true);
        }

        // Construct the configuration we need for this surface.
        QEglProperties configProps;
        configProps.setDeviceType(devType);
        configProps.setRenderableType(QEgl::OpenGL);
        qt_eglproperties_set_glformat(configProps, d->glFormat);

        // Set buffer preserved for regular QWidgets, QGLWidgets are ok with either preserved or destroyed:
        if ((devType == QInternal::Widget) && qobject_cast<QGLWidget*>(static_cast<QWidget*>(device())) == 0)
            configProps.setValue(EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED);

        if (!d->eglContext->chooseConfig(configProps, QEgl::BestPixelFormat)) {
            delete d->eglContext;
            d->eglContext = 0;
            return false;
        }

        // Create a new context for the configuration.
        QEglContext* eglSharedContext = shareContext ? shareContext->d_func()->eglContext : 0;
        if (!d->eglContext->createContext(eglSharedContext)) {
            delete d->eglContext;
            d->eglContext = 0;
            return false;
        }
        d->sharing = d->eglContext->isSharing();
        if (d->sharing && shareContext)
            const_cast<QGLContext *>(shareContext)->d_func()->sharing = true;
    }

    // Inform the higher layers about the actual format properties
    qt_glformat_from_eglconfig(d->glFormat, d->eglContext->config());

    // Do don't create the EGLSurface for everything.
    //    QWidget - yes, create the EGLSurface and store it in QGLContextPrivate::eglSurface
    //    QGLWidget - yes, create the EGLSurface and store it in QGLContextPrivate::eglSurface
    //    QPixmap - yes, create the EGLSurface but store it in QX11PixmapData::gl_surface
    //    QGLPixelBuffer - no, it creates the surface itself and stores it in QGLPixelBufferPrivate::pbuf

    if (devType == QInternal::Widget) {
        if (d->eglSurface != EGL_NO_SURFACE)
            eglDestroySurface(d->eglContext->display(), d->eglSurface);
        d->eglSurface = QEgl::createSurface(device(), d->eglContext->config());
        XFlush(X11->display);
        setWindowCreated(true);
    }

    if (x11PixmapData) {
        // TODO: Actually check to see if the existing surface can be re-used
        if (x11PixmapData->gl_surface)
            eglDestroySurface(d->eglContext->display(), (EGLSurface)x11PixmapData->gl_surface);

        x11PixmapData->gl_surface = (void*)QEgl::createSurface(device(), d->eglContext->config());
    }

    return true;
}

void QGLWidget::resizeEvent(QResizeEvent *)
{
    Q_D(QGLWidget);
    if (!isValid())
        return;
    makeCurrent();
    if (!d->glcx->initialized())
        glInit();
    resizeGL(width(), height());
    //handle overlay
}

const QGLContext* QGLWidget::overlayContext() const
{
    return 0;
}

void QGLWidget::makeOverlayCurrent()
{
    //handle overlay
}

void QGLWidget::updateOverlayGL()
{
    //handle overlay
}

void QGLWidget::setContext(QGLContext *context, const QGLContext* shareContext, bool deleteOldContext)
{
    Q_D(QGLWidget);
    if (context == 0) {
        qWarning("QGLWidget::setContext: Cannot set null context");
        return;
    }
    if (!context->deviceIsPixmap() && context->device() != this) {
        qWarning("QGLWidget::setContext: Context must refer to this widget");
        return;
    }

    if (d->glcx)
        d->glcx->doneCurrent();
    QGLContext* oldcx = d->glcx;
    d->glcx = context;

    bool createFailed = false;
    if (!d->glcx->isValid()) {
        // Create the QGLContext here, which in turn chooses the EGL config
        // and creates the EGL context:
        if (!d->glcx->create(shareContext ? shareContext : oldcx))
            createFailed = true;
    }
    if (createFailed) {
        if (deleteOldContext)
            delete oldcx;
        return;
    }


    d->eglSurfaceWindowId = winId(); // Remember the window id we created the surface for
}

void QGLWidgetPrivate::init(QGLContext *context, const QGLWidget* shareWidget)
{
    Q_Q(QGLWidget);

    initContext(context, shareWidget);

    if (q->isValid() && glcx->format().hasOverlay()) {
        //no overlay
        qWarning("QtOpenGL ES doesn't currently support overlays");
    }
}

void QGLWidgetPrivate::cleanupColormaps()
{
}

const QGLColormap & QGLWidget::colormap() const
{
    return d_func()->cmap;
}

void QGLWidget::setColormap(const QGLColormap &)
{
}

// Re-creates the EGL surface if the window ID has changed or if there isn't a surface
void QGLWidgetPrivate::recreateEglSurface()
{
    Q_Q(QGLWidget);

    Window currentId = q->winId();

    // If the window ID has changed since the surface was created, we need to delete the
    // old surface before re-creating a new one. Note: This should not be the case as the
    // surface should be deleted before the old window id.
    if (glcx->d_func()->eglSurface != EGL_NO_SURFACE && (currentId != eglSurfaceWindowId)) {
        qWarning("EGL surface for deleted window %x was not destroyed", eglSurfaceWindowId);
        glcx->d_func()->destroyEglSurfaceForDevice();
    }

    if (glcx->d_func()->eglSurface == EGL_NO_SURFACE) {
        glcx->d_func()->eglSurface = glcx->d_func()->eglContext->createSurface(q);
        eglSurfaceWindowId = currentId;
    }
}


QGLTexture *QGLContextPrivate::bindTextureFromNativePixmap(QPixmap *pixmap, const qint64 key,
                                                           QGLContext::BindOptions options)
{
    Q_Q(QGLContext);

    // The EGL texture_from_pixmap has no facility to invert the y coordinate
    if (!(options & QGLContext::CanFlipNativePixmapBindOption))
        return 0;


    static bool checkedForTFP = false;
    static bool haveTFP = false;
    static bool checkedForEglImageTFP = false;
    static bool haveEglImageTFP = false;


    if (!checkedForEglImageTFP) {
        checkedForEglImageTFP = true;

        // We need to be able to create an EGLImage from a native pixmap, which was split
        // into a seperate EGL extension, EGL_KHR_image_pixmap. It is possible to have
        // eglCreateImageKHR & eglDestroyImageKHR without support for pixmaps, so we must
        // check we have the EGLImage from pixmap functionality.
        if (QEgl::hasExtension("EGL_KHR_image") || QEgl::hasExtension("EGL_KHR_image_pixmap")) {

            // Being able to create an EGLImage from a native pixmap is also pretty useless
            // without the ability to bind that EGLImage as a texture, which is provided by
            // the GL_OES_EGL_image extension, which we try to resolve here:
            haveEglImageTFP = qt_resolve_eglimage_gl_extensions(q);

            if (haveEglImageTFP)
                qDebug("Found EGL_KHR_image_pixmap & GL_OES_EGL_image extensions (preferred method)!");
        }
    }

    if (!checkedForTFP) {
        // Check for texture_from_pixmap egl extension
        checkedForTFP = true;
        if (QEgl::hasExtension("EGL_NOKIA_texture_from_pixmap") ||
            QEgl::hasExtension("EGL_EXT_texture_from_pixmap"))
        {
            qDebug("Found texture_from_pixmap EGL extension!");
            haveTFP = true;
        }
    }

    if (!haveTFP && !haveEglImageTFP)
        return 0;


    QX11PixmapData *pixmapData = static_cast<QX11PixmapData*>(pixmap->data_ptr().data());
    Q_ASSERT(pixmapData->classId() == QPixmapData::X11Class);
    bool hasAlpha = pixmapData->hasAlphaChannel();
    bool pixmapHasValidSurface = false;
    bool textureIsBound = false;
    GLuint textureId;
    glGenTextures(1, &textureId);
    glBindTexture(GL_TEXTURE_2D, textureId);

    if (haveTFP && pixmapData->gl_surface &&
        hasAlpha == (pixmapData->flags & QX11PixmapData::GlSurfaceCreatedWithAlpha))
    {
        pixmapHasValidSurface = true;
    }

    // If we already have a valid EGL surface for the pixmap, we should use it
    if (pixmapHasValidSurface) {
        EGLBoolean success;
        success = eglBindTexImage(QEgl::display(), (EGLSurface)pixmapData->gl_surface, EGL_BACK_BUFFER);
        if (success == EGL_FALSE) {
            qWarning() << "eglBindTexImage() failed:" << QEgl::errorString();
            eglDestroySurface(QEgl::display(), (EGLSurface)pixmapData->gl_surface);
            pixmapData->gl_surface = (void*)EGL_NO_SURFACE;
        } else
            textureIsBound = true;
    }

    // If the pixmap doesn't already have a valid surface, try binding it via EGLImage
    // first, as going through EGLImage should be faster and better supported:
    if (!textureIsBound && haveEglImageTFP) {
        EGLImageKHR eglImage;

        EGLint attribs[] = {
            EGL_IMAGE_PRESERVED_KHR, EGL_TRUE,
            EGL_NONE
        };
        eglImage = QEgl::eglCreateImageKHR(QEgl::display(), EGL_NO_CONTEXT, EGL_NATIVE_PIXMAP_KHR,
                                     (EGLClientBuffer)QEgl::nativePixmap(pixmap), attribs);

        QGLContext* ctx = q;
        glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, eglImage);

        GLint err = glGetError();
        if (err == GL_NO_ERROR)
            textureIsBound = true;

        // Once the egl image is bound, the texture becomes a new sibling image and we can safely
        // destroy the EGLImage we created for the pixmap:
        if (eglImage != EGL_NO_IMAGE_KHR)
            QEgl::eglDestroyImageKHR(QEgl::display(), eglImage);
    }

    if (!textureIsBound && haveTFP) {
        // Check to see if the surface is still valid
        if (pixmapData->gl_surface &&
            hasAlpha != (pixmapData->flags & QX11PixmapData::GlSurfaceCreatedWithAlpha))
        {
            // Surface is invalid!
            destroyGlSurfaceForPixmap(pixmapData);
        }

        if (pixmapData->gl_surface == 0) {
            EGLConfig config = QEgl::defaultConfig(QInternal::Pixmap,
                                                   QEgl::OpenGL,
                                                   hasAlpha ? QEgl::Translucent : QEgl::NoOptions);

            pixmapData->gl_surface = (void*)QEgl::createSurface(pixmap, config);
            if (pixmapData->gl_surface == (void*)EGL_NO_SURFACE)
                return false;
        }

        EGLBoolean success;
        success = eglBindTexImage(QEgl::display(), (EGLSurface)pixmapData->gl_surface, EGL_BACK_BUFFER);
        if (success == EGL_FALSE) {
            qWarning() << "eglBindTexImage() failed:" << QEgl::errorString();
            eglDestroySurface(QEgl::display(), (EGLSurface)pixmapData->gl_surface);
            pixmapData->gl_surface = (void*)EGL_NO_SURFACE;
            haveTFP = false; // If TFP isn't working, disable it's use
        } else
            textureIsBound = true;
    }

    QGLTexture *texture = 0;

    if (textureIsBound) {
        texture = new QGLTexture(q, textureId, GL_TEXTURE_2D, options);
        pixmapData->flags |= QX11PixmapData::InvertedWhenBoundToTexture;

        // We assume the cost of bound pixmaps is zero
        QGLTextureCache::instance()->insert(q, key, texture, 0);

        glBindTexture(GL_TEXTURE_2D, textureId);
    } else
        glDeleteTextures(1, &textureId);

    return texture;
}


void QGLContextPrivate::destroyGlSurfaceForPixmap(QPixmapData* pmd)
{
    Q_ASSERT(pmd->classId() == QPixmapData::X11Class);
    QX11PixmapData *pixmapData = static_cast<QX11PixmapData*>(pmd);
    if (pixmapData->gl_surface) {
        EGLBoolean success;
        success = eglDestroySurface(QEgl::display(), (EGLSurface)pixmapData->gl_surface);
        if (success == EGL_FALSE) {
            qWarning() << "destroyGlSurfaceForPixmap() - Error deleting surface: "
                       << QEgl::errorString();
        }
        pixmapData->gl_surface = 0;
    }
}

void QGLContextPrivate::unbindPixmapFromTexture(QPixmapData* pmd)
{
    Q_ASSERT(pmd->classId() == QPixmapData::X11Class);
    QX11PixmapData *pixmapData = static_cast<QX11PixmapData*>(pmd);
    if (pixmapData->gl_surface) {
        EGLBoolean success;
        success = eglReleaseTexImage(QEgl::display(),
                                     (EGLSurface)pixmapData->gl_surface,
                                     EGL_BACK_BUFFER);
        if (success == EGL_FALSE) {
            qWarning() << "unbindPixmapFromTexture() - Unable to release bound texture: "
                       << QEgl::errorString();
        }
    }
}

QT_END_NAMESPACE
