#include "plutovg-private.h"
#include "plutovg-utils.h"

#define STB_IMAGE_WRITE_STATIC
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "plutovg-stb-image-write.h"

#define STB_IMAGE_STATIC
#define STB_IMAGE_IMPLEMENTATION
#include "plutovg-stb-image.h"

static plutovg_surface_t* plutovg_surface_create_uninitialized(int width, int height)
{
    if(width > STBI_MAX_DIMENSIONS || height > STBI_MAX_DIMENSIONS)
        return NULL;
    const size_t size = width * height * 4;
    plutovg_surface_t* surface = malloc(size + sizeof(plutovg_surface_t));
    if(surface == NULL)
        return NULL;
    surface->ref_count = 1;
    surface->width = width;
    surface->height = height;
    surface->stride = width * 4;
    surface->data = (uint8_t*)(surface + 1);
    return surface;
}

plutovg_surface_t* plutovg_surface_create(int width, int height)
{
    plutovg_surface_t* surface = plutovg_surface_create_uninitialized(width, height);
    if(surface)
        memset(surface->data, 0, surface->height * surface->stride);
    return surface;
}

plutovg_surface_t* plutovg_surface_create_for_data(unsigned char* data, int width, int height, int stride)
{
    plutovg_surface_t* surface = malloc(sizeof(plutovg_surface_t));
    surface->ref_count = 1;
    surface->width = width;
    surface->height = height;
    surface->stride = stride;
    surface->data = data;
    return surface;
}

static plutovg_surface_t* plutovg_surface_load_from_image(stbi_uc* image, int width, int height)
{
    plutovg_surface_t* surface = plutovg_surface_create_uninitialized(width, height);
    if(surface)
        plutovg_convert_rgba_to_argb(surface->data, image, surface->width, surface->height, surface->stride);
    stbi_image_free(image);
    return surface;
}

plutovg_surface_t* plutovg_surface_load_from_image_file(const char* filename)
{
    int width, height, channels;
    stbi_uc* image = stbi_load(filename, &width, &height, &channels, STBI_rgb_alpha);
    if(image == NULL)
        return NULL;
    return plutovg_surface_load_from_image(image, width, height);
}

plutovg_surface_t* plutovg_surface_load_from_image_data(const void* data, int length)
{
    int width, height, channels;
    stbi_uc* image = stbi_load_from_memory(data, length, &width, &height, &channels, STBI_rgb_alpha);
    if(image == NULL)
        return NULL;
    return plutovg_surface_load_from_image(image, width, height);
}

static const uint8_t base64_table[128] = {
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3F,
    0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B,
    0x3C, 0x3D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
    0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
    0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
    0x17, 0x18, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20,
    0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
    0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30,
    0x31, 0x32, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00
};

plutovg_surface_t* plutovg_surface_load_from_image_base64(const char* data, int length)
{
    plutovg_surface_t* surface = NULL;
    uint8_t* output_data = NULL;
    size_t output_length = 0;

    size_t equals_sign_count = 0;
    size_t sidx = 0;
    size_t didx = 0;

    if(length == -1)
        length = strlen(data);
    output_data = malloc(length);
    if(output_data == NULL)
        return NULL;
    for(int i = 0; i < length; ++i) {
        uint8_t cc = data[i];
        if(cc == '=') {
            ++equals_sign_count;
        } else if(cc == '+' || cc == '/' || PLUTOVG_IS_ALNUM(cc)) {
            if(equals_sign_count > 0)
                goto cleanup;
            output_data[output_length++] = base64_table[cc];
        } else if(!PLUTOVG_IS_WS(cc)) {
            goto cleanup;
        }
    }

    if(output_length == 0 || equals_sign_count > 2 || (output_length % 4) == 1)
        goto cleanup;
    output_length -= (output_length + 3) / 4;
    if(output_length == 0) {
        goto cleanup;
    }

    if(output_length > 1) {
        while(didx < output_length - 2) {
            output_data[didx + 0] = (((output_data[sidx + 0] << 2) & 255) | ((output_data[sidx + 1] >> 4) & 003));
            output_data[didx + 1] = (((output_data[sidx + 1] << 4) & 255) | ((output_data[sidx + 2] >> 2) & 017));
            output_data[didx + 2] = (((output_data[sidx + 2] << 6) & 255) | ((output_data[sidx + 3] >> 0) & 077));
            sidx += 4;
            didx += 3;
        }
    }

    if(didx < output_length)
        output_data[didx] = (((output_data[sidx + 0] << 2) & 255) | ((output_data[sidx + 1] >> 4) & 003));
    if(++didx < output_length) {
        output_data[didx] = (((output_data[sidx + 1] << 4) & 255) | ((output_data[sidx + 2] >> 2) & 017));
    }

    surface = plutovg_surface_load_from_image_data(output_data, output_length);
cleanup:
    free(output_data);
    return surface;
}

plutovg_surface_t* plutovg_surface_reference(plutovg_surface_t* surface)
{
    if(surface == NULL)
        return NULL;
    ++surface->ref_count;
    return surface;
}

void plutovg_surface_destroy(plutovg_surface_t* surface)
{
    if(surface == NULL)
        return;
    if(--surface->ref_count == 0) {
        free(surface);
    }
}

int plutovg_surface_get_reference_count(const plutovg_surface_t* surface)
{
    if(surface)
        return surface->ref_count;
    return 0;
}

unsigned char* plutovg_surface_get_data(const plutovg_surface_t* surface)
{
    return surface->data;
}

int plutovg_surface_get_width(const plutovg_surface_t* surface)
{
    return surface->width;
}

int plutovg_surface_get_height(const plutovg_surface_t* surface)
{
    return surface->height;
}

int plutovg_surface_get_stride(const plutovg_surface_t* surface)
{
    return surface->stride;
}

void plutovg_surface_clear(plutovg_surface_t* surface, const plutovg_color_t* color)
{
    uint32_t pixel = plutovg_premultiply_argb(plutovg_color_to_argb32(color));
    for(int y = 0; y < surface->height; y++) {
        uint32_t* pixels = (uint32_t*)(surface->data + surface->stride * y);
        plutovg_memfill32(pixels, surface->width, pixel);
    }
}

static void plutovg_surface_write_begin(const plutovg_surface_t* surface)
{
    plutovg_convert_argb_to_rgba(surface->data, surface->data, surface->width, surface->height, surface->stride);
}

static void plutovg_surface_write_end(const plutovg_surface_t* surface)
{
    plutovg_convert_rgba_to_argb(surface->data, surface->data, surface->width, surface->height, surface->stride);
}

bool plutovg_surface_write_to_png(const plutovg_surface_t* surface, const char* filename)
{
    plutovg_surface_write_begin(surface);
    int success = stbi_write_png(filename, surface->width, surface->height, 4, surface->data, surface->stride);
    plutovg_surface_write_end(surface);
    return success;
}

bool plutovg_surface_write_to_jpg(const plutovg_surface_t* surface, const char* filename, int quality)
{
    plutovg_surface_write_begin(surface);
    int success = stbi_write_jpg(filename, surface->width, surface->height, 4, surface->data, quality);
    plutovg_surface_write_end(surface);
    return success;
}

bool plutovg_surface_write_to_png_stream(const plutovg_surface_t* surface, plutovg_write_func_t write_func, void* closure)
{
    plutovg_surface_write_begin(surface);
    int success = stbi_write_png_to_func(write_func, closure, surface->width, surface->height, 4, surface->data, surface->stride);
    plutovg_surface_write_end(surface);
    return success;
}

bool plutovg_surface_write_to_jpg_stream(const plutovg_surface_t* surface, plutovg_write_func_t write_func, void* closure, int quality)
{
    plutovg_surface_write_begin(surface);
    int success = stbi_write_jpg_to_func(write_func, closure, surface->width, surface->height, 4, surface->data, quality);
    plutovg_surface_write_end(surface);
    return success;
}

void plutovg_convert_argb_to_rgba(unsigned char* dst, const unsigned char* src, int width, int height, int stride)
{
    for(int y = 0; y < height; y++) {
        const uint32_t* src_row = (const uint32_t*)(src + stride * y);
        unsigned char* dst_row = dst + stride * y;
        for(int x = 0; x < width; x++) {
            uint32_t pixel = src_row[x];
            uint32_t a = (pixel >> 24) & 0xFF;
            if(a == 0) {
                *dst_row++ = 0;
                *dst_row++ = 0;
                *dst_row++ = 0;
                *dst_row++ = 0;
            } else {
                uint32_t r = (pixel >> 16) & 0xFF;
                uint32_t g = (pixel >> 8) & 0xFF;
                uint32_t b = (pixel >> 0) & 0xFF;
                if(a != 255) {
                    r = (r * 255) / a;
                    g = (g * 255) / a;
                    b = (b * 255) / a;
                }

                *dst_row++ = r;
                *dst_row++ = g;
                *dst_row++ = b;
                *dst_row++ = a;
            }
        }
    }
}

void plutovg_convert_rgba_to_argb(unsigned char* dst, const unsigned char* src, int width, int height, int stride)
{
    for(int y = 0; y < height; y++) {
        const unsigned char* src_row = src + stride * y;
        uint32_t* dst_row = (uint32_t*)(dst + stride * y);
        for(int x = 0; x < width; x++) {
            uint32_t a = src_row[4 * x + 3];
            if(a == 0) {
                dst_row[x] = 0x00000000;
            } else {
                uint32_t r = src_row[4 * x + 0];
                uint32_t g = src_row[4 * x + 1];
                uint32_t b = src_row[4 * x + 2];
                if(a != 255) {
                    r = (r * a) / 255;
                    g = (g * a) / 255;
                    b = (b * a) / 255;
                }

                dst_row[x] = (a << 24) | (r << 16) | (g << 8) | b;
            }
        }
    }
}
