/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** 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 Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or 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.GPL2 and 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-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qdirectfbblitter.h"
#include "qdirectfbconvenience.h"

#include <QtGui/private/qpixmap_blitter_p.h>

#include <QDebug>
#include <QFile>

#include <directfb.h>

QT_BEGIN_NAMESPACE

static QBlittable::Capabilities dfb_blitter_capabilities()
{
    return QBlittable::Capabilities(QBlittable::SolidRectCapability
                                    |QBlittable::SourcePixmapCapability
                                    |QBlittable::SourceOverPixmapCapability
                                    |QBlittable::SourceOverScaledPixmapCapability
                                    |QBlittable::AlphaFillRectCapability
                                    |QBlittable::OpacityPixmapCapability
                                    |QBlittable::DrawScaledCachedGlyphsCapability
                                    );
}

QDirectFbBlitter::QDirectFbBlitter(const QSize &rect, IDirectFBSurface *surface)
    : QBlittable(rect, dfb_blitter_capabilities())
    , m_surface(surface)
    , m_debugPaint(false)
{
    m_surface->AddRef(m_surface.data());

    DFBSurfaceCapabilities surfaceCaps;
    m_surface->GetCapabilities(m_surface.data(), &surfaceCaps);
    m_premult = (surfaceCaps & DSCAPS_PREMULTIPLIED);
    if (qEnvironmentVariableIntValue("QT_DIRECTFB_BLITTER_DEBUGPAINT"))
        m_debugPaint = true;
}

QDirectFbBlitter::QDirectFbBlitter(const QSize &rect, bool alpha)
    : QBlittable(rect, dfb_blitter_capabilities())
    , m_premult(false)
    , m_debugPaint(false)
{
    DFBSurfaceDescription surfaceDesc;
    memset(&surfaceDesc,0,sizeof(DFBSurfaceDescription));
    surfaceDesc.width = rect.width();
    surfaceDesc.height = rect.height();

    // force alpha format to get AlphaFillRectCapability and ExtendedPixmapCapability support
    alpha = true;

    if (alpha) {
        m_premult = true;
        surfaceDesc.caps = DSCAPS_PREMULTIPLIED;
        surfaceDesc.pixelformat = QDirectFbBlitter::alphaPixmapFormat();
        surfaceDesc.flags = DFBSurfaceDescriptionFlags(DSDESC_WIDTH | DSDESC_HEIGHT | DSDESC_CAPS | DSDESC_PIXELFORMAT);
    } else {
        surfaceDesc.flags = DFBSurfaceDescriptionFlags(DSDESC_WIDTH | DSDESC_HEIGHT | DSDESC_PIXELFORMAT);
        surfaceDesc.pixelformat = QDirectFbBlitter::pixmapFormat();
    }

    if (qEnvironmentVariableIntValue("QT_DIRECTFB_BLITTER_DEBUGPAINT"))
        m_debugPaint = true;

    IDirectFB *dfb = QDirectFbConvenience::dfbInterface();
    dfb->CreateSurface(dfb , &surfaceDesc, m_surface.outPtr());
    m_surface->Clear(m_surface.data(), 0, 0, 0, 0);
}

QDirectFbBlitter::~QDirectFbBlitter()
{
    unlock();
}

DFBSurfacePixelFormat QDirectFbBlitter::alphaPixmapFormat()
{
    return DSPF_ARGB;
}

DFBSurfacePixelFormat QDirectFbBlitter::pixmapFormat()
{
    return DSPF_RGB32;
}

DFBSurfacePixelFormat QDirectFbBlitter::selectPixmapFormat(bool withAlpha)
{
    return withAlpha ? alphaPixmapFormat() : pixmapFormat();
}

void QDirectFbBlitter::fillRect(const QRectF &rect, const QColor &color)
{
    alphaFillRect(rect, color, QPainter::CompositionMode_Source);
}

void QDirectFbBlitter::drawPixmap(const QRectF &rect, const QPixmap &pixmap, const QRectF &srcRect)
{
    drawPixmapOpacity(rect, pixmap, srcRect, QPainter::CompositionMode_SourceOver, 1.0);
}

void QDirectFbBlitter::alphaFillRect(const QRectF &rect, const QColor &color, QPainter::CompositionMode cmode)
{
    int x, y, w, h;
    DFBResult result;

    // check parameters
    rect.toRect().getRect(&x, &y ,&w, &h);
    if ((w <= 0) || (h <= 0)) return;

    if ((cmode == QPainter::CompositionMode_Source) || (color.alpha() == 255)) {
        // CompositionMode_Source case or CompositionMode_SourceOver with opaque color

        m_surface->SetDrawingFlags(m_surface.data(),
            DFBSurfaceDrawingFlags(m_premult ? (DSDRAW_NOFX | DSDRAW_SRC_PREMULTIPLY) : DSDRAW_NOFX));
        m_surface->SetPorterDuff(m_surface.data(), DSPD_SRC);

    } else {
        // CompositionMode_SourceOver case

        // check if operation is useless
        if (color.alpha() == 0)
            return;

        m_surface->SetDrawingFlags(m_surface.data(),
            DFBSurfaceDrawingFlags(m_premult ? (DSDRAW_BLEND | DSDRAW_SRC_PREMULTIPLY) : DSDRAW_BLEND));
        m_surface->SetPorterDuff(m_surface.data(), DSPD_SRC_OVER);
    }

    // set color
    m_surface->SetColor(m_surface.data(), color.red(), color.green(), color.blue(), color.alpha());

    // perform fill
    result = m_surface->FillRectangle(m_surface.data(), x, y, w, h);
    if (result != DFB_OK)
        DirectFBError("QDirectFBBlitter::alphaFillRect()", result);
    if (m_debugPaint)
        drawDebugRect(QRect(x, y, w, h), QColor(Qt::blue));
}

void QDirectFbBlitter::drawPixmapOpacity(const QRectF &rect, const QPixmap &pixmap, const QRectF &subrect, QPainter::CompositionMode cmode, qreal opacity)
{
    QRect sQRect = subrect.toRect();
    QRect dQRect = rect.toRect();
    DFBRectangle sRect(sQRect.x(), sQRect.y(), sQRect.width(), sQRect.height());
    DFBRectangle dRect(dQRect.x(), dQRect.y(), dQRect.width(), dQRect.height());
    DFBResult result;

    // skip if dst too small
    if ((dRect.w <= 0) || (dRect.h <= 0)) return;

    // correct roundings if needed
    if (sRect.w <= 0) sRect.w = 1;
    if (sRect.h <= 0) sRect.h = 1;

    QDirectFbBlitterPlatformPixmap *blitPm = static_cast<QDirectFbBlitterPlatformPixmap *>(pixmap.handle());
    QDirectFbBlitter *dfbBlitter = static_cast<QDirectFbBlitter *>(blitPm->blittable());
    dfbBlitter->unlock();

    IDirectFBSurface *s = dfbBlitter->m_surface.data();

    DFBSurfaceBlittingFlags blittingFlags = DFBSurfaceBlittingFlags(DSBLIT_BLEND_ALPHACHANNEL);
    DFBSurfacePorterDuffRule porterDuff = (cmode == QPainter::CompositionMode_SourceOver) ? DSPD_SRC_OVER : DSPD_SRC;

    if (opacity != 1.0)
    {
        blittingFlags = DFBSurfaceBlittingFlags(blittingFlags | DSBLIT_BLEND_COLORALPHA | (m_premult ? DSBLIT_SRC_PREMULTCOLOR : 0));
        m_surface->SetColor(m_surface.data(), 0xff, 0xff, 0xff, (u8) (opacity * 255.0));
    }

    m_surface->SetBlittingFlags(m_surface.data(), DFBSurfaceBlittingFlags(blittingFlags));
    m_surface->SetPorterDuff(m_surface.data(), porterDuff);

    if (cmode == QPainter::CompositionMode_SourceOver)
        m_surface->SetDstBlendFunction(m_surface.data(), DSBF_INVSRCALPHA);

    if ((sRect.w == dRect.w) && (sRect.h == dRect.h)) {
        result = m_surface->Blit(m_surface.data(), s, &sRect, dRect.x, dRect.y);
        if (result != DFB_OK)
            DirectFBError("QDirectFBBlitter::drawPixmapOpacity()", result);
        if (m_debugPaint)
            drawDebugRect(QRect(dRect.x, dRect.y, sRect.w, sRect.h), QColor(Qt::green));
    } else {
        result = m_surface->StretchBlit(m_surface.data(), s, &sRect, &dRect);
        if (result != DFB_OK)
            DirectFBError("QDirectFBBlitter::drawPixmapOpacity()", result);
        if (m_debugPaint)
            drawDebugRect(QRect(dRect.x, dRect.y, dRect.w, dRect.h), QColor(Qt::red));
    }
}

bool QDirectFbBlitter::drawCachedGlyphs(const QPaintEngineState *state, QFontEngine::GlyphFormat glyphFormat, int numGlyphs, const glyph_t *glyphs, const QFixedPoint *positions, QFontEngine *fontEngine)
{
    void *cacheKey = QDirectFbConvenience::dfbInterface();

    QDirectFbTextureGlyphCache *cache =
            static_cast<QDirectFbTextureGlyphCache *>(fontEngine->glyphCache(cacheKey, glyphFormat, state->transform()));
    if (!cache) {
        cache = new QDirectFbTextureGlyphCache(glyphFormat, state->transform());
        fontEngine->setGlyphCache(cacheKey, cache);
    }

    cache->populate(fontEngine, numGlyphs, glyphs, positions);
    cache->fillInPendingGlyphs();

    if (cache->image().width() == 0 || cache->image().height() == 0)
        return false;

    const int margin = fontEngine->glyphMargin(glyphFormat);

    QVarLengthArray<DFBRectangle, 64> sourceRects(numGlyphs);
    QVarLengthArray<DFBPoint, 64> destPoints(numGlyphs);
    int nGlyphs = 0;

    for (int i=0; i<numGlyphs; ++i) {

        QFixed subPixelPosition = fontEngine->subPixelPositionForX(positions[i].x);
        QTextureGlyphCache::GlyphAndSubPixelPosition glyph(glyphs[i], subPixelPosition);
        const QTextureGlyphCache::Coord &c = cache->coords[glyph];
        if (c.isNull())
            continue;

        int x = qFloor(positions[i].x) + c.baseLineX - margin;
        int y = qRound(positions[i].y) - c.baseLineY - margin;

        // printf("drawing [%d %d %d %d] baseline [%d %d], glyph: %d, to: %d %d, pos: %d %d\n",
        //        c.x, c.y,
        //        c.w, c.h,
        //        c.baseLineX, c.baseLineY,
        //        glyphs[i],
        //        x, y,
        //        positions[i].x.toInt(), positions[i].y.toInt());

        sourceRects[nGlyphs].x = c.x;
        sourceRects[nGlyphs].y = c.y;
        sourceRects[nGlyphs].w = c.w;
        sourceRects[nGlyphs].h = c.h;
        destPoints[nGlyphs].x = x;
        destPoints[nGlyphs].y = y;
        ++nGlyphs;
    }

    const QColor color = state->pen().color();
    m_surface->SetColor(m_surface.data(), color.red(), color.green(), color.blue(), color.alpha());

    m_surface->SetSrcBlendFunction(m_surface.data(), DSBF_SRCALPHA);
    m_surface->SetDstBlendFunction(m_surface.data(), DSBF_INVSRCALPHA);

    int flags = DSBLIT_BLEND_ALPHACHANNEL | DSBLIT_COLORIZE;
    if (color.alpha() != 0xff)
        flags |= DSBLIT_BLEND_COLORALPHA;
    m_surface->SetBlittingFlags(m_surface.data(), DFBSurfaceBlittingFlags(flags));

    const QRasterPaintEngineState *rs = static_cast<const QRasterPaintEngineState*>(state);
    if (rs->clip && rs->clip->enabled) {
        Q_ASSERT(rs->clip->hasRectClip);
        DFBRegion dfbClip;
        dfbClip.x1 = rs->clip->clipRect.x();
        dfbClip.y1 = rs->clip->clipRect.y();
        dfbClip.x2 = rs->clip->clipRect.right();
        dfbClip.y2 = rs->clip->clipRect.bottom();
        m_surface->SetClip(m_surface.data(), &dfbClip);
    }

    m_surface->BatchBlit(m_surface.data(), cache->sourceSurface(), sourceRects.constData(), destPoints.constData(), nGlyphs);

    if (m_debugPaint) {
        for (int i = 0; i < nGlyphs; ++i) {
            drawDebugRect(QRect(destPoints[i].x, destPoints[i].y, sourceRects[i].w, sourceRects[i].h), QColor(Qt::yellow));
        }
    }

    if (rs->clip && rs->clip->enabled)
        m_surface->SetClip(m_surface.data(), 0);
    return true;
}

QImage *QDirectFbBlitter::doLock()
{
    Q_ASSERT(m_surface);
    Q_ASSERT(size().isValid());

    void *mem;
    int bpl;
    const DFBResult result = m_surface->Lock(m_surface.data(), DFBSurfaceLockFlags(DSLF_WRITE|DSLF_READ), static_cast<void**>(&mem), &bpl);
    if (result == DFB_OK) {
        DFBSurfacePixelFormat dfbFormat;
        DFBSurfaceCapabilities dfbCaps;
        m_surface->GetPixelFormat(m_surface.data(), &dfbFormat);
        m_surface->GetCapabilities(m_surface.data(), &dfbCaps);
        QImage::Format format = QDirectFbConvenience::imageFormatFromSurfaceFormat(dfbFormat, dfbCaps);
        int w, h;
        m_surface->GetSize(m_surface.data(), &w, &h);
        m_image = QImage(static_cast<uchar *>(mem),w,h,bpl,format);
    } else {
        DirectFBError("Failed to lock image", result);
    }

    return &m_image;
}

bool QDirectFbBlitterPlatformPixmap::fromDataBufferDescription(const DFBDataBufferDescription &dataBufferDescription)
{
    DFBResult result;
    IDirectFB *dfb = QDirectFbConvenience::dfbInterface();

    // Create a data buffer
    QDirectFBPointer<IDirectFBDataBuffer> dataBuffer;
    result = dfb->CreateDataBuffer(dfb, &dataBufferDescription, dataBuffer.outPtr());
    if (result != DFB_OK) {
        DirectFBError(QDFB_PRETTY, result);
        return false;
    }

    // Create the image provider
    QDirectFBPointer<IDirectFBImageProvider> provider;
    result = dataBuffer->CreateImageProvider(dataBuffer.data(), provider.outPtr());
    if (result != DFB_OK) {
        DirectFBError(QDFB_PRETTY, result);
        return false;
    }

    // Extract image information
    DFBImageDescription imageDescription;
    result = provider->GetImageDescription(provider.data(), &imageDescription);
    if (result != DFB_OK) {
        DirectFBError(QDFB_PRETTY, result);
        return false;
    }

    // Can we handle this directlu?
    if (imageDescription.caps & DICAPS_COLORKEY)
        return false;

    DFBSurfaceDescription surfaceDescription;
    result = provider->GetSurfaceDescription(provider.data(), &surfaceDescription);
    if (result != DFB_OK) {
        DirectFBError(QDFB_PRETTY, result);
        return false;
    }

    m_alpha = imageDescription.caps & DICAPS_ALPHACHANNEL;
    resize(surfaceDescription.width, surfaceDescription.height);
    // TODO: FIXME; update d


    result = provider->RenderTo(provider.data(), dfbBlitter()->dfbSurface(), 0);
    if (result != DFB_OK) {
        DirectFBError(QDFB_PRETTY, result);
        return false;
    }

    return true;
}

bool QDirectFbBlitterPlatformPixmap::fromFile(const QString &filename, const char *format,
                                              Qt::ImageConversionFlags flags)
{
    // If we can't find the file, pass it on to the base class as it is
    // trying harder by appending various extensions to the path.
    if (!QFile::exists(filename))
        return QBlittablePlatformPixmap::fromFile(filename, format, flags);

    // Stop if there is a requirement for colors
    if (flags != Qt::AutoColor)
        return QBlittablePlatformPixmap::fromFile(filename, format, flags);

    // Deal with resources
    if (filename.startsWith(QLatin1Char(':')))
        return QBlittablePlatformPixmap::fromFile(filename, format, flags);

    // Try to use directfb to load it.
    DFBDataBufferDescription description;
    description.flags = DBDESC_FILE;
    const QByteArray fileNameData = filename.toLocal8Bit();
    description.file = fileNameData.constData();
    if (fromDataBufferDescription(description))
        return true;

    // Fallback
    return QBlittablePlatformPixmap::fromFile(filename, format, flags);
}

void QDirectFbBlitter::doUnlock()
{
    m_surface->Unlock(m_surface.data());
}

void QDirectFbBlitter::drawDebugRect(const QRect &rect, const QColor &color)
{
    int x, y, w, h;
    DFBResult result;

    // check parameters
    rect.getRect(&x, &y ,&w, &h);
    if ((w <= 0) || (h <= 0)) return;

    m_surface->SetDrawingFlags(m_surface.data(),
    DFBSurfaceDrawingFlags(m_premult ? (DSDRAW_BLEND | DSDRAW_SRC_PREMULTIPLY) : DSDRAW_BLEND));
    m_surface->SetPorterDuff(m_surface.data(), DSPD_SRC_OVER);

    // set color
    m_surface->SetColor(m_surface.data(), color.red(), color.green(), color.blue(), 120);

    result = m_surface->DrawLine(m_surface.data(), x, y, x + w-1, y);
    if (result != DFB_OK)
        DirectFBError("QDirectFBBlitter::drawDebugRect()", result);
    result = m_surface->DrawLine(m_surface.data(), x + w-1, y, x + w-1, y + h-1);
    if (result != DFB_OK)
        DirectFBError("QDirectFBBlitter::drawDebugRect()", result);
    result = m_surface->DrawLine(m_surface.data(), x + w-1, y + h-1, x, y + h-1);
    if (result != DFB_OK)
        DirectFBError("QDirectFBBlitter::drawDebugRect()", result);
    result = m_surface->DrawLine(m_surface.data(), x, y + h-1, x, y);
    if (result != DFB_OK)
        DirectFBError("QDirectFBBlitter::drawDebugRect()", result);

    m_surface->SetColor(m_surface.data(), color.red(), color.green(), color.blue(), 10);
    result = m_surface->FillRectangle(m_surface.data(), x, y, w, h);
    if (result != DFB_OK)
        DirectFBError("QDirectFBBlitter::drawDebugRect()", result);
}

void QDirectFbTextureGlyphCache::resizeTextureData(int width, int height)
{
    m_surface.reset();;
    QImageTextureGlyphCache::resizeTextureData(width, height);
}

IDirectFBSurface *QDirectFbTextureGlyphCache::sourceSurface()
{
    if (m_surface.isNull()) {
        const QImage &source = image();
        DFBSurfaceDescription desc;
        memset(&desc, 0, sizeof(desc));
        desc.flags = DFBSurfaceDescriptionFlags(DSDESC_WIDTH | DSDESC_HEIGHT | DSDESC_PIXELFORMAT | DSDESC_PREALLOCATED | DSDESC_CAPS);
        desc.width = source.width();
        desc.height = source.height();
        desc.caps = DSCAPS_SYSTEMONLY;

        switch (source.format()) {
        case QImage::Format_Mono:
            desc.pixelformat = DSPF_A1;
            break;
        case QImage::Format_Alpha8:
            desc.pixelformat = DSPF_A8;
            break;
        default:
            qFatal("QDirectFBTextureGlyphCache: Unsupported source texture image format.");
            break;
        }

        desc.preallocated[0].data = const_cast<void*>(static_cast<const void*>(source.bits()));
        desc.preallocated[0].pitch = source.bytesPerLine();
        desc.preallocated[1].data = 0;
        desc.preallocated[1].pitch = 0;

        IDirectFB *dfb = QDirectFbConvenience::dfbInterface();
        dfb->CreateSurface(dfb , &desc, m_surface.outPtr());
    }
    return m_surface.data();
}

QT_END_NAMESPACE
