// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include "qsgopenvghelpers.h"
#include <cmath>

QT_BEGIN_NAMESPACE

namespace QSGOpenVGHelpers {

VGPath qPainterPathToVGPath(const QPainterPath &path)
{
    int count = path.elementCount();

    VGPath vgpath = vgCreatePath(VG_PATH_FORMAT_STANDARD,
                                 VG_PATH_DATATYPE_F,
                                 1.0f,        // scale
                                 0.0f,        // bias
                                 count + 1,   // segmentCapacityHint
                                 count * 2,   // coordCapacityHint
                                 VG_PATH_CAPABILITY_ALL);

    if (count == 0)
        return vgpath;

    QVector<VGfloat> coords;
    QVector<VGubyte> segments;

    int curvePos = 0;
    QPointF temp;

    // Keep track of the start and end of each sub-path.  QPainterPath
    // does not have an "implicit close" flag like QVectorPath does.
    // We therefore have to detect closed paths by looking for a LineTo
    // element that connects back to the initial MoveTo element.
    qreal startx = 0.0;
    qreal starty = 0.0;
    qreal endx = 0.0;
    qreal endy = 0.0;
    bool haveStart = false;
    bool haveEnd = false;

    for (int i = 0; i < count; ++i) {
        const QPainterPath::Element element = path.elementAt(i);
        switch (element.type) {

        case QPainterPath::MoveToElement:
        {
            if (haveStart && haveEnd && startx == endx && starty == endy) {
                // Implicitly close the previous sub-path.
                segments.append(VG_CLOSE_PATH);
            }
            temp = QPointF(element.x, element.y);
            startx = temp.x();
            starty = temp.y();
            coords.append(startx);
            coords.append(starty);
            haveStart = true;
            haveEnd = false;
            segments.append(VG_MOVE_TO_ABS);
        }
            break;

        case QPainterPath::LineToElement:
        {
            temp = QPointF(element.x, element.y);
            endx = temp.x();
            endy = temp.y();
            coords.append(endx);
            coords.append(endy);
            haveEnd = true;
            segments.append(VG_LINE_TO_ABS);
        }
            break;

        case QPainterPath::CurveToElement:
        {
            temp = QPointF(element.x, element.y);
            coords.append(temp.x());
            coords.append(temp.y());
            haveEnd = false;
            curvePos = 2;
        }
            break;

        case QPainterPath::CurveToDataElement:
        {
            temp = QPointF(element.x, element.y);
            coords.append(temp.x());
            coords.append(temp.y());
            haveEnd = false;
            curvePos += 2;
            if (curvePos == 6) {
                curvePos = 0;
                segments.append(VG_CUBIC_TO_ABS);
            }
        }
            break;

        }
    }

    if (haveStart && haveEnd && startx == endx && starty == endy) {
        // Implicitly close the last sub-path.
        segments.append(VG_CLOSE_PATH);
    }

    vgAppendPathData(vgpath, segments.count(),
                     segments.constData(), coords.constData());

    return vgpath;
}


void qDrawTiled(VGImage image, const QSize imageSize, const QRectF &targetRect, const QPointF offset, float scaleX, float scaleY) {

    //Check for valid image size and targetRect
    if (imageSize.width() <= 0 || imageSize.height() <= 0)
        return;
    if (targetRect.width() <= 0 || targetRect.height() <= 0)
        return;

    // This logic is mostly from the Qt Raster PaintEngine's qt_draw_tile
    qreal drawH;
    qreal drawW;
    qreal xPos;
    qreal xOff;
    qreal yPos = targetRect.y();
    qreal yOff;

    if (offset.y() < 0)
        yOff = imageSize.height() - qRound(-offset.y()) % imageSize.height();
    else
        yOff = qRound(offset.y()) % imageSize.height();


    // Save the current image transform matrix
    vgSeti(VG_MATRIX_MODE, VG_MATRIX_IMAGE_USER_TO_SURFACE);
    QVector<float> originalMatrix(9);
    vgGetMatrix(originalMatrix.data());

    while (!qFuzzyCompare(yPos, targetRect.y() + targetRect.height()) &&
           yPos < targetRect.y() + targetRect.height()) {
        drawH = imageSize.height() - yOff; // Cropping first row
        if (yPos + drawH * scaleY > targetRect.y() + targetRect.height()) { // Cropping last row
            // Check that values aren't equal
            if (!qFuzzyCompare((float)(yPos + drawH * scaleY), (float)(targetRect.y() + targetRect.height())))
                drawH = targetRect.y() + targetRect.height() - yPos;
        }
        xPos = targetRect.x();
        if (offset.x() < 0)
            xOff = imageSize.width() - qRound(-offset.x()) % imageSize.width();
        else
            xOff = qRound(offset.x()) % imageSize.width();

        while (!qFuzzyCompare(xPos, targetRect.x() + targetRect.width()) &&
               xPos < targetRect.x() + targetRect.width()) {
            drawW = imageSize.width() - xOff; // Cropping first column
            if (xPos + drawW * scaleX > targetRect.x() + targetRect.width()) {
                // Check that values aren't equal
                if (!qFuzzyCompare((float)(xPos + drawW * scaleX), (float)(targetRect.x() + targetRect.width())))
                    drawW = targetRect.x() + targetRect.width() - xPos;
            }
            if (round(drawW) > 0 && round(drawH) > 0) { // Can't source image less than 1 width or height
                //Draw here
                VGImage childRectImage = vgChildImage(image, xOff, yOff, round(drawW), round(drawH));
                vgTranslate(xPos, yPos);
                vgScale(scaleX, scaleY);
                vgDrawImage(childRectImage);
                vgDestroyImage(childRectImage);
                vgLoadMatrix(originalMatrix.constData());
            }
            if ( drawW > 0)
                xPos += drawW * scaleX;
            xOff = 0;
        }
        if ( drawH > 0)
            yPos += drawH * scaleY;
        yOff = 0;

    }
}

void qDrawBorderImage(VGImage image, const QSizeF &textureSize, const QRectF &targetRect, const QRectF &innerTargetRect, const QRectF &subSourceRect)
{
    // Create normalized margins
    QMarginsF margins(qMax(innerTargetRect.left() - targetRect.left(), qreal(0.0)),
                      qMax(innerTargetRect.top() - targetRect.top(), qreal(0.0)),
                      qMax(targetRect.right() - innerTargetRect.right(), qreal(0.0)),
                      qMax(targetRect.bottom() - innerTargetRect.bottom(), qreal(0.0)));

    QRectF sourceRect(0, 0, textureSize.width(), textureSize.height());

    // Create all the subRects
    QRectF topLeftSourceRect(sourceRect.topLeft(), QSizeF(margins.left(), margins.top()));
    QRectF topRightSourceRect(sourceRect.width() - margins.right(), sourceRect.top(), margins.right(), margins.top());
    QRectF bottomLeftSourceRect(sourceRect.left(), sourceRect.height() - margins.bottom(), margins.left(), margins.bottom());
    QRectF bottomRightSourceRect(sourceRect.width() - margins.right(), sourceRect.height() - margins.bottom(), margins.right(), margins.bottom());

    QRectF topSourceRect(margins.left(), 0.0, sourceRect.width() - (margins.right() + margins.left()), margins.top());
    QRectF topTargetRect(margins.left(), 0.0, innerTargetRect.width(), margins.top());
    QRectF bottomSourceRect(margins.left(), sourceRect.height() - margins.bottom(), sourceRect.width() - (margins.right() + margins.left()), margins.bottom());
    QRectF bottomTargetRect(margins.left(), targetRect.height() - margins.bottom(), innerTargetRect.width(), margins.bottom());
    QRectF leftSourceRect(0.0, margins.top(), margins.left(), sourceRect.height() - (margins.bottom() + margins.top()));
    QRectF leftTargetRect(0.0, margins.top(), margins.left(), innerTargetRect.height());
    QRectF rightSourceRect(sourceRect.width() - margins.right(), margins.top(), margins.right(), sourceRect.height() - (margins.bottom() + margins.top()));
    QRectF rightTargetRect(targetRect.width() - margins.right(), margins.top(), margins.right(), innerTargetRect.height());

    QRectF centerSourceRect(margins.left(), margins.top(), sourceRect.width() - (margins.right() + margins.left()), sourceRect.height() - (margins.top() + margins.bottom()));

    // Draw the 9 different sections
    // (1) Top Left (unscaled)
    qDrawSubImage(image,
                 topLeftSourceRect,
                 targetRect.topLeft());

    // (3) Top Right (unscaled)
    qDrawSubImage(image,
                 topRightSourceRect,
                 QPointF(targetRect.width() - margins.right(), 0.0));

    // (7) Bottom Left (unscaled)
    qDrawSubImage(image,
                 bottomLeftSourceRect,
                 QPointF(targetRect.left(), targetRect.height() - margins.bottom()));

    // (9) Bottom Right (unscaled)
    qDrawSubImage(image,
                 bottomRightSourceRect,
                 QPointF(targetRect.width() - margins.right(), targetRect.height() - margins.bottom()));

    double scaledWidth = 1.0;
    double scaledHeight = 1.0;

    // (2) Top (scaled via horizontalTileRule)
    VGImage topImage = vgChildImage(image, topSourceRect.x(), topSourceRect.y(), topSourceRect.width(), topSourceRect.height());
    scaledWidth = (topTargetRect.width() / subSourceRect.width()) / topSourceRect.width();

    QSGOpenVGHelpers::qDrawTiled(topImage, topSourceRect.size().toSize(), topTargetRect, QPoint(0.0, 0.0), scaledWidth, 1);

    vgDestroyImage(topImage);

    // (8) Bottom (scaled via horizontalTileRule)
    VGImage bottomImage = vgChildImage(image, bottomSourceRect.x(), bottomSourceRect.y(), bottomSourceRect.width(), bottomSourceRect.height());
    scaledWidth = (bottomTargetRect.width() / subSourceRect.width()) / bottomSourceRect.width();

    QSGOpenVGHelpers::qDrawTiled(bottomImage, bottomSourceRect.size().toSize(), bottomTargetRect, QPoint(0.0, 0.0), scaledWidth, 1);

    vgDestroyImage(bottomImage);

    // (4) Left (scaled via verticalTileRule)
    VGImage leftImage = vgChildImage(image, leftSourceRect.x(), leftSourceRect.y(), leftSourceRect.width(), leftSourceRect.height());
    scaledHeight = (leftTargetRect.height() / subSourceRect.height()) / leftSourceRect.height();
    QSGOpenVGHelpers::qDrawTiled(leftImage, leftSourceRect.size().toSize(), leftTargetRect, QPointF(0.0, 0.0), 1, scaledHeight);

    vgDestroyImage(leftImage);

    // (6) Right (scaled via verticalTileRule)
    VGImage rightImage = vgChildImage(image, rightSourceRect.x(), rightSourceRect.y(), rightSourceRect.width(), rightSourceRect.height());
    scaledHeight = (rightTargetRect.height() / subSourceRect.height()) / rightSourceRect.height();

    QSGOpenVGHelpers::qDrawTiled(rightImage, rightSourceRect.size().toSize(), rightTargetRect, QPointF(0, 0), 1, scaledHeight);

    vgDestroyImage(rightImage);

    // (5) Center (saled via verticalTileRule and horizontalTileRule)
    VGImage centerImage = vgChildImage(image, centerSourceRect.x(), centerSourceRect.y(), centerSourceRect.width(), centerSourceRect.height());

    scaledWidth = (innerTargetRect.width() / subSourceRect.width()) / centerSourceRect.width();
    scaledHeight = (innerTargetRect.height() / subSourceRect.height()) / centerSourceRect.height();

    QSGOpenVGHelpers::qDrawTiled(centerImage, centerSourceRect.size().toSize(), innerTargetRect, QPointF(0, 0), scaledWidth, scaledHeight);

    vgDestroyImage(centerImage);
}

void qDrawSubImage(VGImage image, const QRectF &sourceRect, const QPointF &destOffset)
{
    // Check for valid source size
    if (sourceRect.width() <= 0 || sourceRect.height() <= 0)
        return;

    // Save the current image transform matrix
    vgSeti(VG_MATRIX_MODE, VG_MATRIX_IMAGE_USER_TO_SURFACE);
    QVector<float> originalMatrix(9);
    vgGetMatrix(originalMatrix.data());

    // Get the child Image
    VGImage childRectImage = vgChildImage(image, sourceRect.x(), sourceRect.y(), sourceRect.width(), sourceRect.height());
    vgTranslate(destOffset.x(), destOffset.y());
    vgDrawImage(childRectImage);
    vgDestroyImage(childRectImage);

    // Pop Matrix
    vgLoadMatrix(originalMatrix.constData());
}

const QVector<VGfloat> qColorToVGColor(const QColor &color, float opacity)
{
    QVector<VGfloat> vgColor(4);
    float r, g, b, a;
    color.getRgbF(&r, &g, &b, &a);
    vgColor[0] = r;
    vgColor[1] = g;
    vgColor[2] = b;
    vgColor[3] = a * opacity;
    return vgColor;
}

VGImageFormat qImageFormatToVGImageFormat(QImage::Format format)
{
    VGImageFormat vgFormat;

    switch (format) {
    case QImage::Format_Mono:
    case QImage::Format_MonoLSB:
        vgFormat = VG_BW_1;
        break;
    case QImage::Format_RGB32:
        vgFormat = VG_sXRGB_8888;
        break;
    case QImage::Format_ARGB32:
        vgFormat = VG_sARGB_8888;
        break;
    case QImage::Format_ARGB32_Premultiplied:
        vgFormat = VG_sARGB_8888_PRE;
        break;
    case QImage::Format_RGB16:
        vgFormat = VG_sRGB_565;
        break;
    case QImage::Format_RGBX8888:
        vgFormat = VG_sRGBX_8888;
        break;
    case QImage::Format_RGBA8888:
        vgFormat = VG_sRGBA_8888;
        break;
    case QImage::Format_RGBA8888_Premultiplied:
        vgFormat = VG_sRGBA_8888_PRE;
        break;
    case QImage::Format_Alpha8:
        vgFormat = VG_A_8;
        break;
    case QImage::Format_Grayscale8:
        vgFormat = VG_sL_8;
        break;
    default:
        //Invalid
        vgFormat = (VGImageFormat)-1;
        break;
    }

    return vgFormat;
}

QImage::Format qVGImageFormatToQImageFormat(VGImageFormat format)
{
    QImage::Format qImageFormat;

    switch (format) {
    case VG_BW_1:
        qImageFormat = QImage::Format_Mono;
        break;
    case VG_sXRGB_8888:
        qImageFormat = QImage::Format_RGB32;
        break;
    case VG_sARGB_8888:
        qImageFormat = QImage::Format_ARGB32;
        break;
    case VG_sARGB_8888_PRE:
        qImageFormat = QImage::Format_ARGB32_Premultiplied;
        break;
    case VG_sRGB_565:
        qImageFormat = QImage::Format_RGB16;
        break;
    case VG_sRGBX_8888:
        qImageFormat = QImage::Format_RGBX8888;
        break;
    case VG_sRGBA_8888:
        qImageFormat = QImage::Format_RGBA8888;
        break;
    case VG_sRGBA_8888_PRE:
        qImageFormat = QImage::Format_RGBA8888_Premultiplied;
        break;
    case VG_A_8:
        qImageFormat = QImage::Format_Alpha8;
        break;
    case VG_sL_8:
        qImageFormat = QImage::Format_Grayscale8;
        break;
    default:
        qImageFormat = QImage::Format_ARGB32;
        break;
    }

    return qImageFormat;
}

} // end namespace QSGOpenVGHelpers

QT_END_NAMESPACE
