#include "rawimage.h"
#include <cstring>
#include <algorithm>
#ifndef NO_QT
#include <lcms2.h>
#include <QDebug>
#include <QElapsedTimer>
#include <QFloat16>
#include <QColorSpace>
using F16 = qfloat16;
#else
#define __STDC_WANT_IEC_60559_TYPES_EXT__
#include <float.h>
#ifdef FLT16_MAX
using F16 = _Float16;
#else
#include "tfloat16.h"
using F16 = TFloat16;// this is only for MXE
#endif // FLT16_MAX

#endif // NO_QT

int THUMB_SIZE = 128;
int THUMB_SIZE_BORDER = 138;
int THUMB_SIZE_BORDER_Y = 158;
double SATURATION = 0.95;
bool QUALITY_RESIZE = true;
extern bool OpenGLES;

#ifdef __SSE2__
template<typename T, int ch>
void fromPlanarSSE(const void *in, void *out, size_t count);
#endif

size_t RawImage::typeSize(RawImage::DataType type)
{
    switch(type)
    {
    case RawImage::UINT8:
        return 1;
    case RawImage::UINT16:
    case RawImage::FLOAT16:
        return 2;
    case RawImage::UINT32:
    case RawImage::FLOAT32:
        return 4;
    case RawImage::FLOAT64:
        return 8;
    default: return 1;
    }
}

void RawImage::allocate(uint32_t w, uint32_t h, uint32_t ch, DataType type)
{
    m_width = w;
    m_height = h;
    m_channels = ch;
    m_ch = ch > 1 ? 4 : ch;
    m_origType = m_type = type;
    m_pixels = std::make_unique<PixelType[]>((size_t)m_width * m_height * m_ch * typeSize(type));
}

RawImage::RawImage()
{
}

RawImage::RawImage(uint32_t w, uint32_t h, uint32_t ch, DataType type)
{
    allocate(w, h, ch, type);
}

RawImage::RawImage(const RawImage &d)
{
    allocate(d.m_width, d.m_height, d.m_channels, d.m_type);
    std::memcpy(m_pixels.get(), d.m_pixels.get(), (size_t)m_width * m_height * m_ch * typeSize(m_type));
    m_stats = d.m_stats;
}

RawImage::RawImage(RawImage &&d)
{
    m_pixels = std::move(d.m_pixels);
    m_original = std::move(d.m_original);
    m_width = d.m_width;
    m_height = d.m_height;
    m_channels = d.m_channels;
    m_ch = d.m_ch;
    m_type = d.m_type;
    m_origType = d.m_origType;
    m_stats = d.m_stats;
    m_thumbAspect = d.m_thumbAspect;
}

#ifndef NO_QT
RawImage::RawImage(const QImage &img)
{
    qDebug() << img;
    setICCProfile(img.colorSpace().iccProfile());
    if(img.format() == QImage::Format_RGBX8888)
    {
        allocate(img.width(), img.height(), 3, UINT8);
        for(int i=0; i<img.height(); i++)
            std::memcpy(data(i), img.scanLine(i), img.width()*4);
    }
    else if(img.format() == QImage::Format_RGBA8888)
    {
        allocate(img.width(), img.height(), 4, UINT8);
        for(int i=0; i<img.height(); i++)
            std::memcpy(data(i), img.scanLine(i), img.width()*4);
    }
    else if(img.format() == QImage::Format_RGBX64)
    {
        allocate(img.width(), img.height(), 3, UINT16);
        for(int i=0; i<img.height(); i++)
            std::memcpy(data(i), img.scanLine(i), img.width()*8);
    }
    else if(img.format() == QImage::Format_RGBA64)
    {
        allocate(img.width(), img.height(), 4, UINT16);
        for(int i=0; i<img.height(); i++)
            std::memcpy(data(i), img.scanLine(i), img.width()*8);
    }
    else if(img.format() == QImage::Format_Grayscale8)
    {
        allocate(img.width(), img.height(), 1, UINT8);
        for(int i=0; i<img.height(); i++)
            std::memcpy(data(i), img.scanLine(i), img.width());
    }
    else if(img.format() == QImage::Format_Grayscale16)
    {
        allocate(img.width(), img.height(), 1, UINT16);
        for(int i=0; i<img.height(); i++)
            std::memcpy(data(i), img.scanLine(i), img.width()*2);
    }
    else if(img.format() == QImage::Format_RGB32 || img.format() == QImage::Format_ARGB32)
    {
        allocate(img.width(), img.height(), 4, UINT8);
        for(int i=0; i<img.height(); i++)
        {
            uint32_t *src = (uint32_t*)img.scanLine(i);
            uint32_t *dst = (uint32_t*)data(i);
            for(int o=0; o<img.width(); o++)
            {
                uint32_t p = src[o];
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
                dst[o] = (p & 0xff000000) | (p >> 16 & 0xff) | (p & 0xff00) | (p << 16 & 0xff0000);
#else
                dst[o] = (p >> 24) | (p << 8 & 0xffffff00);
#endif
            }
        }
    }
    else
    {
        QImage tmp = img.convertToFormat(QImage::Format_RGBA8888);
        allocate(img.width(), img.height(), 4, UINT8);
        for(int i=0; i<tmp.height(); i++)
            std::memcpy(data(i), tmp.scanLine(i), tmp.width()*4);
    }
    m_stats.m_stats = false;
}
#endif

const RawImage::Stats& RawImage::imageStats() const
{
    return m_stats;
}

template<typename T, typename U, int ch>
void calcStats(const T *data, size_t n, size_t w, RawImage::Stats &stats)
{
    U sum[4] = {0};
    U sumSq[4] = {0};
    T min[4] = {std::numeric_limits<T>::max(), std::numeric_limits<T>::max(), std::numeric_limits<T>::max(), std::numeric_limits<T>::max()};
    T max[4] = {std::numeric_limits<T>::min(), std::numeric_limits<T>::min(), std::numeric_limits<T>::min(), std::numeric_limits<T>::min()};
    uint32_t histSize = 65536;
    if constexpr(std::is_same<T, uint8_t>::value)histSize = 256;
    std::vector<uint32_t> histogram[4];
    histogram[0].resize(histSize); histogram[1].resize(histSize); histogram[2].resize(histSize); histogram[3].resize(histSize);

    T sat = SATURATION * std::numeric_limits<T>::max();
    if constexpr(!std::numeric_limits<T>::is_integer)sat = SATURATION;
    uint32_t saturated[4] = {0};

    auto statsFunc = [&](T d, int x)
    {
        sum[x]   += d;
        sumSq[x] += (U)d * d;
        min[x] = std::min(min[x], d);
        max[x] = std::max(max[x], d);
        uint16_t idx;
        if constexpr(std::is_same<T, uint32_t>::value)idx = d >> 16;
        if constexpr(std::is_same<T, uint8_t>::value || std::is_same<T, uint16_t>::value)idx = d;
        if constexpr(!std::numeric_limits<T>::is_integer)idx = std::clamp((T)d * histSize, (T)0.0, (T)65535.0);
        histogram[x][idx]++;
        if(d > sat)saturated[x]++;
    };

    auto findMedian = [histSize](std::vector<uint32_t> &histogram, size_t n) -> size_t
    {
        size_t histSum = 0;
        for(size_t o=1; o < histSize; o++)
        {
            histSum += histogram[o];
            if(histSum >= n/2)
                return o;
        }
        return 0;
    };

    size_t na[4] = {n, n, n, n};
    if constexpr(ch == 1)
    {
        na[1] /= 4;
        na[2] /= 2;
        na[3] /= 4;
    }
    for(size_t i = 0; i < n; i++)
    {
        statsFunc(data[i*ch], 0);
        if constexpr(ch >= 3)
        {
            statsFunc(data[i*ch + 1], 1);
            statsFunc(data[i*ch + 2], 2);
        }
    }
    if constexpr(ch == 1)
    {
        size_t h = (n / w) & (SIZE_MAX-1);
        w &= (SIZE_MAX-1);
        for(size_t y=0; y<h; y+=2)
        {
            for(size_t x=0; x<w; x+=2)
            {
               statsFunc(data[y*w+x], 1);
               statsFunc(data[y*w+x+1], 2);
               statsFunc(data[(y+1)*w+x], 2);
               statsFunc(data[(y+1)*w+x+1], 3);
            }
        }
    }

    if constexpr(std::is_floating_point_v<T>)
    {
        T mmin = *std::min_element(min, min + 4);
        T mmax = *std::max_element(max, max + 4);

        T a = 1.0 / (mmax - mmin);
        T b = -mmin / (mmax - mmin);

        auto histFunc = [&](T d, int x)
        {
            uint16_t idx = std::clamp((T)(d * a + b) * histSize, (T)0.0, (T)65535.0);
            histogram[x][idx]++;
        };

        // calculate histogram again
        if(mmin < 0.0 || mmax > 1.0)
        {
            for(int i=0; i<4; i++)
            {
                histogram[i].clear();
                histogram[i].resize(histSize, 0);
            }

            for(size_t i = 0; i < n; i++)
            {
                histFunc(data[i*ch], 0);
                if constexpr(ch >= 3)
                {
                    histFunc(data[i*ch + 1], 1);
                    histFunc(data[i*ch + 2], 2);
                }
            }

            if constexpr(ch == 1)
            {
                size_t h = (n / w) & (SIZE_MAX-1);
                w &= (SIZE_MAX-1);
                for(size_t y=0; y<h; y+=2)
                {
                    for(size_t x=0; x<w; x+=2)
                    {
                        histFunc(data[y*w+x], 1);
                        histFunc(data[y*w+x+1], 2);
                        histFunc(data[(y+1)*w+x], 2);
                        histFunc(data[(y+1)*w+x+1], 3);
                    }
                }
            }
        }
    }

    for(int i = 0; i < 4; i++)
    {
        stats.m_min[i] = min[i];
        stats.m_max[i] = max[i];
        stats.m_mean[i] = (double)sum[i] / na[i];
        stats.m_saturated[i] = saturated[i];
        double sum2 = (double)sum[i] * sum[i];
        stats.m_stdDev[i] = std::sqrt((sumSq[i] - sum2 / na[i]) / (na[i] - 1));

        size_t naclip = na[i] - histogram[i][0];
        uint32_t median = findMedian(histogram[i], naclip);
        stats.m_median[i] = median;
        std::vector<uint32_t> madHist(histSize, 0);
        madHist[0] = histogram[i][median];
        for(size_t o = 1; o < histSize; o++)
        {
            if(median + o < histSize)madHist[o] += histogram[i][median + o];
            if(o <= median)madHist[o] += histogram[i][median - o];
        }
        stats.m_mad[i] = findMedian(madHist, naclip);
        if constexpr(!std::numeric_limits<T>::is_integer)
        {
            stats.m_median[i] /= 65535.0;
            stats.m_mad[i] /= 65535.0;
        }
    }

    stats.m_histogram.resize(histSize, 0);
    for(size_t i = 0; i < histSize; i++)
        for(size_t o = 0; o < ch; o++)
            stats.m_histogram[i] += histogram[o][i];
}

void RawImage::calcStats()
{
    if(m_stats.m_stats)return;
    m_stats.m_stats = true;

    switch(m_origType)
    {
    case UINT8:
        if(channels()==1)
            ::calcStats<uint8_t, uint64_t, 1>(static_cast<const uint8_t*>(origData()), size(), width(), m_stats);
        else
            ::calcStats<uint8_t, uint64_t, 4>(static_cast<const uint8_t*>(origData()), size(), width(), m_stats);
        break;
    case UINT16:
        if(channels()==1)
            ::calcStats<uint16_t, uint64_t, 1>(static_cast<const uint16_t*>(origData()), size(), width(), m_stats);
        else
            ::calcStats<uint16_t, uint64_t, 4>(static_cast<const uint16_t*>(origData()), size(), width(), m_stats);
        break;
    case UINT32:
        if(channels()==1)
            ::calcStats<uint32_t, double, 1>(static_cast<const uint32_t*>(origData()), size(), width(), m_stats);
        else
            ::calcStats<uint32_t, double, 4>(static_cast<const uint32_t*>(origData()), size(), width(), m_stats);
        break;
    case FLOAT32:
        if(channels()==1)
            ::calcStats<float, double, 1>(static_cast<const float*>(origData()), size(), width(), m_stats);
        else
            ::calcStats<float, double, 4>(static_cast<const float*>(origData()), size(), width(), m_stats);
        break;
    case FLOAT64:
        if(channels()==1)
            ::calcStats<double, double, 1>(static_cast<const double*>(origData()), size(), width(), m_stats);
        else
            ::calcStats<double, double, 4>(static_cast<const double*>(origData()), size(), width(), m_stats);
        break;
    }
}

uint32_t RawImage::width() const
{
    return m_width;
}

uint32_t RawImage::height() const
{
    return m_height;
}

uint32_t RawImage::channels() const
{
    return m_channels;
}

uint64_t RawImage::size() const
{
    return (uint64_t)width()*height();
}

RawImage::DataType RawImage::type() const
{
    return m_type;
}

uint32_t RawImage::norm() const
{
    switch(m_origType)
    {
    case UINT8:
        return UINT8_MAX;
    case UINT16:
        return UINT16_MAX;
    case UINT32:
        return UINT32_MAX;
    default:
        return 1;
    }
}

uint64_t RawImage::widthBytes() const
{
    return (uint64_t)m_ch * m_width * typeSize(m_type);
}

uint32_t RawImage::widthSamples() const
{
    return m_ch * m_width;
}

void* RawImage::data()
{
    return m_pixels.get();
}

const void *RawImage::data() const
{
    return m_pixels.get();
}

void *RawImage::data(uint32_t row, uint32_t col)
{
    return m_pixels.get() + ((size_t)m_width * row * m_ch + (size_t)col * m_ch) * typeSize(m_type);
}

const void *RawImage::data(uint32_t row, uint32_t col) const
{
    return m_pixels.get() + ((size_t)m_width * row * m_ch + (size_t)col * m_ch) * typeSize(m_type);
}

const void *RawImage::origData() const
{
    if(m_original)
        return m_original.get();
    else
        return m_pixels.get();
}

const void *RawImage::origData(uint32_t row, uint32_t col) const
{
    if(m_original)
    {
        col = (uint64_t)col * m_origWidth / m_width;
        row = (uint64_t)row * m_origHeight / m_height;
        return m_original.get() + ((size_t)m_origWidth * row * m_ch + (size_t)col * m_ch) * typeSize(m_origType);
    }
    else
        return m_pixels.get() + ((size_t)m_width * row * m_ch + (size_t)col * m_ch) * typeSize(m_type);
}

bool RawImage::planar() const
{
    return m_planar;
}

void RawImage::convertToThumbnail()
{
    if(!valid())
        return;

    if(m_thumbAspect == 0.0f)
        m_thumbAspect = (float)width() / height();
    std::unique_ptr<PixelType[]> outptr = std::make_unique<PixelType[]>(THUMB_SIZE * THUMB_SIZE * 4 * sizeof(F16));
    F16 *out = reinterpret_cast<F16*>(outptr.get());

    auto loop = [&](F16 *out, auto *in, float scale)
    {
        for(int64_t i=0; i<THUMB_SIZE; i++)
        {
            for(int64_t o=0; o<THUMB_SIZE; o++)
            {
                int64_t idx = (i*THUMB_SIZE + o)*4;
                int64_t idx2 = ((i * m_height / THUMB_SIZE * m_width) + (o * m_width / THUMB_SIZE)) * m_ch;

                if(m_channels == 1)
                {
                    if(scale == 1.0f)
                        out[idx] = out[idx + 1] = out[idx + 2] = (F16)(in[idx2]);
                    else
                        out[idx] = out[idx + 1] = out[idx + 2] = (F16)(in[idx2] * scale);
                }
                else
                {
                    if(scale == 1.0f)
                    {
                        out[idx]     = (F16)(in[idx2]);
                        out[idx + 1] = (F16)(in[idx2 + 1]);
                        out[idx + 2] = (F16)(in[idx2 + 2]);
                    }
                    else
                    {
                        out[idx]     = (F16)(in[idx2] * scale);
                        out[idx + 1] = (F16)(in[idx2 + 1] * scale);
                        out[idx + 2] = (F16)(in[idx2 + 2] * scale);
                    }
                }
                out[idx + 3] = (F16)1.0f;
            }
        }
    };

    switch(m_type)
    {
    case UINT8:
        loop(out, reinterpret_cast<uint8_t*>(m_pixels.get()), 1.0f/UINT8_MAX);
        break;
    case UINT16:
        loop(out, reinterpret_cast<uint16_t*>(m_pixels.get()), 1.0f/UINT16_MAX);
        break;
    case UINT32:
        loop(out, reinterpret_cast<uint32_t*>(m_pixels.get()), (float)(1.0/UINT32_MAX));
        break;
    case FLOAT16:
        loop(out, reinterpret_cast<F16*>(m_pixels.get()), 1.0f);
        break;
    case FLOAT32:
        loop(out, reinterpret_cast<float*>(m_pixels.get()), 1.0f);
        break;
    default:
#ifndef NO_QT
        qWarning() << "FLOAT64 should not happend";
#endif
        return;
    }

    m_pixels = std::move(outptr);
    m_width = THUMB_SIZE;
    m_height = THUMB_SIZE;
    m_ch = 4;
    m_channels = 3;
    m_type = FLOAT16;
}

void RawImage::convertToGLFormat()
{
    if(m_type == UINT32 || m_type == FLOAT64)
        convertToType(FLOAT32);
    else if(OpenGLES && m_type == UINT16)
        convertToType(FLOAT16);
}

template<typename T, typename U>
void convertType2(size_t size, const T *src, U *dst)
{
    if constexpr((std::is_floating_point_v<T> || std::is_same_v<T, F16>) && (std::is_floating_point_v<U> || std::is_same_v<T, F16>))
    {
        for(size_t i = 0; i < size; i++)
            dst[i] = src[i];
    }

    if constexpr(std::is_integral_v<T> && std::is_integral_v<U>)
    {
        if constexpr(sizeof(T) > sizeof(U))
            for(size_t i = 0; i < size; i++)
                dst[i] = src[i] >> ((sizeof(T) - sizeof(U)) * 8);
        else
            for(size_t i = 0; i < size; i++)
                dst[i] = static_cast<U>(src[i]) << ((sizeof(U) - sizeof(T)) * 8);
    }

    if constexpr((std::is_floating_point_v<T> || std::is_same_v<T, F16>) && std::is_integral_v<U>)
    {
        U max = std::numeric_limits<U>::max();
        T scale = (T)(max);
        for(size_t i = 0; i < size; i++)
            dst[i] = src[i] * scale;
    }

    if constexpr(std::is_integral_v<T> && (std::is_floating_point_v<U> || std::is_same_v<U, F16>))
    {
        float scale = (float)(1.0 / (double)std::numeric_limits<T>::max());
        for(size_t i = 0; i < size; i++)
            dst[i] = (U)(src[i] * scale);
    }
}

template<typename T>
void convertType(size_t size, RawImage::DataType dstType, const T *src, void *dst)
{
    switch(dstType)
    {
    case RawImage::UINT8:
        convertType2(size, src, static_cast<uint8_t*>(dst));
        break;
    case RawImage::UINT16:
        convertType2(size, src, static_cast<uint16_t*>(dst));
        break;
    case RawImage::UINT32:
        convertType2(size, src, static_cast<uint32_t*>(dst));
        break;
    case RawImage::FLOAT16:
        convertType2(size, src, static_cast<F16*>(dst));
        break;
    case RawImage::FLOAT32:
        convertType2(size, src, static_cast<float*>(dst));
        break;
    case RawImage::FLOAT64:
        convertType2(size, src, static_cast<double*>(dst));
        break;
    }
}

void RawImage::convertToType(DataType type)
{
    if(type == m_type)
        return;

    m_origWidth = m_width;
    m_origHeight = m_height;
    m_original = std::move(m_pixels);
    DataType origType = m_type;
    allocate(m_width, m_height, m_channels, type);
    m_origType = origType;

    size_t s = size() * m_ch;
    switch(m_origType)
    {
    case UINT8:
        convertType(s, type, reinterpret_cast<uint8_t*>(m_original.get()), m_pixels.get());
        break;
    case UINT16:
        convertType(s, type, reinterpret_cast<uint16_t*>(m_original.get()), m_pixels.get());
        break;
    case UINT32:
        convertType(s, type, reinterpret_cast<uint32_t*>(m_original.get()), m_pixels.get());
        break;
    case FLOAT16:
        convertType(s, type, reinterpret_cast<F16*>(m_original.get()), m_pixels.get());
        break;
    case FLOAT32:
        convertType(s, type, reinterpret_cast<float*>(m_original.get()), m_pixels.get());
        break;
    case FLOAT64:
        convertType(s, type, reinterpret_cast<double*>(m_original.get()), m_pixels.get());
        break;
    }
}

float RawImage::thumbAspect() const
{
    return m_thumbAspect;
}

bool RawImage::pixel(int x, int y, double &r, double &g, double &b) const
{
    if(x < 0 || y < 0 || x >= (int)width() || y >= (int)height())return false;

    switch(m_origType)
    {
    case UINT8:
    {
        const uint8_t *v = static_cast<const uint8_t*>(origData(y, x));
        if(m_channels == 1)
        {
            r = g = b = *v;
        }
        else
        {
            r = v[0];
            g = v[1];
            b = v[2];
        }
        break;
    }
    case UINT16:
    {
        const uint16_t *v = static_cast<const uint16_t*>(origData(y, x));
        if(m_channels == 1)
        {
            r = g = b = *v;
        }
        else
        {
            r = v[0];
            g = v[1];
            b = v[2];
        }
        break;
    }
    case UINT32:
    {
        const uint32_t *v = static_cast<const uint32_t*>(origData(y, x));
        if(m_channels == 1)
        {
            r = g = b = *v;
        }
        else
        {
            r = v[0];
            g = v[1];
            b = v[2];
        }
        break;
    }
    case FLOAT32:
    {
        const float *v = static_cast<const float*>(origData(y, x));
        if(m_channels == 1)
        {
            r = g = b = *v;
        }
        else
        {
            r = v[0];
            g = v[1];
            b = v[2];
        }
        break;
    }
    case FLOAT64:
    {
        const double *v = static_cast<const double*>(origData(y, x));
        if(m_channels == 1)
        {
            r = g = b = *v;
        }
        else
        {
            r = v[0];
            g = v[1];
            b = v[2];
        }
        break;
    }
    case FLOAT16:
    {
        const F16 *v = static_cast<const F16*>(origData(y, x));
        if(m_channels == 1)
        {
            r = g = b = *v;
        }
        else
        {
            r = v[0];
            g = v[1];
            b = v[2];
        }
        break;
    }
    }
    return true;
}

template<typename T, typename U = float>
void boxResample(uint32_t w, uint32_t h, uint32_t ch, uint32_t oldw, uint32_t oldh, const uint8_t *in_, uint8_t *out_)
{
    if(oldw == 0 || oldh == 0)return;

    const T *in = reinterpret_cast<const T*>(in_);
    T *out = reinterpret_cast<T*>(out_);
    U max = 255.0f;
    if constexpr(std::is_integral_v<T>)
        max = (U)std::numeric_limits<T>::max();

    float sx = (float)w / oldw;
    float sy = (float)h / oldh;
    for(uint64_t y = 0; y < h; y++)//iterate over destination Y
    {
        for(uint64_t x = 0; x < w; x++)//iterate over destination X
        {
            U p[4] = {0.0f};
            uint64_t xx = x * oldw / w;//calculate source rect
            uint64_t yy = y * oldh / h;
            uint64_t xe = std::min((x + 1) * oldw / w, (uint64_t)oldw - 1);
            uint64_t ye = std::min((y + 1) * oldh / h, (uint64_t)oldh - 1);
            for(uint32_t o = yy; o <= ye; o++)//iterate over source Y
            {
                float cy = o * sy - y;
                if(cy < 0.0f)cy = sy + cy;
                else if(sy + cy > 1.0f)cy = 1.0f - cy;
                else cy = sy;
                if(yy==ye)cy = 1.0f;
                for(uint32_t i = xx; i <= xe; i++)//iterate over source X
                {
                    float cx = i * sx - x;
                    if(cx < 0.0f)cx = sx + cx;
                    else if(sx + cx > 1.0f)cx = 1.0f - cx;
                    else cx = sx;
                    if(xx==xe)cx = 1.0f;
                    for(uint32_t z = 0; z < ch; z++)
                        p[z] += in[(o * oldw + i) * ch + z] * cy * cx;
                }
            }
            for(uint32_t z = 0; z < ch; z++)
                if constexpr(std::is_floating_point_v<T> || std::is_same_v<T, F16>)
                    out[(y * w + x) * ch + z] = (T)p[z];
                else
                    out[(y * w + x) * ch + z] = std::clamp(std::round(p[z]), 0.0f, max);
        }
    }
}

void RawImage::resize(uint32_t w, uint32_t h)
{
    if(!valid())return;

    std::unique_ptr<PixelType[]> old_pixels = std::move(m_pixels);
    uint32_t oldw = m_width;
    uint32_t oldh = m_height;
    m_thumbAspect = (float)m_width / m_height;

    DataType origType = m_origType;
    allocate(w, h, m_channels, m_type);
    m_origType = origType;

    switch(m_type)
    {
    case RawImage::UINT8:
        boxResample<uint8_t>(w, h, m_ch, oldw, oldh, old_pixels.get(), m_pixels.get());
        break;
    case RawImage::UINT16:
        boxResample<uint16_t>(w, h, m_ch, oldw, oldh, old_pixels.get(), m_pixels.get());
        break;
    case RawImage::UINT32:
        boxResample<uint32_t>(w, h, m_ch, oldw, oldh, old_pixels.get(), m_pixels.get());
        break;
    case RawImage::FLOAT16:
        boxResample<F16>(w, h, m_ch, oldw, oldh, old_pixels.get(), m_pixels.get());
        break;
    case RawImage::FLOAT32:
        boxResample<float>(w, h, m_ch, oldw, oldh, old_pixels.get(), m_pixels.get());
        break;
    case RawImage::FLOAT64:
        boxResample<double, double>(w, h, m_ch, oldw, oldh, old_pixels.get(), m_pixels.get());
        break;
    }
}

template<typename T, typename U>
void integerResample(uint32_t w, uint32_t h, uint32_t ch, uint32_t oldw, uint32_t down, bool avg, const uint8_t *in_, uint8_t *out_)
{
    const T *in = reinterpret_cast<const T*>(in_);
    T *out = reinterpret_cast<T*>(out_);
    const uint32_t down2 = down * down;

    U m = std::numeric_limits<T>::max();
    if constexpr(std::is_floating_point_v<T>)m = down2;

    for(uint64_t i = 0; i < h; i++)
    {
        for(uint64_t o = 0; o < w; o++)
        {
            for(uint64_t p = 0; p < ch; p++)
            {
                U pix = 0;
                for(uint32_t y = 0; y < down; y++)
                    for(uint32_t x = 0; x < down; x++)
                        pix += in[((i * down) + y) * oldw * ch + ((o * down) + x) * ch + p];

                if (avg)
                    out[(i * w + o) * ch + p] = pix / down2;
                else
                    out[(i * w + o) * ch + p] = std::min(pix, m);
            }
        }
    }
}

void RawImage::resizeInt(int downsample, bool avg)
{
    uint32_t oldw = m_width;
    std::unique_ptr<PixelType[]> old_pixels = std::move(m_pixels);
    allocate(m_width / downsample, m_height / downsample, m_channels, m_type);

    switch(m_type)
    {
    case RawImage::UINT8:
        integerResample<uint8_t, uint32_t>(m_width, m_height, m_ch, oldw, downsample, avg, old_pixels.get(), m_pixels.get());
        break;
    case RawImage::UINT16:
        integerResample<uint16_t, uint32_t>(m_width, m_height, m_ch, oldw, downsample, avg, old_pixels.get(), m_pixels.get());
        break;
    case RawImage::UINT32:
        integerResample<uint32_t, uint64_t>(m_width, m_height, m_ch, oldw, downsample, avg, old_pixels.get(), m_pixels.get());
        break;
    case RawImage::FLOAT32:
        integerResample<float, double>(m_width, m_height, m_ch, oldw, downsample, avg, old_pixels.get(), m_pixels.get());
        break;
    case RawImage::FLOAT64:
        integerResample<double, double>(m_width, m_height, m_ch, oldw, downsample, avg, old_pixels.get(), m_pixels.get());
        break;
    default:
        break;
    }
}

std::pair<float, float> RawImage::unitScale() const
{
    float min = *std::min_element(m_stats.m_min, m_stats.m_min + 4);
    float max = *std::max_element(m_stats.m_max, m_stats.m_max + 4);

    if(m_origType == UINT32)
    {
        min /= (float)UINT32_MAX;
        max /= (float)UINT32_MAX;
    }

    if(min < 0.0f || max > 1.0f)
        return {1.0f / (max - min), -min / (max - min)};
    else
        return {1.0f, 0.0f};
}

void RawImage::flip()
{
    std::unique_ptr<PixelType[]> tmp = std::move(m_pixels);
    allocate(m_width, m_height, m_channels, m_type);
    uint32_t rowSize = m_width * m_ch * typeSize(m_type);
    for(uint32_t i=0; i<m_height; i++)
        std::memcpy(m_pixels.get() + rowSize * (m_height - i - 1), tmp.get() + rowSize * i, rowSize);
}

std::shared_ptr<RawImage> RawImage::fromPlanar(const RawImage &img)
{
    return RawImage::fromPlanar(img.data(), img.width(), img.height(), img.channels(), img.type());
}

std::shared_ptr<RawImage> RawImage::fromPlanar(const void *pixels, uint32_t w, uint32_t h, uint32_t ch, RawImage::DataType type)
{
    std::shared_ptr<RawImage> image = std::make_shared<RawImage>(w, h, ch, type);
    size_t size = w * h;
    size_t ch2 = ch == 1 ? 1 : 4;
    auto convert = [&](auto *in, auto *out, auto alpha)
    {
        for(size_t i=0; i<size; i++)
            for(size_t o=0; o<ch; o++)
                out[i*ch2 + o] = in[o*size + i];

        if(ch != ch2)
            for(size_t i=0; i<size; i++)
                out[i*ch2 + 3] = alpha;
    };

    switch(type)
    {
    case UINT8:
#ifdef __SSE2__
        if(ch==3)
            fromPlanarSSE<uint8_t, 3>(pixels, image->data(), size);
        else
            fromPlanarSSE<uint8_t, 4>(pixels, image->data(), size);
#else
        convert(static_cast<const uint8_t*>(pixels), static_cast<uint8_t*>(image->data()), UINT8_MAX);
#endif
        break;
    case UINT16:
#ifdef __SSE2__
        if(ch==3)
            fromPlanarSSE<uint16_t, 3>(pixels, static_cast<uint16_t*>(image->data()), size);
        else
            fromPlanarSSE<uint16_t, 4>(pixels, static_cast<uint16_t*>(image->data()), size);
#else
        convert(static_cast<const uint16_t*>(pixels), static_cast<uint16_t*>(image->data()), UINT16_MAX);
#endif
        break;
    case FLOAT16:
        convert(static_cast<const F16*>(pixels), static_cast<F16*>(image->data()), (F16)1.0f);
        break;
    case UINT32:
#ifdef __SSE2__
        if(ch==3)
            fromPlanarSSE<uint32_t, 3>(pixels, image->data(), size);
        else
            fromPlanarSSE<uint32_t, 4>(pixels, image->data(), size);
#else
        convert(static_cast<const uint32_t*>(pixels), static_cast<uint32_t*>(image->data()), UINT32_MAX);
#endif
        break;
    case FLOAT32:
#ifdef __SSE2__
        if(ch==3)
            fromPlanarSSE<float, 3>(pixels, image->data(), size);
        else
            fromPlanarSSE<float, 4>(pixels, image->data(), size);
#else
        convert(static_cast<const float*>(pixels), static_cast<float*>(image->data()), 1);
#endif
        break;
    case FLOAT64:
        convert(static_cast<const double*>(pixels), static_cast<double*>(image->data()), 1);
        break;
    }
    image->m_planar = false;
    return image;
}

std::shared_ptr<RawImage> RawImage::toPlanar()
{
    std::shared_ptr<RawImage> ret = std::make_shared<RawImage>(m_width, m_height, 1, m_type);
    size_t size = m_width * m_height;
    size_t ch = m_ch;

    auto convert = [&](auto *in, auto *out)
    {
        for(size_t i=0; i<size; i++)
            out[i] = in[i * ch];
    };

    switch(m_type)
    {
    case UINT8:
        convert(static_cast<uint8_t*>(data()), static_cast<uint8_t*>(ret->data()));
        break;
    case UINT16:
    case FLOAT16:
        convert(static_cast<uint16_t*>(data()), static_cast<uint16_t*>(ret->data()));
        break;
    case UINT32:
    case FLOAT32:
        convert(static_cast<uint32_t*>(data()), static_cast<uint32_t*>(ret->data()));
        break;
    case FLOAT64:
        convert(static_cast<double*>(data()), static_cast<double*>(ret->data()));
        break;
    }

    return ret;
}

std::vector<RawImage> RawImage::split() const
{
    std::vector<RawImage> planes;
    planes.resize(m_channels);
    for(size_t i=0; i<m_channels; i++)
        planes[i].allocate(m_width, m_height, 1, m_type);

    size_t s = size();
    auto extract = [&](auto *in, auto *out, size_t off)
    {
        for(size_t i=0; i < s; i++)
            out[i] = in[i*m_ch + off];
    };

    for(uint32_t i=0; i<m_channels; i++)
    {
        switch(m_type)
        {
        case UINT8:
            extract(static_cast<const uint8_t*>(data()), static_cast<uint8_t*>(planes[i].data()), i);
            break;
        case FLOAT16:
        case UINT16:
            extract(static_cast<const uint16_t*>(data()), static_cast<uint16_t*>(planes[i].data()), i);
            break;
        case UINT32:
        case FLOAT32:
            extract(static_cast<const uint32_t*>(data()), static_cast<uint32_t*>(planes[i].data()), i);
            break;
        case FLOAT64:
            extract(static_cast<const double*>(data()), static_cast<double*>(planes[i].data()), i);
            break;
        }
    }

    return planes;
}

bool RawImage::valid() const
{
    return m_width > 0 && m_height > 0;
}

#ifndef NO_QT
void RawImage::setICCProfile(const QByteArray &icc)
{
    if(icc.size())
        m_iccProfile = std::vector<uint8_t>(icc.begin(), icc.end());
}

void RawImage::setICCProfile(const LibXISF::ByteArray &icc)
{
    if(icc.size())
        m_iccProfile = std::vector<uint8_t>(icc.data(), icc.data() + icc.size());
}

void RawImage::convertTosRGB()
{
    if(m_channels == 1 || m_iccProfile.empty())
        return;

    cmsUInt32Number type = TYPE_RGBA_8;
    switch(m_type)
    {
    case UINT8:
        type = TYPE_RGBA_8;
        break;
    case UINT16:
        type = TYPE_RGBA_16;
        break;
    case FLOAT16:
        type = TYPE_RGBA_HALF_FLT;
        break;
    case FLOAT32:
        type = TYPE_RGBA_FLT;
        break;
    default:
        return;
    }

    cmsHPROFILE inProfile = cmsOpenProfileFromMem(m_iccProfile.data(), m_iccProfile.size());
    cmsHPROFILE outProfile = cmsCreate_sRGBProfile();
    if(inProfile && outProfile)
    {
        cmsHTRANSFORM transform = cmsCreateTransform(inProfile, type, outProfile, type, INTENT_PERCEPTUAL, cmsFLAGS_COPY_ALPHA);

        std::unique_ptr<PixelType[]> tmp = std::move(m_pixels);
        allocate(m_width, m_height, m_channels, m_type);
        if(transform)
        {
            cmsDoTransform(transform, tmp.get(), m_pixels.get(), size());
            cmsDeleteTransform(transform);
        }
        else
        {
            //qDebug() << "Failed to create color transform";
        }
    }
    else
    {
        //qDebug() << "Failed to open icc profile";
    }

    cmsCloseProfile(inProfile);
    cmsCloseProfile(outProfile);
}

void RawImage::generateLUT()
{
    if(m_channels == 1 || m_iccProfile.empty())
        return;

    const int LUT_SIZE = 32;
    const float LUT_SIZEF = LUT_SIZE - 1;
    std::vector<F16> lut(LUT_SIZE * LUT_SIZE * LUT_SIZE * 4);
    m_lut.resize(lut.size());
    for(int z = 0; z < LUT_SIZE; z++)
    {
        for(int y = 0; y < LUT_SIZE; y++)
        {
            F16 *line = &lut[(z*LUT_SIZE*LUT_SIZE + y*LUT_SIZE) * 4];
            for(int x = 0; x < LUT_SIZE; x++)
            {
                line[x*4 + 0] = (F16)(x / LUT_SIZEF);
                line[x*4 + 1] = (F16)(y / LUT_SIZEF);
                line[x*4 + 2] = (F16)(z / LUT_SIZEF);
                line[x*4 + 3] = (F16)1.0f;
            }
        }
    }

    cmsHPROFILE inProfile = cmsOpenProfileFromMem(m_iccProfile.data(), m_iccProfile.size());
    cmsHPROFILE outProfile = cmsCreate_sRGBProfile();
    if(inProfile && outProfile)
    {
        cmsHTRANSFORM transform = cmsCreateTransform(inProfile, TYPE_RGBA_HALF_FLT, outProfile, TYPE_RGBA_HALF_FLT, INTENT_PERCEPTUAL, cmsFLAGS_COPY_ALPHA);

        if(transform)
        {
            cmsDoTransform(transform, &lut[0], &m_lut[0], lut.size()/4);
            cmsDeleteTransform(transform);
        }
        else
        {
            //qDebug() << "Failed to create color transform";
            m_lut.clear();
        }
    }
    else
    {
        //qDebug() << "Failed to open icc profile";
        m_lut.clear();
    }

    cmsCloseProfile(inProfile);
    cmsCloseProfile(outProfile);
}
#endif

void RawImage::applySTF(const MTFParam &mtfParams)
{
    auto applyMTF = [&](auto *src) -> void
    {
        float s = 1.0f;
        if constexpr(std::numeric_limits<std::remove_reference_t<decltype(*src)>>::is_integer)
            s = (float)std::numeric_limits<std::remove_reference_t<decltype(*src)>>::max();

        auto unit = unitScale();
        float iscale = 1.0f / s;
        size_t len = size() * m_ch;
        for(size_t i = 0; i < len; i++)
        {
            float x;
            if(m_ch == 4)
            {
                size_t c = i & 0x3;
                if(c < 3)
                {
                    if constexpr(std::numeric_limits<std::remove_reference_t<decltype(*src)>>::is_integer)x = src[i] * iscale;
                    else x = src[i] * unit.first + unit.second;
                    x = (x - mtfParams.blackPoint[c]) / (mtfParams.whitePoint[c] - mtfParams.blackPoint[c]);
                    x = std::clamp(x, 0.0f, 1.0f);
                    x = ((mtfParams.midPoint[c] - 1.0f) * x) / ((2.0f * mtfParams.midPoint[c] - 1.0f) * x - mtfParams.midPoint[c]);
                    src[i] = x * s;
                }
            }
            else
            {
                if constexpr(std::numeric_limits<std::remove_reference_t<decltype(*src)>>::is_integer)x = src[i] * iscale;
                else x = src[i] * unit.first + unit.second;
                x = (x - mtfParams.blackPoint[0]) / (mtfParams.whitePoint[0] - mtfParams.blackPoint[0]);
                x = std::clamp(x, 0.0f, 1.0f);
                x = ((mtfParams.midPoint[0] - 1.0f) * x) / ((2.0f * mtfParams.midPoint[0] - 1.0f) * x - mtfParams.midPoint[0]);
                src[i] = x * s;
            }
        }
    };

    switch(m_type)
    {
    case UINT8:
        applyMTF(reinterpret_cast<uint8_t*>(m_pixels.get()));
        break;
    case UINT16:
        applyMTF(reinterpret_cast<uint16_t*>(m_pixels.get()));
        break;
    case UINT32:
        applyMTF(reinterpret_cast<uint32_t*>(m_pixels.get()));
        break;
    case FLOAT32:
        applyMTF(reinterpret_cast<float*>(m_pixels.get()));
        break;
    case FLOAT64:
        applyMTF(reinterpret_cast<double*>(m_pixels.get()));
        break;
    default:
        break;
    }
}

MTFParam RawImage::calcMTFParams(bool linked, bool debayer) const
{
    const float BLACK_POINT_SIGMA = -2.8f;
    const float MAD_TO_SIGMA = 1.4826f;
    const float TARGET_BACKGROUND = 0.25f;

    auto MTF = [](float x, float m)
    {
        if(x < 0)return 0.0f;
        if(x > 1)return 1.0f;
        return ((m - 1) * x) / ((2 * m - 1) * x - m);
    };

    MTFParam mtfParam;

    int i = 0;
    int ch = m_channels;
    int o = 0;
    if(debayer)
    {
        i = 1;
        ch = 4;
        o = 1;
    }

    float bp2 = 0;
    float mid2 = 0;
    float max2 = 0;
    for(; i < ch; i++)
    {
        double median, mad, max;
        median = m_stats.m_median[i];
        mad = m_stats.m_mad[i];
        median /= norm();
        bool a = median > 0.5 ? true : false;
        mad /= norm();
        max = 1.0f;
        float bp = a || mad == 0.0f ? 0.0f : std::clamp(median + mad * BLACK_POINT_SIGMA * MAD_TO_SIGMA, 0.0, 1.0);
        if(a && mad != 0.0f)
            max = std::clamp(median - mad * BLACK_POINT_SIGMA * MAD_TO_SIGMA, 0.0, 1.0);

        float mid = !a ? MTF(median - bp, TARGET_BACKGROUND) : MTF(TARGET_BACKGROUND, max - median);
        mtfParam.blackPoint[i-o] = bp;
        mtfParam.midPoint[i-o] = mid;
        mtfParam.whitePoint[i-o] = max;
        bp2 += bp;
        mid2 += mid;
        max2 = max > max2 ? max : max2;
    }
    if(ch == 1)
    {
        mtfParam.blackPoint[1] = mtfParam.blackPoint[2] = mtfParam.blackPoint[0];
        mtfParam.midPoint[1] = mtfParam.midPoint[2] = mtfParam.midPoint[0];
        mtfParam.whitePoint[1] = mtfParam.whitePoint[2] = mtfParam.whitePoint[0];
    }
    if(linked)
    {
        mtfParam.blackPoint[0] = mtfParam.blackPoint[1] = mtfParam.blackPoint[2] = bp2 / ch;
        mtfParam.midPoint[0] = mtfParam.midPoint[1] = mtfParam.midPoint[2] = mid2 / ch;
        mtfParam.whitePoint[0] = mtfParam.whitePoint[1] = mtfParam.whitePoint[2] = max2;
    }
    return mtfParam;
}

const std::vector<uint16_t> &RawImage::getLUT() const
{
    return m_lut;
}
