/* ============================================================
 *
 * This file is a part of digiKam project
 * https://www.digikam.org
 *
 * Date        : 2023-05-15
 * Description : geolocation engine based on Marble.
 *               (c) 2007-2022 Marble Team
 *               https://invent.kde.org/education/marble/-/raw/master/data/credits_authors.html
 *
 * SPDX-FileCopyrightText: 2023-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 *
 * ============================================================ */

#include "StackedTile.h"

// Local includes

#include "TextureTile.h"
#include "digikam_debug.h"

namespace Marble
{

static const uint** jumpTableFromQImage32(const QImage& img)
{
    if (img.depth() != 48 && img.depth() != 32)
    {
        return nullptr;
    }

    const int  height = img.height();
    const int  bpl    = img.bytesPerLine() / 4;
    const uint*  data = reinterpret_cast<const QRgb*>(img.bits());
    const uint** jumpTable = new const uint*[height];

    for (int y = 0; y < height; ++y)
    {
        jumpTable[ y ] = data;
        data += bpl;
    }

    return jumpTable;
}

static const uchar** jumpTableFromQImage8(const QImage& img)
{
    if (img.depth() != 8 && img.depth() != 1)
    {
        return nullptr;
    }

    const int  height = img.height();
    const int  bpl    = img.bytesPerLine();
    const uchar*  data = img.bits();
    const uchar** jumpTable = new const uchar*[height];

    for (int y = 0; y < height; ++y)
    {
        jumpTable[ y ] = data;
        data += bpl;
    }

    return jumpTable;
}

// return channelwise average of colors c1 and c2
static inline uint colorMix50(uint c1, uint c2)
{
    return (((c1 ^ c2) & 0xfefefefeUL) >> 1) + (c1 & c2);
}

static inline uint colorMix75(uint c1, uint c2)
{
    return colorMix50(c1, colorMix50(c1, c2)); // 75% c1
}

static inline uint colorMix25(uint c1, uint c2)
{
    return colorMix50(colorMix50(c1, c2), c2);  // 25% c1
}

StackedTile::StackedTile(const TileId& id, const QImage& resultImage, QVector<QSharedPointer<TextureTile> > const& tiles) :
    Tile(id),
    m_resultImage(resultImage),
    m_depth(resultImage.depth()),
    m_isGrayscale(resultImage.isGrayscale()),
    m_tiles(tiles),
    jumpTable8(jumpTableFromQImage8(m_resultImage)),
    jumpTable32(jumpTableFromQImage32(m_resultImage)),
    m_byteCount(calcByteCount(resultImage, tiles)),
    m_isUsed(false)
{
    Q_ASSERT(!tiles.isEmpty());

    if (jumpTable32 == nullptr && jumpTable8 == nullptr)
    {
        qCWarning(DIGIKAM_MARBLE_LOG) << "Color depth" << m_depth << " is not supported.";
    }
}

StackedTile::~StackedTile()
{
    delete [] jumpTable32;
    delete [] jumpTable8;
}

uint StackedTile::pixel(int x, int y) const
{
    if (m_depth == 32 && !m_isGrayscale)
    {
        return (jumpTable32)[y][x];
    }

    if (m_depth == 8)
    {
        if (m_isGrayscale)
        {
            return (jumpTable8)[y][x];
        }

        else
        {
            return m_resultImage.color((jumpTable8)[y][x]);
        }
    }

    if (m_depth == 1 && !m_isGrayscale)
    {
        return m_resultImage.color((jumpTable8)[y][x / 8] >> 7);
    }

    return m_resultImage.pixel(x, y);
}

#define CHEAPHIGH
#ifdef CHEAPHIGH

uint StackedTile::pixelF(qreal x, qreal y, const QRgb& topLeftValue) const
{
    // Bilinear interpolation to determine the color of a subpixel
    int iX = (int)(x);
    int iY = (int)(y);

    qreal fY = 8 * (y - iY);

    // Interpolation in y-direction
    if ((iY + 1) < m_resultImage.height())
    {
        QRgb bottomLeftValue = pixel(iX, iY + 1);

        QRgb leftValue;

        if (fY < 1)
        {
            leftValue = topLeftValue;
        }

        else if (fY < 3)
        {
            leftValue = colorMix75(topLeftValue, bottomLeftValue);
        }

        else if (fY < 5)
        {
            leftValue = colorMix50(topLeftValue, bottomLeftValue);
        }

        else if (fY < 7)
        {
            leftValue = colorMix25(topLeftValue, bottomLeftValue);
        }

        else
        {
            leftValue = bottomLeftValue;
        }

        // Interpolation in x-direction
        if (iX + 1 < m_resultImage.width())
        {
            qreal fX = 8 * (x - iX);

            QRgb topRightValue    = pixel(iX + 1, iY);
            QRgb bottomRightValue = pixel(iX + 1, iY + 1);

            QRgb rightValue;

            if (fY < 1)
            {
                rightValue = topRightValue;
            }

            else if (fY < 3)
            {
                rightValue = colorMix75(topRightValue, bottomRightValue);
            }

            else if (fY < 5)
            {
                rightValue = colorMix50(topRightValue, bottomRightValue);
            }

            else if (fY < 7)
            {
                rightValue = colorMix25(topRightValue, bottomRightValue);
            }

            else
            {
                rightValue = bottomRightValue;
            }


            QRgb averageValue;

            if (fX < 1)
            {
                averageValue = leftValue;
            }

            else if (fX < 3)
            {
                averageValue = colorMix75(leftValue, rightValue);
            }

            else if (fX < 5)
            {
                averageValue = colorMix50(leftValue, rightValue);
            }

            else if (fX < 7)
            {
                averageValue = colorMix25(leftValue, rightValue);
            }

            else
            {
                averageValue = rightValue;
            }

            return averageValue;
        }

        else
        {
            return leftValue;
        }
    }

    else
    {
        // Interpolation in x-direction
        if (iX + 1 < m_resultImage.width())
        {

            qreal fX = 8 * (x - iX);

            if (fX == 0)
            {
                return topLeftValue;
            }

            QRgb topRightValue = pixel(iX + 1, iY);

            QRgb topValue;

            if (fX < 1)
            {
                topValue = topLeftValue;
            }

            else if (fX < 3)
            {
                topValue = colorMix75(topLeftValue, topRightValue);
            }

            else if (fX < 5)
            {
                topValue = colorMix50(topLeftValue, topRightValue);
            }

            else if (fX < 7)
            {
                topValue = colorMix25(topLeftValue, topRightValue);
            }

            else
            {
                topValue = topRightValue;
            }

            return topValue;
        }
    }

    return topLeftValue;
}

#else

uint StackedTile::pixelF(qreal x, qreal y, const QRgb& topLeftValue) const
{
    // Bilinear interpolation to determine the color of a subpixel

    int iX = (int)(x);
    int iY = (int)(y);

    qreal fY = y - iY;

    // Interpolation in y-direction
    if ((iY + 1) < m_resultImage.height())
    {

        QRgb bottomLeftValue  =  pixel(iX, iY + 1);
        // blending the color values of the top left and bottom left point
        qreal ml_red   = (1.0 - fY) * qRed(topLeftValue) + fY * qRed(bottomLeftValue);
        qreal ml_green = (1.0 - fY) * qGreen(topLeftValue) + fY * qGreen(bottomLeftValue);
        qreal ml_blue  = (1.0 - fY) * qBlue(topLeftValue) + fY * qBlue(bottomLeftValue);

        // Interpolation in x-direction
        if (iX + 1 < m_resultImage.width())
        {

            qreal fX = x - iX;

            QRgb topRightValue    =  pixel(iX + 1, iY);
            QRgb bottomRightValue =  pixel(iX + 1, iY + 1);

            // blending the color values of the top right and bottom right point
            qreal mr_red   = (1.0 - fY) * qRed(topRightValue) + fY * qRed(bottomRightValue);
            qreal mr_green = (1.0 - fY) * qGreen(topRightValue) + fY * qGreen(bottomRightValue);
            qreal mr_blue  = (1.0 - fY) * qBlue(topRightValue) + fY * qBlue(bottomRightValue);

            // blending the color values of the resulting middle left
            // and middle right points
            int mm_red   = (int)((1.0 - fX) * ml_red   + fX * mr_red);
            int mm_green = (int)((1.0 - fX) * ml_green + fX * mr_green);
            int mm_blue  = (int)((1.0 - fX) * ml_blue  + fX * mr_blue);

            return qRgb(mm_red, mm_green, mm_blue);
        }

        else
        {
            return qRgb(ml_red, ml_green, ml_blue);
        }
    }

    else
    {
        // Interpolation in x-direction
        if (iX + 1 < m_resultImage.width())
        {

            qreal fX = x - iX;

            if (fX == 0.0)
            {
                return topLeftValue;
            }

            QRgb topRightValue    =  pixel(iX + 1, iY);
            // blending the color values of the top left and top right point
            int tm_red   = (int)((1.0 - fX) * qRed(topLeftValue) + fX * qRed(topRightValue));
            int tm_green = (int)((1.0 - fX) * qGreen(topLeftValue) + fX * qGreen(topRightValue));
            int tm_blue  = (int)((1.0 - fX) * qBlue(topLeftValue) + fX * qBlue(topRightValue));

            return qRgb(tm_red, tm_green, tm_blue);
        }
    }

    return topLeftValue;
}

#endif

int StackedTile::calcByteCount(const QImage& resultImage, const QVector<QSharedPointer<TextureTile> >& tiles)
{
    int byteCount = resultImage.sizeInBytes();

    QVector<QSharedPointer<TextureTile> >::const_iterator pos = tiles.constBegin();
    QVector<QSharedPointer<TextureTile> >::const_iterator const end = tiles.constEnd();

    for (; pos != end; ++pos)
    {
        byteCount += (*pos)->byteCount();
    }

    return byteCount;
}

void StackedTile::setUsed(bool used)
{
    m_isUsed = used;
}

bool StackedTile::used() const
{
    return m_isUsed;
}

uint StackedTile::pixelF(qreal x, qreal y) const
{
    int iX = (int)(x);
    int iY = (int)(y);

    QRgb topLeftValue  =  pixel(iX, iY);

    return pixelF(x, y, topLeftValue);
}

int StackedTile::depth() const
{
    return m_depth;
}

int StackedTile::byteCount() const
{
    return m_byteCount;
}

QVector<QSharedPointer<TextureTile> > StackedTile::tiles() const
{
    return m_tiles;
}

QImage const* StackedTile::resultImage() const
{
    return &m_resultImage;
}

} // namespace Marble
