//
// BME (Blasphemous Multimedia Engine) graphics main module
//

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <SDL3/SDL.h>

#include "bme_main.h"
#include "bme_cfg.h"
#include "bme_win.h"
#include "bme_io.h"
#include "bme_err.h"

// Prototypes

int gfx_init(unsigned xsize, unsigned ysize, unsigned framerate, unsigned flags);
int gfx_reinit(void);
void gfx_uninit(void);
int gfx_lock(void);
void gfx_unlock(void);
void gfx_flip(void);
void gfx_setclipregion(unsigned left, unsigned top, unsigned right, unsigned bottom);
void gfx_setmaxspritefiles(unsigned num);
void gfx_setmaxcolors(int num);
int gfx_loadpalette(char *name);
void gfx_calcpalette(int fade, int radd, int gadd, int badd);
void gfx_setpalette(void);
int gfx_loadblocks(char *name);
int gfx_loadsprites(unsigned num, char *name);
void gfx_freesprites(unsigned num);

void gfx_drawsprite(int x, int y, unsigned num);
void gfx_getspriteinfo(unsigned num);

int gfx_initted = 0;
int gfx_redraw = 0;
int gfx_scanlinemode = 0;
int gfx_preventswitch = 0;
unsigned gfx_virtualxsize;
unsigned gfx_virtualysize;
unsigned gfx_windowxsize;
unsigned gfx_windowysize;
int gfx_blockxsize = 16;
int gfx_blockysize = 16;
int spr_xsize = 0;
int spr_ysize = 0;
int spr_xhotspot = 0;
int spr_yhotspot = 0;
unsigned gfx_nblocks = 0;
Uint8 gfx_palette[MAX_COLORS * 3] = {0};
SDL_Surface *gfx_screen = NULL;
SDL_Renderer *gfx_renderer = NULL;

// Static variables

static int gfx_initexec = 0;
static unsigned gfx_last_xsize;
static unsigned gfx_last_ysize;
static unsigned gfx_last_framerate;
static unsigned gfx_last_flags;
static int gfx_cliptop;
static int gfx_clipbottom;
static int gfx_clipleft;
static int gfx_clipright;
static int gfx_maxcolors = MAX_COLORS;
static unsigned gfx_maxspritefiles = 0;
static SPRITEHEADER **gfx_spriteheaders = NULL;
static Uint8 **gfx_spritedata = NULL;
static unsigned *gfx_spriteamount = NULL;
static SDL_Color gfx_sdlpalette[MAX_COLORS];
static int gfx_locked = 0;
static SDL_Texture *sdlTexture = NULL;

int gfx_init(unsigned xsize, unsigned ysize, unsigned framerate, unsigned flags)
{
    // Prevent re-entry (by window procedure)
    if (gfx_initexec) return BME_OK;
    gfx_initexec = 1;

    gfx_last_xsize = xsize;
    gfx_last_ysize = ysize;
    gfx_last_framerate = framerate;
    gfx_last_flags = flags & ~(GFX_FULLSCREEN | GFX_WINDOW);

    // Store the options contained in the flags

    gfx_scanlinemode = flags & (GFX_SCANLINES | GFX_DOUBLESIZE);

    if (flags & GFX_NOSWITCHING) gfx_preventswitch = 1;
        else gfx_preventswitch = 0;

    SDL_SetWindowFullscreen(win_window, win_fullscreen);

    // Calculate virtual window size

    gfx_virtualxsize = xsize;
    gfx_virtualxsize /= 16;
    gfx_virtualxsize *= 16;
    gfx_virtualysize = ysize;

    if ((!gfx_virtualxsize) || (!gfx_virtualysize))
    {
        gfx_initexec = 0;
        gfx_uninit();
        bme_error = BME_ILLEGAL_CONFIG;
        return BME_ERROR;
    }

    // Calculate actual window size (for scanline mode & doublesize mode
    // this is double the virtual)

    gfx_windowxsize = gfx_virtualxsize;
    gfx_windowysize = gfx_virtualysize;
    if (gfx_scanlinemode)
    {
        gfx_windowxsize <<= 1;
        gfx_windowysize <<= 1;
    }

    gfx_setclipregion(0, 0, gfx_virtualxsize, gfx_virtualysize);

    // Colors 0 & 255 are always black & white
    gfx_sdlpalette[0].r = 0;
    gfx_sdlpalette[0].g = 0;
    gfx_sdlpalette[0].b = 0;
    gfx_sdlpalette[0].a = 255;
    gfx_sdlpalette[255].r = 255;
    gfx_sdlpalette[255].g = 255;
    gfx_sdlpalette[255].b = 255;
    gfx_sdlpalette[255].a = 255;

    gfx_renderer = SDL_CreateRenderer(win_window, NULL);
    gfx_screen = SDL_CreateSurface(xsize, ysize, SDL_PIXELFORMAT_INDEX8);
    sdlTexture = SDL_CreateTexture(gfx_renderer,
                                             SDL_PIXELFORMAT_RGBA32,
                                             SDL_TEXTUREACCESS_STREAMING,
                                             xsize, ysize);

    gfx_initexec = 0;
    if (gfx_screen)
    {
        gfx_initted = 1;
        gfx_redraw = 1;
        gfx_setpalette();
        win_setmousemode(win_mousemode);
        return BME_OK;
    }
    else return BME_ERROR;
}

int gfx_reinit(void)
{
    gfx_uninit();
    return gfx_init(gfx_last_xsize, gfx_last_ysize, gfx_last_framerate,
        gfx_last_flags);
}

void gfx_uninit(void)
{
    SDL_DestroyTexture(sdlTexture);
    SDL_DestroySurface(gfx_screen);
    SDL_DestroyRenderer(gfx_renderer);
    gfx_initted = 0;
    return;
}

int gfx_lock(void)
{
    if (gfx_locked) return 1;
    if (!gfx_initted) return 0;
    if (SDL_LockSurface(gfx_screen))
    {
        gfx_locked = 1;
        return 1;
    }
    else return 0;
}

void gfx_unlock(void)
{
    if (gfx_locked)
    {
        SDL_UnlockSurface(gfx_screen);
        gfx_locked = 0;
    }
}

void gfx_flip()
{
    SDL_Surface* surf = SDL_ConvertSurface(gfx_screen, SDL_PIXELFORMAT_RGBA32);
    SDL_UpdateTexture(sdlTexture, NULL, surf->pixels, surf->pitch);
    SDL_DestroySurface(surf);
    SDL_RenderClear(gfx_renderer);
    SDL_RenderTexture(gfx_renderer, sdlTexture, NULL, NULL);
    SDL_RenderPresent(gfx_renderer);
    gfx_redraw = 0;
}

void gfx_setmaxcolors(int num)
{
    gfx_maxcolors = num;
}

int gfx_loadpalette(char *name)
{
    int handle;

    handle = io_open(name);
    if (handle == -1)
    {
        bme_error = BME_OPEN_ERROR;
        return BME_ERROR;
    }
    if (io_read(handle, gfx_palette, sizeof gfx_palette) != sizeof gfx_palette)
    {
        bme_error = BME_READ_ERROR;
        io_close(handle);
        return BME_ERROR;
    }

    io_close(handle);
    gfx_calcpalette(64, 0, 0, 0);
    bme_error = BME_OK;
    return BME_OK;
}

void gfx_calcpalette(int fade, int radd, int gadd, int badd)
{
    Uint8  *sptr = &gfx_palette[3];
    int c, cl;
    if (radd < 0) radd = 0;
    if (gadd < 0) gadd = 0;
    if (badd < 0) badd = 0;

    for (c = 1; c < 255; c++)
    {
        cl = *sptr;
        cl *= fade;
        cl >>= 6;
        cl += radd;
        if (cl > 63) cl = 63;
        if (cl < 0) cl = 0;
        gfx_sdlpalette[c].r = (cl << 2) | (cl & 3);
        sptr++;

        cl = *sptr;
        cl *= fade;
        cl >>= 6;
        cl += gadd;
        if (cl > 63) cl = 63;
        if (cl < 0) cl = 0;
        gfx_sdlpalette[c].g = (cl << 2) | (cl & 3);
        sptr++;

        cl = *sptr;
        cl *= fade;
        cl >>= 6;
        cl += badd;
        if (cl > 63) cl = 63;
        if (cl < 0) cl = 0;
        gfx_sdlpalette[c].b = (cl << 2) | (cl & 3);
        sptr++;

        gfx_sdlpalette[c].a = 255;
    }
}

void gfx_setpalette(void)
{
    if (!gfx_initted) return;

    SDL_Palette *palette = SDL_CreateSurfacePalette(gfx_screen);
    SDL_SetPaletteColors(palette, &gfx_sdlpalette[0], 0, gfx_maxcolors);
}

void gfx_setclipregion(unsigned left, unsigned top, unsigned right, unsigned bottom)
{
    if (left >= right) return;
    if (top >= bottom) return;
    if (left >= gfx_virtualxsize) return;
    if (top >= gfx_virtualysize) return;
    if (right > gfx_virtualxsize) return;
    if (bottom > gfx_virtualysize) return;

    gfx_clipleft = left;
    gfx_clipright = right;
    gfx_cliptop = top;
    gfx_clipbottom = bottom;
}

void gfx_setmaxspritefiles(unsigned num)
{
    if (gfx_spriteheaders) return;

    gfx_spriteheaders = malloc(num * sizeof(Uint8 *));
    gfx_spritedata = malloc(num * sizeof(Uint8 *));
    gfx_spriteamount = malloc(num * sizeof(unsigned));
    if ((gfx_spriteheaders) && (gfx_spritedata) && (gfx_spriteamount))
    {
        unsigned c;

        gfx_maxspritefiles = num;
        for (c = 0; c < num; c++)
        {
            gfx_spriteamount[c] = 0;
            gfx_spritedata[c] = NULL;
            gfx_spriteheaders[c] = NULL;
        }
    }
    else gfx_maxspritefiles = 0;
}

int gfx_loadsprites(unsigned num, char *name)
{
    unsigned c;
    int handle, size;
    int datastart;

    if (!gfx_spriteheaders)
    {
        gfx_setmaxspritefiles(DEFAULT_MAX_SPRFILES);
    }

    bme_error = BME_OPEN_ERROR;
    if (num >= gfx_maxspritefiles) return BME_ERROR;

    gfx_freesprites(num);

    handle = io_open(name);
    if (handle == -1) return BME_ERROR;

    size = io_lseek(handle, 0, SEEK_END);
    io_lseek(handle, 0, SEEK_SET);

    gfx_spriteamount[num] = io_readle32(handle);

    gfx_spriteheaders[num] = malloc(gfx_spriteamount[num] * sizeof(SPRITEHEADER));

    if (!gfx_spriteheaders[num])
    {
        bme_error = BME_OUT_OF_MEMORY;    
        io_close(handle);
        return BME_ERROR;
    }

    for (c = 0; c < gfx_spriteamount[num]; c++)
    {
        SPRITEHEADER *hptr = gfx_spriteheaders[num] + c;

        hptr->xsize = io_readle16(handle);
        hptr->ysize = io_readle16(handle);
        hptr->xhot = io_readle16(handle);
        hptr->yhot = io_readle16(handle);
        hptr->offset = io_readle32(handle);
    }

    datastart = io_lseek(handle, 0, SEEK_CUR);
    gfx_spritedata[num] = malloc(size - datastart);
    if (!gfx_spritedata[num])
    {
        bme_error = BME_OUT_OF_MEMORY;    
        io_close(handle);
        return BME_ERROR;
    }
    io_read(handle, gfx_spritedata[num], size - datastart);
    io_close(handle);
    bme_error = BME_OK;
    return BME_OK;
}

void gfx_freesprites(unsigned num)
{
    if (num >= gfx_maxspritefiles) return;

    if (gfx_spritedata[num])
    {
        free(gfx_spritedata[num]);
        gfx_spritedata[num] = NULL;
    }
    if (gfx_spriteheaders[num])
    {
        free(gfx_spriteheaders[num]);
        gfx_spriteheaders[num] = NULL;
    }
}

void gfx_copyscreen8(Uint8  *destaddress, Uint8  *srcaddress, unsigned pitch)
{
    unsigned c;
    int d;

    switch(gfx_scanlinemode)
    {
        default:
        for (c = 0; c < gfx_virtualysize; c++)
        {
            memcpy(destaddress, srcaddress, gfx_virtualxsize);
            destaddress += pitch;
            srcaddress += gfx_virtualxsize;
        }
        break;

        case GFX_SCANLINES:
        for (c = 0; c < gfx_virtualysize; c++)
        {
            d = gfx_virtualxsize;
            while (d--)
            {
                *destaddress = *srcaddress;
                destaddress++;
                *destaddress = *srcaddress;
                destaddress++;
                srcaddress++;
            }
            destaddress += pitch*2 - (gfx_virtualxsize << 1);
        }
        break;

        case GFX_DOUBLESIZE:
        for (c = 0; c < gfx_virtualysize; c++)
        {
            d = gfx_virtualxsize;
            while (d--)
            {
                *destaddress = *srcaddress;
                destaddress++;
                *destaddress = *srcaddress;
                destaddress++;
                srcaddress++;
            }
            destaddress += pitch - (gfx_virtualxsize << 1);
            srcaddress -= gfx_virtualxsize;
            d = gfx_virtualxsize;
            while (d--)
            {
                *destaddress = *srcaddress;
                destaddress++;
                *destaddress = *srcaddress;
                destaddress++;
                srcaddress++;
            }
            destaddress += pitch - (gfx_virtualxsize << 1);
        }
        break;
    }
}


void gfx_getspriteinfo(unsigned num)
{
    unsigned sprf = num >> 16;
    unsigned spr = (num & 0xffff) - 1;
    SPRITEHEADER *hptr;

    if ((sprf >= gfx_maxspritefiles) || (!gfx_spriteheaders[sprf]) ||
        (spr >= gfx_spriteamount[sprf])) hptr = NULL;
    else hptr = gfx_spriteheaders[sprf] + spr;

    if (!hptr)
    {
        spr_xsize = 0;
        spr_ysize = 0;
        spr_xhotspot = 0;
        spr_yhotspot = 0;
        return;
    }

    spr_xsize = hptr->xsize;
    spr_ysize = hptr->ysize;
    spr_xhotspot = hptr->xhot;
    spr_yhotspot = hptr->yhot;
}

void gfx_drawsprite(int x, int y, unsigned num)
{
    unsigned sprf = num >> 16;
    unsigned spr = (num & 0xffff) - 1;
    SPRITEHEADER *hptr;

    Uint8 *sptr;
    Uint8 *dptr;
    int cx;

    if (!gfx_initted) return;
    if (!gfx_locked) return;

    if ((sprf >= gfx_maxspritefiles) || (!gfx_spriteheaders[sprf]) ||
        (spr >= gfx_spriteamount[sprf]))
    {
        spr_xsize = 0;
        spr_ysize = 0;
        spr_xhotspot = 0;
        spr_yhotspot = 0;
        return;
    }
    else hptr = gfx_spriteheaders[sprf] + spr;

    sptr = gfx_spritedata[sprf] + hptr->offset;
    spr_xsize = hptr->xsize;
    spr_ysize = hptr->ysize;
    spr_xhotspot = hptr->xhot;
    spr_yhotspot = hptr->yhot;

    x -= spr_xhotspot;
    y -= spr_yhotspot;

    if (x >= gfx_clipright) return;
    if (y >= gfx_clipbottom) return;
    if (x + spr_xsize <= gfx_clipleft) return;
    if (y + spr_ysize <= gfx_cliptop) return;

    while (y < gfx_cliptop)
    {
        int dec = *sptr++;
        if (dec == 255)
        {
            if (!(*sptr)) return;
            y++;
        }
        else
        {
            if (dec < 128)
            {
                sptr += dec;
            }
        }
    }
    while (y < gfx_clipbottom)
    {
        int dec;
        cx = x;
        dptr = (Uint8*)gfx_screen->pixels + y * gfx_screen->pitch + x;

        for (;;)
        {
            dec = *sptr++;

            if (dec == 255)
            {
                if (!(*sptr)) return;
                y++;
                break;
            }
            if (dec < 128)
            {
                if ((cx + dec <= gfx_clipleft) || (cx >= gfx_clipright))
                {
                    goto SKIP;
                }
                if (cx < gfx_clipleft)
                {
                    dec -= (gfx_clipleft - cx);
                    sptr += (gfx_clipleft - cx);
                    dptr += (gfx_clipleft - cx);
                    cx = gfx_clipleft;
                }
                while ((cx < gfx_clipright) && (dec))
                {
                    *dptr = *sptr;
                    cx++;
                    sptr++;
                    dptr++;
                    dec--;
                }
                SKIP:
                cx += dec;
                sptr += dec;
                dptr += dec;
            }
            else
            {
                cx += (dec & 0x7f);
                dptr += (dec & 0x7f);
            }
        }
    }
}


