#ifndef VGA_SHIM_H
#define VGA_SHIM_H

#include <SDL2/SDL.h>
#include <stdint.h>
#include <stdlib.h>
#include <math.h>
#include <unistd.h>

/* ---- modes ---- */
#define G800x600x256  1
#define G800x600x64K  2
#define G1024x768x16M 3
#define vga_white()     255

#define OFF_SCREEN_BUFFER_WIDTH 800
#define OFF_SCREEN_BUFFER_HEIGHT 600

/* ---- internal state ---- */

static SDL_Window   *vga_window   = NULL;
static SDL_Renderer *vga_renderer = NULL;
static SDL_Texture  *vga_texture  = NULL;
static SDL_Surface  *vga_surface  = NULL;
static uint16_t vga_palette[256] = { 0 };
static uint8_t off_screen_buffer[OFF_SCREEN_BUFFER_WIDTH*OFF_SCREEN_BUFFER_HEIGHT] = { 0 };

static int vga_mode = 0;

/* current color */
static uint8_t  vga_color8  = 0;
static uint16_t vga_color16 = 0;
static uint32_t vga_color32 = 0;

/* ---- helpers ---- */

static void vga_update(void)
{
    SDL_UpdateTexture(vga_texture, NULL,
                      vga_surface->pixels,
                      vga_surface->pitch);
    SDL_RenderClear(vga_renderer);
    SDL_RenderCopy(vga_renderer, vga_texture, NULL, NULL);
    SDL_RenderPresent(vga_renderer);
}

static void vga_putpixel(int x, int y)
{
    if (x < 0 || y < 0 ||
        x >= vga_surface->w ||
        y >= vga_surface->h)
        return;

    uint8_t *base = (uint8_t *)vga_surface->pixels;

    if (vga_mode == G800x600x256) {
        *(off_screen_buffer + y*OFF_SCREEN_BUFFER_WIDTH + x) = vga_color8;
        uint16_t *p = (uint16_t *)(base + y * vga_surface->pitch);
        p[x] = vga_palette[vga_color8];
    } else if (vga_mode == G800x600x64K) {
        uint16_t *p = (uint16_t *)(base + y * vga_surface->pitch);
        p[x] = vga_color16;
    } else { // 16M
        uint32_t *p = (uint32_t *)(base + y * vga_surface->pitch);
        p[x] = vga_color32;
    }
}

static void redraw_after_palette_update()
{
    uint8_t *base = (uint8_t *)vga_surface->pixels;
    for (int y = 0; y < OFF_SCREEN_BUFFER_HEIGHT; y++) {
        for (int x = 0; x < OFF_SCREEN_BUFFER_WIDTH; x++) {
            uint8_t color = *(off_screen_buffer + y*OFF_SCREEN_BUFFER_WIDTH + x);
            uint16_t *p = (uint16_t *)(base + y * vga_surface->pitch);
            p[x] = vga_palette[color];
        }
    }
}

static void vga_setpalette(int index, int r, int g, int b)
{
    if (vga_mode != G800x600x256)
        return;

    vga_palette[index] =
            ((r & 0xF8) << 8) |
            ((g & 0xFC) << 3) |
            ( b >> 3);
}

/* ---- API ---- */

static int vga_init(void)
{
    return SDL_Init(SDL_INIT_VIDEO);
}

static const uint8_t ega_palette[16][3] = {
    {  0,   0,   0}, {  0,   0, 170}, {  0, 170,   0}, {  0, 170, 170},
    {170,   0,   0}, {170,   0, 170}, {170,  85,   0}, {170, 170, 170},
    { 85,  85,  85}, { 85,  85, 255}, { 85, 255,  85}, { 85, 255, 255},
    {255,  85,  85}, {255,  85, 255}, {255, 255,  85}, {255, 255, 255}
};

static void vga_init_default_palette(void)
{
    /* 0–15: EGA colors */
    for (int i = 0; i < 16; i++) {
        vga_setpalette(i,
            ega_palette[i][0],
            ega_palette[i][1],
            ega_palette[i][2]);
    }

    /* 16–231: 6x6x6 RGB cube */
    int idx = 16;
    for (int r = 0; r < 6; r++) {
        for (int g = 0; g < 6; g++) {
            for (int b = 0; b < 6; b++) {
                vga_setpalette(
                    idx++,
                    r * 51,
                    g * 51,
                    b * 51
                );
            }
        }
    }

    /* 232–255: grayscale */
    for (int i = 0; i < 24; i++) {
        int gray = i * 255 / 23;
        vga_setpalette(232 + i, gray, gray, gray);
    }
}

static void vga_clear(void)
{
    SDL_FillRect(vga_surface, NULL, 0);
    memset(off_screen_buffer, 0, sizeof(off_screen_buffer));
}

static void vga_setcolor(int c)
{
    if (vga_mode == G800x600x256) {
        vga_color8 = c & 0xFF;
    } else {
        vga_color16 = c & 0xFFFF;
    }
}

static int vga_setmode(int mode)
{
    vga_mode = mode;

    if ((vga_mode == G800x600x256) ||
        (vga_mode == G800x600x64K)) {
        vga_window = SDL_CreateWindow(
            "svgalib shim",
            SDL_WINDOWPOS_CENTERED,
            SDL_WINDOWPOS_CENTERED,
            800, 600,
            0
        );
    } else {
        // G1024x768x16M
        vga_window = SDL_CreateWindow(
            "svgalib shim",
            SDL_WINDOWPOS_CENTERED,
            SDL_WINDOWPOS_CENTERED,
            1024, 768,
            0
        );
    }
    if (!vga_window) return -1;

    vga_renderer = SDL_CreateRenderer(
        vga_window, -1,
        SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC
    );
    if (!vga_renderer) return -1;

    if ((vga_mode == G800x600x256) ||
        (vga_mode == G800x600x64K)) {
        vga_surface = SDL_CreateRGBSurfaceWithFormat(
            0, 800, 600, 16, SDL_PIXELFORMAT_RGB565
        );
        vga_texture = SDL_CreateTexture(
            vga_renderer,
            SDL_PIXELFORMAT_RGB565,
            SDL_TEXTUREACCESS_STREAMING,
            800, 600
        );
    } else {
        // G1024x768x16M
        vga_surface = SDL_CreateRGBSurfaceWithFormat(
            0, 1024, 768, 32, SDL_PIXELFORMAT_RGB888
        );
        vga_texture = SDL_CreateTexture(
            vga_renderer,
            SDL_PIXELFORMAT_RGB888,
            SDL_TEXTUREACCESS_STREAMING,
            1024, 768
        );
    }
    if (vga_mode == G800x600x256) {
        vga_init_default_palette();
    }

    vga_setcolor(vga_white());
    vga_clear();

    return (vga_surface && vga_texture) ? 0 : -1;
}

static void vga_setrgbcolor(uint8_t r, uint8_t g, uint8_t b)
{
    if (vga_mode == G800x600x256) {
        fprintf(stderr, "Can't use vga_setrgbcolor in palette mode!\n");
        return;
    } else if (vga_mode == G800x600x64K) {
        /* RGB565 */
        vga_color16 =
            ((r & 0xF8) << 8) |
            ((g & 0xFC) << 3) |
            ( b >> 3);
    } else {
        /* RGB888 */
        vga_color32 = (r << 16) | (g << 8) | b;
    }
}

static void vga_drawpixel(int x, int y)
{
    vga_putpixel(x, y);
}

static void vga_drawline(int x1, int y1, int x2, int y2)
{
    int dx = abs(x2 - x1);
    int dy = abs(y2 - y1);
    int sx = (x1 < x2) ? 1 : -1;
    int sy = (y1 < y2) ? 1 : -1;
    int err = dx - dy;

    while (1) {
        vga_putpixel(x1, y1);
        if (x1 == x2 && y1 == y2) break;
        int e2 = err << 1;
        if (e2 > -dy) { err -= dy; x1 += sx; }
        if (e2 <  dx) { err += dx; y1 += sy; }
    }
}

static void vga_waitretrace(void)
{
    vga_update();
}

static void vga_close(void)
{
    if (vga_texture)  SDL_DestroyTexture(vga_texture);
    if (vga_surface)  SDL_FreeSurface(vga_surface);
    if (vga_renderer) SDL_DestroyRenderer(vga_renderer);
    if (vga_window)   SDL_DestroyWindow(vga_window);
    SDL_Quit();
}

#endif /* VGA_SHIM_H */
