#include "SkImage.h"
#include "SkPixmap.h"
#include "SkStream.h"
#include "image_cmp.hpp"

#include <algorithm>
#include <assert.h>
#include <cstdint>
#include <vector>

static int byte0(uint32_t p) { return (p >> 0) & 0xFF; }
static int byte1(uint32_t p) { return (p >> 8) & 0xFF; }
static int byte2(uint32_t p) { return (p >> 16) & 0xFF; }
static int byte3(uint32_t p) { return (p >> 24) & 0xFF; }

static uint32_t pack(unsigned a, unsigned b, unsigned c, unsigned d)
{
    return (a << 24) | (b << 16) | (c << 8) | (d << 0);
}

static ImageCmp pixmap_cmp(const SkPixmap& pa, const SkPixmap& pb)
{
    assert(pa.width() == pb.width());
    assert(pa.height() == pb.height());

    size_t pixcount = pa.width() * pa.height();
    int maxdiff = 0;
    size_t rgb_diffcount = 0;
    size_t pixel_diffcount = 0;

    for (int y = 0; y < pa.height(); ++y)
    {
        const uint32_t* row0 = pa.addr32(0, y);
        const uint32_t* row1 = pb.addr32(0, y);

        for (int x = 0; x < pa.width(); ++x)
        {
            const uint32_t p0 = row0[x];
            const uint32_t p1 = row1[x];
            if (p0 == p1)
            {
                continue;
            }
            // Find the max diff in this pixel's r,g,b,a.
            int rgb_diff = std::abs(byte0(p0) - byte0(p1));
            rgb_diff = std::max(rgb_diff, std::abs(byte1(p0) - byte1(p1)));
            rgb_diff = std::max(rgb_diff, std::abs(byte2(p0) - byte2(p1)));
            rgb_diff = std::max(rgb_diff, std::abs(byte3(p0) - byte3(p1)));
            maxdiff = std::max(maxdiff, rgb_diff);
            rgb_diffcount += rgb_diff;
            if (rgb_diff != 0)
            {
                ++pixel_diffcount;
            }
        }
    }

    return ImageCmp::diff(maxdiff,
                          (float)rgb_diffcount / (pixcount * 255),
                          pixel_diffcount);
}

ImageCmp image_cmp(sk_sp<SkImage> a, sk_sp<SkImage> b)
{
    if (a->width() != b->width() || a->height() != b->height())
    {
        return ImageCmp::dimensionMismatch();
    }

    // ensure we can peek at the pixels
    a = a->makeRasterImage();
    b = b->makeRasterImage();

    SkPixmap pa, pb;
    if (!a->peekPixels(&pa) || !b->peekPixels(&pb))
    {
        return ImageCmp::failedToLoad();
    }

    return pixmap_cmp(pa, pb);
}

uint32_t compute_diff0(uint32_t p0, uint32_t p1)
{
    auto a = std::abs(byte0(p0) - byte0(p1));
    auto b = std::abs(byte1(p0) - byte1(p1));
    auto c = std::abs(byte2(p0) - byte2(p1));
    return pack(0xFF, a, b, c);
}

uint32_t compute_diff1(uint32_t p0, uint32_t p1)
{
    auto d = p0 == p1 ? 0 : 0xFF;
    return pack(0xFF, d, d, d);
}

sk_sp<SkImage> make_diff_image(sk_sp<SkImage> a,
                               sk_sp<SkImage> b,
                               uint32_t (*compute_diff)(uint32_t a, uint32_t b))
{
    // ensure we can peek at the pixels
    a = a->makeRasterImage();
    b = b->makeRasterImage();

    SkPixmap pa, pb;
    if (!a->peekPixels(&pa) || !b->peekPixels(&pb))
    {
        throw "failed to peek";
    }

    const auto info = SkImageInfo::MakeN32Premul(pa.width(), pa.height());
    std::vector<uint32_t> pixels(pa.width() * pa.height());
    auto df = SkPixmap(info, pixels.data(), pa.width() * sizeof(uint32_t));

    for (int y = 0; y < df.height(); ++y)
    {
        for (int x = 0; x < df.width(); ++x)
        {
            *df.writable_addr32(x, y) =
                compute_diff(*pa.addr32(x, y), *pb.addr32(x, y));
        }
    }

    return SkImage::MakeRasterCopy(df);
}
