/****************************************************************************
**
** 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 plugins 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 "qdirectfbpixmap.h"

#ifndef QT_NO_QWS_DIRECTFB

#include "qdirectfbscreen.h"
#include "qdirectfbpaintengine.h"

#include <QtGui/qbitmap.h>
#include <QtCore/qfile.h>
#include <directfb.h>


QT_BEGIN_NAMESPACE

static int global_ser_no = 0;

QDirectFBPixmapData::QDirectFBPixmapData(QDirectFBScreen *screen, PixelType pixelType)
    : QPixmapData(pixelType, DirectFBClass), QDirectFBPaintDevice(screen),
      alpha(false)
{
    setSerialNumber(0);
}

QDirectFBPixmapData::~QDirectFBPixmapData()
{
}

void QDirectFBPixmapData::resize(int width, int height)
{
    if (width <= 0 || height <= 0) {
        invalidate();
        return;
    }

    imageFormat = screen->pixelFormat();
    dfbSurface = screen->createDFBSurface(QSize(width, height),
                                          imageFormat,
                                          QDirectFBScreen::TrackSurface);
    d = QDirectFBScreen::depth(imageFormat);
    alpha = false;
    if (!dfbSurface) {
        invalidate();
        qWarning("QDirectFBPixmapData::resize(): Unable to allocate surface");
        return;
    }

    w = width;
    h = height;
    is_null = (w <= 0 || h <= 0);
    setSerialNumber(++global_ser_no);
}

// mostly duplicated from qimage.cpp (QImageData::checkForAlphaPixels)
static bool checkForAlphaPixels(const QImage &img)
{
    const uchar *bits = img.bits();
    const int bytes_per_line = img.bytesPerLine();
    const uchar *end_bits = bits + bytes_per_line;
    const int width = img.width();
    const int height = img.height();
    switch (img.format()) {
    case QImage::Format_Indexed8:
        return img.hasAlphaChannel();
    case QImage::Format_ARGB32:
    case QImage::Format_ARGB32_Premultiplied:
        for (int y=0; y<height; ++y) {
            for (int x=0; x<width; ++x) {
                if ((((uint *)bits)[x] & 0xff000000) != 0xff000000) {
                    return true;
                }
            }
            bits += bytes_per_line;
        }
        break;

    case QImage::Format_ARGB8555_Premultiplied:
    case QImage::Format_ARGB8565_Premultiplied:
        for (int y=0; y<height; ++y) {
            while (bits < end_bits) {
                if (bits[0] != 0) {
                    return true;
                }
                bits += 3;
            }
            bits = end_bits;
            end_bits += bytes_per_line;
        }
        break;

    case QImage::Format_ARGB6666_Premultiplied:
        for (int y=0; y<height; ++y) {
            while (bits < end_bits) {
                if ((bits[0] & 0xfc) != 0) {
                    return true;
                }
                bits += 3;
            }
            bits = end_bits;
            end_bits += bytes_per_line;
        }
        break;

    case QImage::Format_ARGB4444_Premultiplied:
        for (int y=0; y<height; ++y) {
            while (bits < end_bits) {
                if ((bits[0] & 0xf0) != 0) {
                    return true;
                }
                bits += 2;
            }
            bits = end_bits;
            end_bits += bytes_per_line;
        }
        break;

    default:
        break;
    }

    return false;
}

bool QDirectFBPixmapData::hasAlphaChannel(const QImage &img)
{
#ifndef QT_NO_DIRECTFB_OPAQUE_DETECTION
    return checkForAlphaPixels(img);
#else
    return img.hasAlphaChannel();
#endif
}

#ifdef QT_DIRECTFB_IMAGEPROVIDER
bool QDirectFBPixmapData::fromFile(const QString &filename, const char *format,
                                   Qt::ImageConversionFlags flags)
{
    if (flags == Qt::AutoColor) {
        if (filename.startsWith(QLatin1Char(':'))) { // resource
            QFile file(filename);
            if (!file.open(QIODevice::ReadOnly))
                return false;
            const QByteArray data = file.readAll();
            file.close();
            return fromData(reinterpret_cast<const uchar*>(data.constData()), data.size(), format, flags);
        } else {
            DFBDataBufferDescription description;
            description.flags = DBDESC_FILE;
            const QByteArray fileNameData = filename.toLocal8Bit();
            description.file = fileNameData.constData();
            if (fromDataBufferDescription(description)) {
                return true;
            }
            // fall back to Qt
        }
    }
    return QPixmapData::fromFile(filename, format, flags);
}

bool QDirectFBPixmapData::fromData(const uchar *buffer, uint len, const char *format,
                                   Qt::ImageConversionFlags flags)
{
    if (flags == Qt::AutoColor) {
        DFBDataBufferDescription description;
        description.flags = DBDESC_MEMORY;
        description.memory.data = buffer;
        description.memory.length = len;
        if (fromDataBufferDescription(description))
            return true;
        // fall back to Qt
    }
    return QPixmapData::fromData(buffer, len, format, flags);
}

template <typename T> struct QDirectFBInterfaceCleanupHandler
{
    static void cleanup(T *t) { if (t) t->Release(t); }
};

template <typename T>
class QDirectFBPointer : public QScopedPointer<T, QDirectFBInterfaceCleanupHandler<T> >
{
public:
    QDirectFBPointer(T *t = 0)
        : QScopedPointer<T, QDirectFBInterfaceCleanupHandler<T> >(t)
    {}
};

bool QDirectFBPixmapData::fromDataBufferDescription(const DFBDataBufferDescription &dataBufferDescription)
{
    IDirectFB *dfb = screen->dfb();
    Q_ASSERT(dfb);
    DFBResult result = DFB_OK;
    IDirectFBDataBuffer *dataBufferPtr;
    if ((result = dfb->CreateDataBuffer(dfb, &dataBufferDescription, &dataBufferPtr)) != DFB_OK) {
        DirectFBError("QDirectFBPixmapData::fromDataBufferDescription()", result);
        return false;
    }
    QDirectFBPointer<IDirectFBDataBuffer> dataBuffer(dataBufferPtr);

    IDirectFBImageProvider *providerPtr;
    if ((result = dataBuffer->CreateImageProvider(dataBuffer.data(), &providerPtr)) != DFB_OK) {
        DirectFBError("QDirectFBPixmapData::fromDataBufferDescription(): Can't create image provider", result);
        return false;
    }
    QDirectFBPointer<IDirectFBImageProvider> provider(providerPtr);

    DFBSurfaceDescription surfaceDescription;
    if ((result = provider->GetSurfaceDescription(provider.data(), &surfaceDescription)) != DFB_OK) {
        DirectFBError("QDirectFBPixmapData::fromDataBufferDescription(): Can't get surface description", result);
        return false;
    }

    DFBImageDescription imageDescription;
    result = provider->GetImageDescription(provider.data(), &imageDescription);
    if (result != DFB_OK) {
        DirectFBError("QDirectFBPixmapData::fromSurfaceDescription(): Can't get image description", result);
        return false;
    }

    alpha = imageDescription.caps & (DICAPS_ALPHACHANNEL|DICAPS_COLORKEY);
    imageFormat = alpha ? screen->alphaPixmapFormat() : screen->pixelFormat();

    dfbSurface = screen->createDFBSurface(QSize(surfaceDescription.width, surfaceDescription.height),
                                          imageFormat, QDirectFBScreen::TrackSurface);

    result = provider->RenderTo(provider.data(), dfbSurface, 0);
    if (result != DFB_OK) {
        DirectFBError("QDirectFBPixmapData::fromSurfaceDescription(): Can't render to surface", result);
        return false;
    }

    w = surfaceDescription.width;
    h = surfaceDescription.height;
    is_null = (w <= 0 || h <= 0);
    d = QDirectFBScreen::depth(imageFormat);
    setSerialNumber(++global_ser_no);

#if defined QT_DIRECTFB_IMAGEPROVIDER_KEEPALIVE
    screen->setDirectFBImageProvider(providerPtr);
    provider.take();
#endif

    return true;
}

#endif

void QDirectFBPixmapData::fromImage(const QImage &img,
                                    Qt::ImageConversionFlags flags)
{
    if (img.depth() == 1 || img.format() == QImage::Format_RGB32) {
        fromImage(img.convertToFormat(screen->alphaPixmapFormat()), flags);
        return;
    }

    if (img.hasAlphaChannel()
#ifndef QT_NO_DIRECTFB_OPAQUE_DETECTION
        && (flags & Qt::NoOpaqueDetection || QDirectFBPixmapData::hasAlphaChannel(img))
#endif
        ) {
        alpha = true;
        imageFormat = screen->alphaPixmapFormat();
    } else {
        alpha = false;
        imageFormat = screen->pixelFormat();
    }
    QImage image;
    if (flags != Qt::AutoColor) {
        image = img.convertToFormat(imageFormat, flags);
        flags = Qt::AutoColor;
    } else if (img.format() == QImage::Format_RGB32) {
        image = img.convertToFormat(imageFormat, flags);
    } else {
        image = img;
    }

    IDirectFBSurface *imageSurface = screen->createDFBSurface(image, image.format(), QDirectFBScreen::DontTrackSurface);
    if (!imageSurface) {
        qWarning("QDirectFBPixmapData::fromImage()");
        invalidate();
        return;
    }

    dfbSurface = screen->createDFBSurface(image.size(), imageFormat, QDirectFBScreen::TrackSurface);
    if (!dfbSurface) {
        qWarning("QDirectFBPixmapData::fromImage()");
        invalidate();
        return;
    }

    if (image.hasAlphaChannel()) {
        dfbSurface->Clear(dfbSurface, 0, 0, 0, 0);
        dfbSurface->SetBlittingFlags(dfbSurface, DSBLIT_BLEND_ALPHACHANNEL);
    } else {
        dfbSurface->SetBlittingFlags(dfbSurface, DSBLIT_NOFX);
    }

    dfbSurface->Blit(dfbSurface, imageSurface, 0, 0, 0);
    imageSurface->Release(imageSurface);

    w = image.width();
    h = image.height();
    is_null = (w <= 0 || h <= 0);
    d = QDirectFBScreen::depth(imageFormat);
    setSerialNumber(++global_ser_no);
#ifdef QT_NO_DIRECTFB_OPAQUE_DETECTION
    Q_UNUSED(flags);
#endif
}

void QDirectFBPixmapData::copy(const QPixmapData *data, const QRect &rect)
{
    if (data->classId() != DirectFBClass) {
        QPixmapData::copy(data, rect);
        return;
    }

    const QDirectFBPixmapData *otherData = static_cast<const QDirectFBPixmapData*>(data);
#ifdef QT_NO_DIRECTFB_SUBSURFACE
    if (otherData->lockFlags()) {
        const_cast<QDirectFBPixmapData*>(otherData)->unlockSurface();
    }
#endif
    IDirectFBSurface *src = otherData->directFBSurface();
    alpha = data->hasAlphaChannel();
    imageFormat = (alpha
                   ? QDirectFBScreen::instance()->alphaPixmapFormat()
                   : QDirectFBScreen::instance()->pixelFormat());


    dfbSurface = screen->createDFBSurface(rect.size(), imageFormat,
                                          QDirectFBScreen::TrackSurface);
    if (!dfbSurface) {
        qWarning("QDirectFBPixmapData::copy()");
        invalidate();
        return;
    }

    if (alpha) {
        dfbSurface->Clear(dfbSurface, 0, 0, 0, 0);
        dfbSurface->SetBlittingFlags(dfbSurface, DSBLIT_BLEND_ALPHACHANNEL);
    } else {
        dfbSurface->SetBlittingFlags(dfbSurface, DSBLIT_NOFX);
    }
    const DFBRectangle blitRect = { rect.x(), rect.y(),
                                    rect.width(), rect.height() };
    w = rect.width();
    h = rect.height();
    d = otherData->d;
    is_null = (w <= 0 || h <= 0);
    unlockSurface();
    DFBResult result = dfbSurface->Blit(dfbSurface, src, &blitRect, 0, 0);
#if (Q_DIRECTFB_VERSION >= 0x010000)
    dfbSurface->ReleaseSource(dfbSurface);
#endif
    if (result != DFB_OK) {
        DirectFBError("QDirectFBPixmapData::copy()", result);
        invalidate();
        return;
    }

    setSerialNumber(++global_ser_no);
}

static inline bool isOpaqueFormat(QImage::Format format)
{
    switch (format) {
    case QImage::Format_RGB32:
    case QImage::Format_RGB16:
    case QImage::Format_RGB666:
    case QImage::Format_RGB555:
    case QImage::Format_RGB888:
    case QImage::Format_RGB444:
        return true;
    default:
        break;
    }
    return false;
}

void QDirectFBPixmapData::fill(const QColor &color)
{
    if (!serialNumber())
        return;

    Q_ASSERT(dfbSurface);

    alpha = (color.alpha() < 255);

    if (alpha && isOpaqueFormat(imageFormat)) {
        QSize size;
        dfbSurface->GetSize(dfbSurface, &size.rwidth(), &size.rheight());
        screen->releaseDFBSurface(dfbSurface);
        imageFormat = screen->alphaPixmapFormat();
        d = QDirectFBScreen::depth(imageFormat);
        dfbSurface = screen->createDFBSurface(size, screen->alphaPixmapFormat(), QDirectFBScreen::TrackSurface);
        setSerialNumber(++global_ser_no);
        if (!dfbSurface) {
            qWarning("QDirectFBPixmapData::fill()");
            invalidate();
            return;
        }
    }

    dfbSurface->Clear(dfbSurface, color.red(), color.green(), color.blue(), color.alpha());
}

QPixmap QDirectFBPixmapData::transformed(const QTransform &transform,
                                         Qt::TransformationMode mode) const
{
    QDirectFBPixmapData *that = const_cast<QDirectFBPixmapData*>(this);
#ifdef QT_NO_DIRECTFB_SUBSURFACE
    if (lockFlags())
        that->unlockSurface();
#endif

    if (!dfbSurface || transform.type() != QTransform::TxScale
        || mode != Qt::FastTransformation)
    {
        const QImage *image = that->buffer();
        Q_ASSERT(image);
        const QImage transformed = image->transformed(transform, mode);
        QDirectFBPixmapData *data = new QDirectFBPixmapData(screen, QPixmapData::PixmapType);
        data->fromImage(transformed, Qt::AutoColor);
        return QPixmap(data);
    }

    const QSize size = transform.mapRect(QRect(0, 0, w, h)).size();
    if (size.isEmpty())
        return QPixmap();

    QDirectFBPixmapData *data = new QDirectFBPixmapData(screen, QPixmapData::PixmapType);
    data->setSerialNumber(++global_ser_no);
    DFBSurfaceBlittingFlags flags = DSBLIT_NOFX;
    data->alpha = alpha;
    if (alpha) {
        flags = DSBLIT_BLEND_ALPHACHANNEL;
    }
    data->dfbSurface = screen->createDFBSurface(size,
                                                imageFormat,
                                                QDirectFBScreen::TrackSurface);
    if (flags & DSBLIT_BLEND_ALPHACHANNEL) {
        data->dfbSurface->Clear(data->dfbSurface, 0, 0, 0, 0);
    }
    data->dfbSurface->SetBlittingFlags(data->dfbSurface, flags);

    const DFBRectangle destRect = { 0, 0, size.width(), size.height() };
    data->dfbSurface->StretchBlit(data->dfbSurface, dfbSurface, 0, &destRect);
    data->w = size.width();
    data->h = size.height();
    data->is_null = (data->w <= 0 || data->h <= 0);

#if (Q_DIRECTFB_VERSION >= 0x010000)
    data->dfbSurface->ReleaseSource(data->dfbSurface);
#endif
    return QPixmap(data);
}

QImage QDirectFBPixmapData::toImage() const
{
    if (!dfbSurface)
        return QImage();

#if 0
    // In later versions of DirectFB one can set a flag to tell
    // DirectFB not to move the surface to videomemory. When that
    // happens we can use this (hopefully faster) codepath
#ifndef QT_NO_DIRECTFB_PREALLOCATED
    QImage ret(w, h, QDirectFBScreen::getImageFormat(dfbSurface));
    if (IDirectFBSurface *imgSurface = screen->createDFBSurface(ret, QDirectFBScreen::DontTrackSurface)) {
        if (hasAlphaChannel()) {
            imgSurface->SetBlittingFlags(imgSurface, DSBLIT_BLEND_ALPHACHANNEL);
            imgSurface->Clear(imgSurface, 0, 0, 0, 0);
        } else {
            imgSurface->SetBlittingFlags(imgSurface, DSBLIT_NOFX);
        }
        imgSurface->Blit(imgSurface, dfbSurface, 0, 0, 0);
#if (Q_DIRECTFB_VERSION >= 0x010000)
        imgSurface->ReleaseSource(imgSurface);
#endif
        imgSurface->Release(imgSurface);
        return ret;
    }
#endif
#endif

    QDirectFBPixmapData *that = const_cast<QDirectFBPixmapData*>(this);
    const QImage *img = that->buffer();
    return img->copy();
}

/* This is QPixmapData::paintEngine(), not QPaintDevice::paintEngine() */

QPaintEngine *QDirectFBPixmapData::paintEngine() const
{
    if (!engine) {
        // QDirectFBPixmapData is also a QCustomRasterPaintDevice, so pass
        // that to the paint engine:
        QDirectFBPixmapData *that = const_cast<QDirectFBPixmapData*>(this);
        that->engine = new QDirectFBPaintEngine(that);
    }
    return engine;
}

QImage *QDirectFBPixmapData::buffer()
{
    if (!lockFlgs) {
        lockSurface(DSLF_READ|DSLF_WRITE);
    }
    Q_ASSERT(lockFlgs);
    Q_ASSERT(!lockedImage.isNull());
    return &lockedImage;
}


bool QDirectFBPixmapData::scroll(int dx, int dy, const QRect &rect)
{
    if (!dfbSurface) {
        return false;
    }
    unlockSurface();
    DFBResult result = dfbSurface->SetBlittingFlags(dfbSurface, DSBLIT_NOFX);
    if (result != DFB_OK) {
        DirectFBError("QDirectFBPixmapData::scroll", result);
        return false;
    }
    result = dfbSurface->SetPorterDuff(dfbSurface, DSPD_NONE);
    if (result != DFB_OK) {
        DirectFBError("QDirectFBPixmapData::scroll", result);
        return false;
    }

    const DFBRectangle source = { rect.x(), rect.y(), rect.width(), rect.height() };
    result = dfbSurface->Blit(dfbSurface, dfbSurface, &source, source.x + dx, source.y + dy);
    if (result != DFB_OK) {
        DirectFBError("QDirectFBPixmapData::scroll", result);
        return false;
    }

    return true;
}

void QDirectFBPixmapData::invalidate()
{
    if (dfbSurface) {
        screen->releaseDFBSurface(dfbSurface);
        dfbSurface = 0;
    }
    setSerialNumber(0);
    alpha = false;
    d = w = h = 0;
    is_null = true;
    imageFormat = QImage::Format_Invalid;
}

QT_END_NAMESPACE

#endif // QT_NO_QWS_DIRECTFB
