#include "apisurface.h"
#include "thumbnail.h"

#include <algorithm>
#include <sstream>
#include <memory>

#include <QDebug>
#include <QSysInfo>

#include "image.hpp"


ApiSurface::ApiSurface()
{
}

QSize ApiSurface::size() const
{
    return m_size;
}

void ApiSurface::setSize(const QSize &size)
{
    m_size = size;
}

struct ByteArrayBuf : public std::streambuf
{
    ByteArrayBuf(const QByteArray & a)
    {
        setg((char *)a.data(), (char *)a.data(), (char *)a.data() + a.size());
    }
};

QImage ApiSurface::calculateThumbnail(bool opaque, bool alpha) const
{
    return m_data.isEmpty() ? QImage{} : calculateThumbnail(m_data, opaque, alpha);
}

QImage ApiSurface::calculateThumbnail(const QByteArray &data, bool opaque,
                                      bool alpha) const
{
    /*
     * We need to do the conversion to create the thumbnail
     */
    std::unique_ptr<image::Image> image{imageFromData(data)};
    Q_ASSERT(image);
    QImage img = qimageFromRawImage(image.get(), 0.0f, 1.0f, opaque, alpha);
    return thumbnail(img);
}

void ApiSurface::setData(const QByteArray &data)
{
    m_data = data;
}

QByteArray ApiSurface::data() const
{
    return m_data;
}

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

void ApiSurface::setDepth(int depth)
{
    m_depth = depth;
}

QString ApiSurface::formatName() const
{
    return m_formatName;
}

void ApiSurface::setFormatName(const QString &str)
{
    m_formatName = str;
}


ApiTexture::ApiTexture()
    : ApiSurface()
{
}

QString ApiTexture::label() const
{
    return m_label;
}

void ApiTexture::setLabel(const QString &str)
{
    m_label = str;
}

ApiFramebuffer::ApiFramebuffer()
    : ApiSurface()
{
}

QString ApiFramebuffer::type() const
{
    return m_type;
}

void ApiFramebuffer::setType(const QString &str)
{
    m_type = str;
}

image::Image *
ApiSurface::imageFromData(const QByteArray &dataArray)
{
    image::Image *image;

    /*
     * Detect the PNG vs PFM images.
     */
    const char pngSignature[] = {(char)0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0};
    if (dataArray.startsWith(pngSignature)) {
        ByteArrayBuf buf(dataArray);
        std::istream istr(&buf);
        image = image::readPNG(istr);
    } else {
        image = image::readPNM(dataArray.data(), dataArray.size());
    }

    return image;
}


static inline unsigned char clamp(int x)
{
    if (x <= 0) {
        return 0;
    }
    if (x > 255) {
        return 255;
    }
    return (unsigned char) x;
}

static inline unsigned char clamp(float x)
{
    if (x <= 0.0f) {
        return 0;
    }
    if (x > 255.0f) {
        return 255;
    }
    return (unsigned char) (x + 0.5f);
}


QImage
ApiSurface::qimageFromRawImage(const image::Image *image,
                               float lowerValue,
                               float upperValue,
                               bool opaque,
                               bool alpha)
{
    QImage img;
    int width = std::min(image->width, 32767U);
    int height = std::min(image->height, 32767U);

    img = QImage(width, height, QImage::Format_ARGB32);

    int offset = - lowerValue * 255;
    int scale = 256 / (upperValue - lowerValue);

    float offset_f = - lowerValue;
    float scale_f = 255.0f / (upperValue - lowerValue);

    int aMask = (opaque || alpha) ? 0xff : 0;

    const unsigned char *srcRow = image->start();
    for (int y = 0; y < height; ++y) {
        QRgb *dst = (QRgb *)img.scanLine(y);

        if (image->channelType == image::TYPE_UNORM8) {
            const unsigned char *src = srcRow;
            for (int x = 0; x < width; ++x) {
                unsigned char rgba[4] = {0, 0, 0, 0xff};
                for (int c = 0; c < image->channels; ++c) {
                    rgba[c] = clamp(((*src++ + offset) * scale) >> 8);
                }
                if (image->channels == 1) {
                    // Use gray-scale instead of red
                    rgba[1] = rgba[0];
                    rgba[2] = rgba[0];
                }
                if (alpha) {
                    rgba[2] = rgba[1] = rgba[0] = rgba[3];
                }
                rgba[3] |= aMask;
                dst[x] = qRgba(rgba[0], rgba[1], rgba[2], rgba[3]);
            }
        } else {
            const float *src = (const float *)srcRow;
            for (int x = 0; x < width; ++x) {
                unsigned char rgba[4] = {0, 0, 0, 0xff};
                for (int c = 0; c < image->channels; ++c) {
                    rgba[c] = clamp((*src++ + offset_f)*scale_f);
                }
                if (image->channels == 1) {
                    // Use gray-scale instead of red
                    rgba[1] = rgba[0];
                    rgba[2] = rgba[0];
                }
                if (alpha) {
                    rgba[2] = rgba[1] = rgba[0] = rgba[3];
                }
                rgba[3] |= aMask;
                dst[x] = qRgba(rgba[0], rgba[1], rgba[2], rgba[3]);
            }
        }

        srcRow += image->stride();
    }

    return img;
}
