Silent(1); // This is needed to suppress "Function should return val" warnings for wrappers to non-HolyC functions

// class @image
// {
//     CDC* (*FromBuffer)(U8* buffer, I64 len);
//     CDC* (*Load)(U8* filename);
//     CDC* (*Write)(U8* filename, CDC* dc);
// };
//
// @image Image;

class @image_frame
{
    CDC* dc;
    CSprite* sprite;
    I64 delay;
};

class @image_collection
{
    @image_frame** frames;
    I64 count;
    I64 current;
    I64 jiffies;
    I64 index;
    @image_collection* next;
};

I64 @image_cbgr24_to_4_bit(CBGR24* ptr, Bool dither_probability)
{
    I64 res, k;
    if (dither_probability) {
        k = RandU32;
        if (SqrI64(ptr->r) + SqrI64(ptr->g) + SqrI64(ptr->b) >= 3 * SqrI64(k.u8[0]))
            res = 8;
        else
            res = 0;
        if (ptr->r >= k.u8[1])
            res |= RED;
        if (ptr->g >= k.u8[2])
            res |= GREEN;
        if (ptr->b >= k.u8[3])
            res |= BLUE;
    } else {
        if (SqrI64(ptr->r) + SqrI64(ptr->g) + SqrI64(ptr->b) >= SqrI64(0x80)) {
            res = 8;
            if (ptr->r >= 0x80)
                res |= RED;
            if (ptr->g >= 0x80)
                res |= GREEN;
            if (ptr->b >= 0x80)
                res |= BLUE;
        } else {
            res = 0;
            if (ptr->r >= 0x40)
                res |= RED;
            if (ptr->g >= 0x40)
                res |= GREEN;
            if (ptr->b >= 0x40)
                res |= BLUE;
        }
    }
    return res;
}

#define IMAGE_DITHER_NONE 0
#define IMAGE_DITHER_NATIVE 1
#define IMAGE_DITHER_FLOYDSTEINBERG 2

U0 @image_render_4bit_floydstein(U8* buffer, I32 width, I32 height)
{
    U64 reg RDI rdi = buffer;
    U64 reg RSI rsi = width;
    U64 reg RDX rdx = height;
    no_warn rdi, rsi, rdx;
    asm {
        MOV RAX, RENDER_4BIT_FLOYDSTEIN
        CALL RAX
    }
}

CDC* @image_render_16color_native(U8* pixels, I32 x, I32 y, Bool dither)
{
    I64 i;
    I64 j;
    I64 cnt = 0;
    CBGR24 cbgr24;
    CDC* dc = DCNew(x, y);
    for (i = 0; i < y; i++)
        for (j = 0; j < x; j++) {
            cbgr24.r = pixels[cnt];
            cbgr24.g = pixels[cnt + 1];
            cbgr24.b = pixels[cnt + 2];
            if (!pixels[cnt + 3])
                dc->color = TRANSPARENT;
            else
                dc->color = @image_cbgr24_to_4_bit(&cbgr24, dither);
            GrPlot(dc, j, y - i - 1);
            cnt += 4;
        }
    return dc;
}

CBGR24 @image_palette_std[COLORS_NUM] = {
    0x000000, 0x0000AA, 0x00AA00, 0x00AAAA,
    0xAA0000, 0xAA00AA, 0xAA5500, 0xAAAAAA,
    0x555555, 0x5555FF, 0x55FF55, 0x55FFFF,
    0xFF5555, 0xFF55FF, 0xFFFF55, 0xFFFFFF
};

CBGR24 @image_dif_rgb(CBGR24 from, CBGR24 to)
{
    CBGR24 dif;
    dif.r = to.r - from.r;
    dif.g = to.g - from.g;
    dif.b = to.b - from.b;
    return dif;
}

F64 @image_dist_rgb(CBGR24 from, CBGR24 to)
{
    CBGR24 dif = @image_dif_rgb(from, to);
    F64 dist = dif.r * dif.r + dif.g * dif.g + dif.b * dif.b;
    return dist;
}

I64 @image_get_4bit_color(CBGR24* cbgr24)
{
    F64 dist = -1, tempDist;
    I64 i;
    I64 color = TRANSPARENT;
    for (i = 0; i < COLORS_NUM; i++) {
        tempDist = @image_dist_rgb(*cbgr24, @image_palette_std[i]);
        if (tempDist < dist || dist < 0) {
            dist = tempDist;
            color = i;
        }
    }
    return color;
}

CDC* @image_render_16color_floydsteinberg(U8* pixels, I32 width, I32 height)
{
    @image_render_4bit_floydstein(pixels, width, height);
    I64 i;
    I64 j;
    I64 cnt = 0;
    CBGR24 cbgr24;
    CDC* dc = DCNew(width, height);
    for (i = 0; i < height; i++)
        for (j = 0; j < width; j++) {
            cbgr24.r = pixels[cnt];
            cbgr24.g = pixels[cnt + 1];
            cbgr24.b = pixels[cnt + 2];
            if (!pixels[cnt + 3])
                dc->color = TRANSPARENT;
            else
                dc->color = @image_get_4bit_color(&cbgr24);
            GrPlot(dc, j, height - i - 1);
            cnt += 4;
        }
    return dc;
}

CDC* @image_generate_dc_from_pixels(U8* pixels, I32 width, I32 height, Bool dither = IMAGE_DITHER_FLOYDSTEINBERG)
{
    switch (dither) {
    case IMAGE_DITHER_NONE:
    case IMAGE_DITHER_NATIVE:
        return @image_render_16color_native(pixels, width, height, dither);
        break;
    case IMAGE_DITHER_FLOYDSTEINBERG:
        return @image_render_16color_floydsteinberg(pixels, width, height);
        break;
    default:
        break;
    }
    return NULL;
}

U8* @image_load_gif_from_memory(U8* buffer, I64 len, I64** delays, I64* x, I64* y,
    I64* z)
{
    U64 reg RDI rdi = buffer;
    U64 reg RSI rsi = len;
    U64 reg RDX rdx = delays;
    U64 reg RCX rcx = x;
    U64 reg R8 r8 = y;
    U64 reg R9 r9 = z;
    no_warn rdi, rsi, rdx, rcx, r8, r9;
    asm {
        MOV RAX, IMAGE_LOAD_GIF_FROM_MEMORY
        CALL RAX
    }
}

U8* @stbi_failure_reason()
{
    asm {
        MOV RAX, STBI_FAILURE_REASON
        CALL RAX
    }
}

I32 @stbi_info_from_memory(U8* buffer, I64 len, I64* x, I64* y, I64* comp)
{
    U64 reg RDI rdi = buffer;
    U64 reg RSI rsi = len;
    U64 reg RDX rdx = x;
    U64 reg RCX rcx = y;
    U64 reg R8 r8 = comp;
    no_warn rdi, rsi, rdx, rcx, r8;
    asm {
        MOV RAX, STBI_INFO_FROM_MEMORY
        CALL RAX
    }
}

U8* @stbi_load_from_memory(U8* buffer, I64 len, I64* x, I64* y,
    I64* channels_in_file, I64 desired_channels)
{
    U64 reg RDI rdi = buffer;
    U64 reg RSI rsi = len;
    U64 reg RDX rdx = x;
    U64 reg RCX rcx = y;
    U64 reg R8 r8 = channels_in_file;
    U64 reg R9 r9 = desired_channels;
    no_warn rdi, rsi, rdx, rcx, r8, r9;
    asm {
        MOV RAX, STBI_LOAD_FROM_MEMORY
        CALL RAX
    }
}

U32* @stbi_write_png_to_mem(U32* pixels, I32 stride_bytes, I32 x, I32 y, I32 n, I32* out_len)
{
    U64 reg RDI rdi = pixels;
    U64 reg RSI rsi = stride_bytes;
    U64 reg RDX rdx = x;
    U64 reg RCX rcx = y;
    U64 reg R8 r8 = n;
    U64 reg R9 r9 = out_len;
    no_warn rdi, rsi, rdx, rcx, r8, r9;
    asm {
        MOV RAX, STBI_WRITE_PNG_TO_MEM
        CALL RAX
    }
}

CDC* @image_load(U8* filename)
{
    if (!filename || !FileFind(filename)) {
        // PrintErr("Image file not found.\n");
        return NULL;
    }
    I64 len;
    I32 x;
    I32 y;
    I32 comp;
    U8* buffer = FileRead(filename, &len);
    I32 code = @stbi_info_from_memory(buffer, len, &x, &y, &comp);
    if (code != 1) {
        Free(buffer);
        return NULL;
    }
    U8* pixels = @stbi_load_from_memory(buffer, len, &x, &y, &comp, 4);
    Free(buffer);
    CDC* dc = @image_generate_dc_from_pixels(pixels, x, y);
    Free(pixels);
    return dc;
}

U32 @image_rgba_color_table[16] = {
    0xff000000, 0xffaa0000, 0xff00aa00, 0xffaaaa00,
    0xff0000aa, 0xffaa00aa, 0xff0055aa, 0xffaaaaaa,
    0xff555555, 0xffff5555, 0xff55ff55, 0xffffff55,
    0xff5555ff, 0xffff55ff, 0xff55ffff, 0xffffffff
};

U32 @image_get_rgba_color(I64 color)
{
    if (color > 15)
        return 0;
    return @image_rgba_color_table[color];
}

U32* @image_get_rgba_buffer_from_dc_body(CDC* dc)
{
    if (!dc)
        return NULL;
    U32* pixels = CAlloc((dc->width * dc->height) * 4, erythros_mem_task);
    I64 x;
    I64 y;
    I64 p = 0;
    for (y = 0; y < dc->height; y++)
        for (x = 0; x < dc->width; x++)
            pixels[p++] = @image_get_rgba_color(GrPeek(dc, x, y));
    return pixels;
}

U0 @image_write(U8* filename, CDC* dc)
{
    if (!dc) {
        PrintErr("Device context is NULL.\n");
        return;
    }
    I32 out_len;
    U32* rgba_buffer = @image_get_rgba_buffer_from_dc_body(dc);
    if (!rgba_buffer) {
        PrintErr("RGBA buffer is NULL.\n");
        return;
    }
    U8* png_buffer = @stbi_write_png_to_mem(rgba_buffer, dc->width * 4, dc->width, dc->height, 4, &out_len);
    if (!png_buffer) {
        PrintErr("PNG buffer is NULL.\n");
        Free(rgba_buffer);
        return;
    }
    FileWrite(filename, png_buffer, out_len);
    Free(rgba_buffer);
    Free(png_buffer);
}

U32 @image_pixel_flip_rgb_bgr(U32 src)
{
    U32 dst;
    dst.u8[0] = src.u8[2];
    dst.u8[1] = src.u8[1];
    dst.u8[2] = src.u8[0];
    dst.u8[3] = src.u8[3];
    return dst;
}

CDC* @image_from_buffer(U8* buffer, I64 len)
{
    I32 x = 0;
    I32 y = 0;
    U8* pixels = NULL;
    CDC* dc = NULL;

    I32 comp;
    I32 code = @stbi_info_from_memory(buffer, len, &x, &y, &comp);
    if (code != 1) {
        return NULL;
    }
    pixels = @stbi_load_from_memory(buffer, len, &x, &y, &comp, 4);
    if (!pixels)
        PopUpOk(@stbi_failure_reason);
    dc = @image_generate_dc_from_pixels(pixels, x, y);
    Free(pixels);
    return dc;
}

@image_collection* @image_collection_from_buffer(U8* buffer, I64 len)
{
    I64 i;
    I32* delays;
    I32 x;
    I32 y;
    I32 z;
    I32 comp;
    I32 code = @stbi_info_from_memory(buffer, len, &x, &y, &comp);
    if (code != 1) {
        return NULL;
    }
    U64 pixels = @image_load_gif_from_memory(buffer, len, &delays, &x, &y, &z);
    if (!pixels)
        PopUpOk(@stbi_failure_reason);
    if (!z)
        return NULL; // no frames?
    @image_collection* collection = CAlloc(sizeof(@image_collection), erythros_mem_task);
    @image_frame* frame;
    collection->frames = CAlloc(sizeof(@image_frame*) * z, erythros_mem_task);
    collection->count = z;
    for (i = 0; i < z; i++) {
        frame = CAlloc(sizeof(@image_frame), erythros_mem_task);
        frame->dc = @image_generate_dc_from_pixels(pixels, x, y);
        frame->sprite = DC2Sprite(frame->dc);
        frame->delay = delays[i];
        collection->frames[i] = frame;
        pixels += (x * y) * 4;
    }
    return collection;
}

// Image.FromBuffer = &@image_from_buffer;
// Image.Load = &@image_load;
// Image.Write = &@image_write;

Silent(0);

U0 Screenshot(U8* custom_filename = NULL, Bool output_filename_to_focus_task = FALSE)
{
    CDC* dc = DCScrnCapture;
    U8 filename[256];
    CDateStruct ds;
    if (custom_filename)
        StrCpy(filename, custom_filename);
    else {
        Date2Struct(&ds, Now);
        StrPrint(filename, "C:/Tmp/ScrnShots/%04d-%02d-%02d-%02d-%02d-%02d.png", ds.year, ds.mon, ds.day_of_mon, ds.hour, ds.min, ds.sec);
    }
    @image_write(filename, dc);
    DCDel(dc);
    if (output_filename_to_focus_task)
        XTalk(sys_focus_task, filename);
};

U0 @screenshot_hotkey(I64)
{
    Screenshot("C:/Home/Screenshot.png", TRUE);
}

CtrlAltCBSet('S', &@screenshot_hotkey, "", , FALSE);
