/*  Sarien - A Sierra AGI resource interpreter engine
 *  Copyright (C) 1999-2001 Stuart George and Claudio Matsuoka
 *
 *  $Id: sdl.c,v 1.26 2001/09/13 02:25:53 cmatsuoka Exp $
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; see docs/COPYING for further details.
 */

/* SDL support written by Claudio Matsuoka <claudio@helllabs.org>
 */

/* Sat, 31 Jul 1999 10:32:01 +0200 (MET DST)
 * SDL full screen mode added by Robert Bihlmeyer <robbe@orcus.priv.at>
 *
 * Wed Dec 22 15:51:22 EDT 1999
 * Compile errors fixed by Ryan Gordon <ryan_gordon@hotmail.com>
 *
 * Sun, 5 Aug 2001 14:05:04 -0700 (PDT)
 * Mouse support added by Ryan C. Gordon <icculus@clutteredmind.org>
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "SDL.h"
#include "sarien.h"
#include "graphics.h"
#include "keyboard.h"

extern struct sarien_options opt;

static int scale = 1;
static int key_control = 0;
static int key_alt = 0;
static SDL_Surface *screen;

SDL_Color color[32];
Uint32 mapped_color[32];

UINT32 clock_ticks;
UINT32 clock_count;

#define KEY_QUEUE_SIZE 16

static int key_queue[KEY_QUEUE_SIZE];
static int key_queue_start = 0;
static int key_queue_end = 0;

#define key_enqueue(k) do { key_queue[key_queue_end++] = (k); \
        key_queue_end %= KEY_QUEUE_SIZE; } while (0)
#define key_dequeue(k) do { (k) = key_queue[key_queue_start++]; \
        key_queue_start %= KEY_QUEUE_SIZE; } while (0)

static int      init_vidmode    (void);
static int      deinit_vidmode  (void);
static void     sdl_put_block   (int, int, int, int);
static void     _putpixels      (int, int, int, Uint8 *);
static void     sdl_timer       (void);
static Uint32   timer_function  (Uint32);
int             sdl_is_keypress (void);
int             sdl_get_keypress(void);

static volatile UINT32 tick_timer = 0;


#define TICK_SECONDS 20


static struct gfx_driver gfx_sdl = {
        init_vidmode,
        deinit_vidmode,
        sdl_put_block,
        _putpixels,
        sdl_timer,
        sdl_is_keypress,
        sdl_get_keypress
};

extern struct gfx_driver *gfx;


#define ASPECT_RATIO(x) ((x) * 6 / 5)

/* ====================================================================*/

/* Some optimized put_pixel routines for the most common cases */

#define _putpixels_scale1(d) static void INLINE                         \
_putpixels_##d##bits_scale1 (int x, int y, int w, UINT8 *p) {           \
        Uint##d *s;                                                     \
        if (w == 0) return;                                             \
        s = (Uint##d *)screen->pixels + x + y * screen->w;              \
        if (SDL_MUSTLOCK (screen)) {                                    \
                if (SDL_LockSurface (screen) < 0)                       \
                        return;                                         \
        }                                                               \
        while (w--) { *s++ = mapped_color[*p++]; }                      \
        if (SDL_MUSTLOCK (screen)) SDL_UnlockSurface (screen);          \
}

#define _putpixels_scale2(d) static void INLINE                         \
_putpixels_##d##bits_scale2 (int x, int y, int w, UINT8 *p) {           \
        Uint##d *s, *t;                                         \
        if (w == 0) return;                                             \
        x <<= 1; y <<= 1;                                               \
        s = (Uint##d *)screen->pixels + x + y * screen->w;              \
        t = s + screen->w;                                              \
        if (SDL_MUSTLOCK (screen)) {                                    \
                if (SDL_LockSurface (screen) < 0)                       \
                        return;                                         \
        }                                                               \
        while (w--) {                                                   \
                int c = mapped_color[*p];                               \
                *s++ = c; *s++ = c; *t++ = c; *t++ = c; p++;            \
        }                                                               \
        if (SDL_MUSTLOCK (screen)) SDL_UnlockSurface (screen);          \
}

_putpixels_scale1(8);
_putpixels_scale1(16);
_putpixels_scale1(32);
_putpixels_scale2(8);
_putpixels_scale2(16);
_putpixels_scale2(32);


/* ====================================================================*/

/* Aspect ratio correcting put pixels handlers */

#define _putpixels_fixratio_scale1(d) static void                       \
_putpixels_fixratio_##d##bits_scale1 (int x, int y, int w, UINT8 *p) {  \
        if (y > 0 && ASPECT_RATIO (y) - 1 != ASPECT_RATIO (y - 1))      \
                _putpixels_##d##bits_scale1 (x, ASPECT_RATIO(y) - 1, w, p);\
        _putpixels_##d##bits_scale1 (x, ASPECT_RATIO(y), w, p);         \
}

#define _putpixels_fixratio_scale2(d) static void INLINE                \
_putpixels_fixratio_##d##bits_scale2 (int x, int y, int w, Uint8 *p0) { \
        Uint##d *s, *t, *u; Uint8 *p; int extra = 0;                    \
        if (w == 0) return;                                             \
        x <<= 1; y <<= 1;                                               \
        if (y < ((GFX_WIDTH - 1) << 2) && ASPECT_RATIO (y) + 2 != ASPECT_RATIO (y + 2)) extra = w; \
        y = ASPECT_RATIO(y);                                            \
        s = (Uint##d *)screen->pixels + x + y * screen->w;              \
        t = s + screen->w;                                              \
        u = t + screen->w;                                              \
        if (SDL_MUSTLOCK (screen)) {                                    \
                if (SDL_LockSurface (screen) < 0)                       \
                        return;                                         \
        }                                                               \
        for (p = p0; w--; p++) {                                        \
                int c = mapped_color[*p];                               \
                *s++ = c; *s++ = c; *t++ = c; *t++ = c;                 \
        }                                                               \
        for (p = p0; extra--; p++) {                                    \
                int c = mapped_color[*p];                               \
                *u++ = c; *u++ = c;                                     \
        }                                                               \
        if (SDL_MUSTLOCK (screen)) SDL_UnlockSurface (screen);          \
}

_putpixels_fixratio_scale1(8);
_putpixels_fixratio_scale1(16);
_putpixels_fixratio_scale1(32);
_putpixels_fixratio_scale2(8);
_putpixels_fixratio_scale2(16);
_putpixels_fixratio_scale2(32);

/* ====================================================================*/

/* Slow, non-optimized put pixel routine */

static void inline _put_pixel (int x, int y, int c)
{
        Uint32 pixel;
        Uint8 *bits, bpp;

        pixel = mapped_color[c];

        bpp = screen->format->BytesPerPixel;
        bits = ((UINT8 *) screen->pixels) + y * screen->pitch + x * bpp;

        /* Set the pixel */
        switch (bpp) {
        case 1:
                *(Uint8 *)(bits) = pixel;
                break;
        case 2:
                *((UINT16 *) (bits)) = (Uint16) pixel;
                break;
        case 3:{
                UINT8 r, g, b;
                r = (pixel >> screen->format->Rshift) & 0xff;
                g = (pixel >> screen->format->Gshift) & 0xff;
                b = (pixel >> screen->format->Bshift) & 0xff;
                *((bits) + screen->format->Rshift / 8) = r;
                *((bits) + screen->format->Gshift / 8) = g;
                *((bits) + screen->format->Bshift / 8) = b;
                }
                break;
        case 4:
                *((UINT32 *) (bits)) = (Uint32) pixel;
                break;
        }
}

static void _putpixels (int x, int y, int w, Uint8 *p)
{
        register int c;
        register int i, j;

        if (w == 0) return;

        if (SDL_MUSTLOCK (screen)) {
                if (SDL_LockSurface (screen) < 0)
                        return;
        }

        switch (scale) {
        case 1:
                while (w--) {
                        _put_pixel (x++, y, *p++);
                }
                break;
        case 2:
                x <<= 1;
                y <<= 1;
                while (w--) {
                        c = *p++;
                        _put_pixel (x, y, c);
                        _put_pixel (x++, y + 1, c);
                        _put_pixel (x, y, c);
                        _put_pixel (x++, y + 1, c);
                }
                break;
        default:
                x *= scale;
                y *= scale;
                while (w--) {
                        c = *p++;
                        for (i = 0; i < scale; i++) {
                                for (j = 0; j < scale; j++)
                                        _put_pixel (x + i, y + j, c);
                        }
                        x += scale;
                }
                break;
        }

        if (SDL_MUSTLOCK (screen))
                SDL_UnlockSurface (screen);
}

static void _putpixels_fixratio (int x, int y, int w, UINT8 *p)
{
        if (y > 0 && ASPECT_RATIO (y) - 1 != ASPECT_RATIO (y - 1))
                _putpixels (x, ASPECT_RATIO(y) - 1, w, p);
        _putpixels (x, ASPECT_RATIO(y), w, p);
}

/* ====================================================================*/

static void process_events ()
{
        SDL_Event event;
        int key = 0;

        while (SDL_PollEvent (&event) > 0) {
                switch (event.type) {
                case SDL_MOUSEBUTTONDOWN:
                        key = (event.button.button == 1) ?
                                BUTTON_LEFT : BUTTON_RIGHT;
                        mouse.button = TRUE;
                        key_enqueue (key);
                        /* fall through */
                case SDL_MOUSEMOTION:
                        mouse.x = event.button.x / opt.scale;
                        mouse.y = event.button.y / opt.scale;
                        if (opt.fixratio)
                                mouse.y = mouse.y * 5 / 6;
                        break;
                case SDL_MOUSEBUTTONUP:
                        mouse.button = FALSE;
                        break;
                case SDL_KEYDOWN:
                        switch (key = event.key.keysym.sym) {
                        case SDLK_LSHIFT:
                        case SDLK_RSHIFT:
                                key = 0;
                                break;
                        case SDLK_LCTRL:
                                key_control |= 1;
                                key = 0;
                                break;
                        case SDLK_RCTRL:
                                key_control |= 2;
                                key = 0;
                                break;
                        case SDLK_LALT:
                                key_alt |= 1;
                                key = 0;
                                break;
                        case SDLK_RALT:
                                key_alt |= 2;
                                key = 0;
                                break;
                        case SDLK_UP:
                        case SDLK_KP8:
                                key = KEY_UP;
                                break;
                        case SDLK_LEFT:
                        case SDLK_KP4:
                                key = KEY_LEFT;
                                break;
                        case SDLK_DOWN:
                        case SDLK_KP2:
                                key = KEY_DOWN;
                                break;
                        case SDLK_RIGHT:
                        case SDLK_KP6:
                                key = KEY_RIGHT;
                                break;
                        case SDLK_HOME:
                        case SDLK_KP7:
                                key = KEY_UP_LEFT;
                                break;
                        case SDLK_KP5:
                                key = KEY_STATIONARY;
                                break;
                        case SDLK_PAGEUP:
                        case SDLK_KP9:
                                key = KEY_UP_RIGHT;
                                break;
                        case SDLK_PAGEDOWN:
                        case SDLK_KP3:
                                key = KEY_DOWN_RIGHT;
                                break;
                        case SDLK_END:
                        case SDLK_KP1:
                                key = KEY_DOWN_LEFT;
                                break;
                        case SDLK_KP_ENTER:
                                key = KEY_ENTER;
                                break;
                        case SDLK_KP_PLUS:
                                key = '+';
                                break;
                        case SDLK_KP_MINUS:
                                key = '-';
                                break;
                        case SDLK_TAB:
                                key = 0x0009;
                                break;
                        case SDLK_F1:
                                key = 0x3b00;
                                break;
                        case SDLK_F2:
                                key = 0x3c00;
                                break;
                        case SDLK_F3:
                                key = 0x3d00;
                                break;
                        case SDLK_F4:
                                key = 0x3e00;
                                break;
                        case SDLK_F5:
                                key = 0x3f00;
                                break;
                        case SDLK_F6:
                                key = 0x4000;
                                break;
                        case SDLK_F7:
                                key = 0x4100;
                                break;
                        case SDLK_F8:
                                key = 0x4200;
                                break;
                        case SDLK_F9:
                                key = 0x4300;
                                break;
                        case SDLK_F10:
                                key = 0x4400;
                                break;
                        case SDLK_F11:
                                key = KEY_STATUSLN;
                                break;
                        case SDLK_F12:
                                key = KEY_PRIORITY;
                                break;
                        case SDLK_ESCAPE:
                                key = 0x1b;
                                break;
                        default:
                                if (!isalpha (key))
                                        break;
                                if (key_control)
                                        key = (key & ~0x20) - 0x40;
                                else if (key_alt)
                                        key = scancode_table[(key & ~0x20)
                                                - 0x41] << 8;
                                break;
                        };
                        if (key)
                                key_enqueue (key);

                        break;
                case SDL_KEYUP:
                        switch (event.key.keysym.sym) {
                        case SDLK_LCTRL:
                                key_control &= ~1;
                                break;
                        case SDLK_RCTRL:
                                key_control &= ~2;
                                break;
                        case SDLK_LALT:
                                key_alt &= ~1;
                                break;
                        case SDLK_RALT:
                                key_alt &= ~2;
                                break;
                        default:
                                break;
                        }
                        break;
                }
        }
}


int init_machine (int argc, char **argv)
{
        gfx = &gfx_sdl;
        scale = opt.scale;
        clock_count = 0;
        clock_ticks = 0;

        return err_OK;
}


int deinit_machine ()
{
        return err_OK;
}


static int init_vidmode ()
{
        int i, mode;

        fprintf (stderr, "sdl: SDL support by claudio@helllabs.org\n");

        /* Initialize SDL */
        /* OpenVMS: SDL not built with audio support - removed SDL_INIT_AUDIO */
        if (SDL_Init (SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0) {
                fprintf (stderr, "sdl: can't initialize: %s\n",
                        SDL_GetError ());
                return err_Unk;
        }

        mode = SDL_SWSURFACE | SDL_ANYFORMAT;

        if (opt.fullscreen)
                mode |= SDL_FULLSCREEN;

        if ((screen = SDL_SetVideoMode (320 * scale,
                (opt.fixratio ? ASPECT_RATIO(GFX_HEIGHT) : GFX_HEIGHT) * scale,
                8, mode)) == NULL)
        {
                fprintf (stderr, "sdl: can't set video mode: %s\n",
                        SDL_GetError ());
                return err_Unk;
        }
#ifndef OPENVMS
        /* OpenVMS: Don't register SDL_Quit - it hangs on exit */
        atexit (SDL_Quit);
#endif

        SDL_WM_SetCaption (TITLE " " VERSION, TITLE);

        tick_timer = 0;
#ifdef OPENVMS
        /* OpenVMS: Check for SARIEN_TIMER_MS environment variable
         * Default 1ms for emulated systems, can be increased for real hardware
         * Example: $ DEFINE SARIEN_TIMER_MS 5
         */
        /* OpenVMS: Don't use SDL_SetTimer - we use simple SDL_Delay instead like Hexen
         * This avoids timer callback overhead and is more efficient on ES40 emulator */
        _D("OpenVMS: Using simple SDL_Delay timing (no timer callback)");
#else
        SDL_SetTimer (10, timer_function);
#endif

        for (i = 0; i < 32; i++) {
                color[i].r = palette[i * 3] << 2;
                color[i].g = palette[i * 3 + 1] << 2;
                color[i].b = palette[i * 3 + 2] << 2;
                mapped_color[i] = SDL_MapRGB (screen->format,
                        color[i].r, color[i].g, color[i].b);
        }
        SDL_SetColors (screen, color, 0, 32);

#define handle_case(d,s) case (d/8): \
        gfx_sdl.put_pixels = _putpixels_##d##bits_scale##s; break;
#define handle_fixratio_case(d,s) case (d/8): \
        gfx_sdl.put_pixels = _putpixels_fixratio_##d##bits_scale##s; break;

        /* Use an optimized put_pixels if available */
        if (!opt.fixratio) {
                gfx_sdl.put_pixels = _putpixels_fixratio;
                if (opt.gfxhacks) switch (scale) {
                case 1:
                        switch (screen->format->BytesPerPixel) {
                        handle_case(8,1);
                        handle_case(16,1);
                        handle_case(32,1);
                        }
                        break;
                case 2:
                        switch (screen->format->BytesPerPixel) {
                        handle_case(8,2);
                        handle_case(16,2);
                        handle_case(32,2);
                        }
                        break;
                }
        } else {
                if (opt.gfxhacks) switch (scale) {
                case 1:
                        switch (screen->format->BytesPerPixel) {
                        handle_fixratio_case(8,1);
                        handle_fixratio_case(16,1);
                        handle_fixratio_case(32,1);
                        }
                        break;
                case 2:
                        switch (screen->format->BytesPerPixel) {
                        handle_fixratio_case(8,2);
                        handle_fixratio_case(16,2);
                        handle_fixratio_case(32,2);
                        }
                        break;
                }
        }

        return err_OK;
}


static int deinit_vidmode ()
{
        _D ("()");
#ifdef OPENVMS
        /* OpenVMS: Stop timer and force quick exit */
        SDL_SetTimer (0, NULL);
        /* Don't call SDL_Quit - it hangs on OpenVMS, just exit */
        return err_OK;
#else
        SDL_Quit ();
        return err_OK;
#endif
}


/* put a block onto the screen */
static void sdl_put_block (int x1, int y1, int x2, int y2)
{
        if (x1 >= GFX_WIDTH)
                x1 = GFX_WIDTH - 1;
        if (y1 >= GFX_HEIGHT)
                y1 = GFX_HEIGHT - 1;
        if (x2 >= GFX_WIDTH)
                x2 = GFX_WIDTH - 1;
        if (y2 >= GFX_HEIGHT)
                y2 = GFX_HEIGHT - 1;

        if (scale > 1) {
                x1 *= scale;
                y1 *= scale;
                x2 = (x2 + 1) * scale - 1;
                y2 = (y2 + 1) * scale - 1;
        }

        if (opt.fixratio) {
                SDL_UpdateRect (screen, x1, ASPECT_RATIO(y1), x2 - x1 + 1,
                        ASPECT_RATIO (y2 + 1) - ASPECT_RATIO(y1));
        } else {
                SDL_UpdateRect (screen, x1, y1, x2 - x1 + 1, y2 - y1 + 1);
        }
}


int sdl_is_keypress ()
{
        process_events ();
        return key_queue_start != key_queue_end;
}


int sdl_get_keypress ()
{
        int k;

        while (key_queue_start == key_queue_end)        /* block */
                sdl_timer ();
        key_dequeue(k);

        return k;
}


static Uint32 timer_function (Uint32 i)
{
        tick_timer++;
        return i;
}


static void sdl_timer ()
{
#ifdef OPENVMS
        /* OpenVMS: Simpler timing like Hexen - just delay without timer callback overhead
         * Use 20ms delay for smooth 50 FPS frame pacing */
        SDL_Delay (16);
        process_events ();
#else
        static UINT32 m = 0;
        UINT32 dm;

        if (tick_timer < m)
                m = 0;

        while ((dm = tick_timer - m) < 5) {
                SDL_Delay (5);
        }
        m = tick_timer;

        process_events ();
#endif
}

