// Linux driver
// Supports X, SVGAlib, Glide, Fbcon, KGI (and AALib :) ++ via libGGI
// And X11 via OpenPTC library
// Heavily based on the original Win32 code
// By Jarno Paananen <jpaana@s2.org>

//#define USE_GGI
//#define USE_PTC
#define USE_SDL

#if (defined(USE_PTC) + defined(USE_GGI) + defined(USE_SDL)) != 1
#error Define only one of USE_GGI, USE_PTC or USE_SDL
#endif
#if !defined(USE_PTC) && !defined(USE_GGI) && !defined(USE_SDL)
#error You have to define one of USE_GGI, USE_PTC or USE_SDL
#endif

#ifndef USE_SDL
#include <sys/time.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>
#include <stdarg.h>
#include "midasdll.h"
#include "ixalance.h"
#ifdef USE_GGI
#include <ggi/ggi.h>
#endif
#ifdef USE_PTC
#include <ptc/ptc.h>
#endif
#ifdef USE_SDL
#include "SDL.h"
#endif

typedef struct {
    int x;
    int y;
    int depth;
    int rmask;
    int bmask;
    int gmask;
    int rcomp;
    int gcomp;
    int bcomp;
} Tgfxmodelist;
Tgfxmodelist gfxmodelist[256];
int numgfxmodes;

static FILE* debugfp;

// Setting
int Setting_Debugfile = 0;
int Setting_Doublesize = 0;

typedef unsigned int dword;

//static void *g_lastBufferPtr = NULL;
static int g_lastWidth = 0, g_lastHeight = 0;
//static int ClearCount;

//static int timepaused;
//static int pausetime;
//static int holdtime;

int paused;
static int showfps = 0;
static int stretch = 0;

#ifdef USE_GGI
/* GGI Stuff */
ggi_visual_t vis;
ggi_visual_t mvis;
ggi_mode mode;
const ggi_directbuffer *db;
static int async = 1;
#endif

#ifdef USE_PTC
Surface *surf = NULL;
Console con;
#endif

#ifdef USE_SDL
SDL_Surface *screen = NULL;
int fullscreen = 0;
#endif

int fx = 0, fy = 0;
int sx = 0, sy = 0;


char *usage =
        "iXalance Loader 0.5, " __DATE__ "\n"
        "Original Win32 version by Jurjen Katsman\n"
        "Linux version by Jarno Paananen\n\n"
        "Usage:\tixalance [options] <filename> [options]\n"
        "Options:\n"
        "\t-mx\tMixing rate x Hz\n"
        "\t-ox\tOutput mode (8=8-bit, 1=16-bit, s=stereo, m=mono)\n"
        "\t-fx\tUse filter (0 = none, 1=less, 2=more)\n"
        "\t-i \tUse oversampling (interpolation)\n"
        "\t-sXxY\tForce screen mode to X x Y\n"
        "\t-d\tCreate a debug file called debug.txt\n"
        "\t-t\tPrints frames per second values to stdout\n"
#if defined(USE_GGI)
        "\t-a\tDon't use LibGGI in asynchronous mode\n";
#elif defined(USE_SDL)
        "\t-F\tUse full-screen mode\n";
#else
        "";
#endif

void IXDRV_OutputDebug(const char *fmt, ...) {
    char string[100];
    va_list bla;

    va_start(bla, fmt);

    vsprintf(string, fmt, bla);

//    if (Setting_Debugfile) {
//        fprintf(debugfp, string);
//        fflush(debugfp);
//    }
    va_end(bla);
}

void IXDRV_InitScreen() {
    void* lfb;


#ifdef USE_GGI

    ggiSetGraphMode(mvis, g_lastWidth, g_lastHeight,
            g_lastWidth, g_lastHeight, GT_16BIT);

    db = ggiDBGetBuffer(mvis, 0);
    lfb = db->write;
#endif

#ifdef USE_PTC
    if (surf) {
        surf->unlock();
        delete surf;
    }
    Format form(16, 0xF800, 0x7E0, 0x1F);

    surf = new Surface(g_lastWidth, g_lastHeight, form);
    lfb = surf->lock();
#endif

#ifdef USE_SDL
    screen = SDL_SetVideoMode(g_lastWidth, g_lastHeight, 16,
            SDL_SWSURFACE | fullscreen);

    if (!screen) {
        IXDRV_OutputDebug("Can't setup screen (%i, %i)!\n", g_lastWidth,
                g_lastHeight);
        exit(1);
    }
    SDL_ShowCursor(0);

    if (SDL_MUSTLOCK(screen)) {
        if (SDL_LockSurface(screen) < 0) {
            IXDRV_OutputDebug("Can't lock surface!\n");
            exit(1);
        }
    }
    lfb = screen->pixels;
#endif
    gfxmodeinfo.tclfb = lfb;
    gfxmodeinfo.htclfb = lfb;
    gfxmodeinfo.gfxlfb = lfb;

#ifdef USE_SDL
    gfxmodeinfo.tcxres = screen->w;
    gfxmodeinfo.tcyres = screen->h;
    gfxmodeinfo.tcscanlen = screen->pitch;
    gfxmodeinfo.rcomp = screen->format->Rshift;
    gfxmodeinfo.rmask = (8 - screen->format->Rloss);
    gfxmodeinfo.gcomp = screen->format->Gshift;
    gfxmodeinfo.gmask = (8 - screen->format->Gloss);
    gfxmodeinfo.bcomp = screen->format->Bshift;
    gfxmodeinfo.bmask = (8 - screen->format->Bloss);
#else
    gfxmodeinfo.tcxres = g_lastWidth;
    gfxmodeinfo.tcyres = g_lastHeight;
    gfxmodeinfo.tcscanlen = g_lastWidth * 2;
    gfxmodeinfo.rcomp = 11;
    gfxmodeinfo.rmask = 5;
    gfxmodeinfo.gcomp = 5;
    gfxmodeinfo.gmask = 6;
    gfxmodeinfo.bcomp = 0;
    gfxmodeinfo.bmask = 5;
#endif
};

void IXDRV_CheckMessages() {
#ifdef USE_GGI
    if (ggiKbhit(vis)) {
        if (ggiGetc(vis) == GIIUC_Escape) // ESC
            BreakPart();
    }
#endif
#ifdef USE_PTC
    if (con.key()) {
        Key key = con.read();
        if (key.code() == Key::ESCAPE) // ESC
            BreakPart();
    }
#endif
#ifdef USE_SDL
    SDL_Event ev;

    while (SDL_PollEvent(&ev)) {
        switch (ev.type) {
            case SDL_QUIT:
                BreakPart();
                break;
            case SDL_KEYDOWN:
                if (ev.key.keysym.sym == SDLK_ESCAPE)
                    BreakPart();
                break;
        }
    }
#endif
};

int IXDRV_SetVideoResolution(int x, int y, int d) {
    // no action required if we're already in this mode
    if ((x == g_lastWidth) && (y == g_lastHeight)) return 1;

    if (fx == 0 || fy == 0) {
#ifdef USE_GGI
        int err = ggiCheckGraphMode(vis, x, y, x, y, GT_16BIT, &mode);
        if (err) {
            IXDRV_OutputDebug("Can't setup screen (%i, %i, %i)!\n", x, y, d);
            //            exit(1);
        }
#endif
#ifdef USE_PTC
        Format form(16, 0xF800, 0x7E0, 0x1F);
        con.open("iXalance/Linux", x, y, form);
#endif
#ifdef USE_SDL
        if (!SDL_VideoModeOK(x, y, 16, SDL_SWSURFACE | fullscreen))
            IXDRV_OutputDebug("Can't setup screen (%i, %i)!\n", x, y);
#endif
        sx = x;
        sy = y;
    } else {
#ifdef USE_GGI
        int err = ggiCheckGraphMode(vis, fx, fy, fx, fy, GT_16BIT, &mode);
        if (err) {
            IXDRV_OutputDebug("Can't setup screen (%i, %i, %i)!\n", fx, fy, d);
            //            exit(1);
        }
#endif
#ifdef USE_PTC
        Format form(16, 0xF800, 0x7E0, 0x1F);
        con.open("iXalance/Linux", fx, fy, form);
#endif
#ifdef USE_SDL
        if (!SDL_VideoModeOK(fx, fy, 16, SDL_SWSURFACE | fullscreen))
            IXDRV_OutputDebug("Can't setup screen (%i, %i)!\n", fx, fy);
#endif
        if (x == fx && y == fx)
            stretch = 0;
        else
            stretch = 1;

        sx = fx;
        sy = fy;
    }
#ifdef USE_GGI
    ggiSetMode(vis, &mode);
#endif

    // record resolution
    g_lastWidth = x;
    g_lastHeight = y;
    IXDRV_InitScreen();
    return 1;
};

int IXDRV_GetTime() {
#ifdef USE_SDL
    return SDL_GetTicks();
#else
    struct timeval t;

    gettimeofday(&t, NULL);
    return t.tv_sec * 1000 + t.tv_usec / 1000;
#endif
}

void* IXDRV_Malloc(int memrequired) {
    void* mem;

    mem = calloc(1, memrequired);
    if (!mem) {
        IXDRV_OutputDebug("iXalance memory alloc failed!");
        return 0;
    }
    return mem;
}

int IXDRV_Free(void* mem) {
    if (mem)
        free(mem);
    return 0;
}

void IXDRV_InitGfx() {
#ifdef USE_GGI
    ggiInit();
    vis = ggiOpen(NULL);
    if (!vis) {
        fprintf(stderr, "Unable to open visual\n");
        exit(1);
    }
    mvis = ggiOpen("display-memory", NULL);
    if (!mvis) {
        fprintf(stderr, "Unable to open memory visual\n");
        exit(1);
    }

    if (async) {
        ggiAddFlags(vis, GGIFLAG_ASYNC);
        ggiAddFlags(mvis, GGIFLAG_ASYNC);
    }
#endif
#ifdef USE_SDL
    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
        fprintf(stderr, "Unable to initialize SDL\n");
        exit(1);
    }

    SDL_EventState((Uint8) SDL_ALLEVENTS, SDL_IGNORE);
    SDL_EventState(SDL_QUIT, SDL_ENABLE);
    SDL_EventState(SDL_KEYDOWN, SDL_ENABLE);

    SDL_WM_SetCaption("iXalance/SDL", NULL);
    if (fullscreen)
        SDL_ShowCursor(0);
#endif
}

void IXDRV_QuitGfx() {
#ifdef USE_SDL
    SDL_Quit();
#endif
}

//---------------------------------------------------------------------------------------------------------
// Copies fake-lfb onto the screen
static unsigned oldtime = 0;
static unsigned frames = 0;

void IXDRV_CopyScreen(void) {
    unsigned tm;

#ifdef USE_GGI
    ggiCrossBlit(mvis, 0, 0, g_lastWidth, g_lastHeight, vis, 0, 0);
    if (async)
        ggiFlush(vis);
#endif
#ifdef USE_PTC
    surf->copy(con);
    con.update();
#endif
#ifdef USE_SDL
    if (SDL_MUSTLOCK(screen))
        SDL_UnlockSurface(screen);

    SDL_UpdateRect(screen, 0, 0, 0, 0);

    if (SDL_MUSTLOCK(screen))
        SDL_LockSurface(screen);
#endif
    frames++;
    tm = IXDRV_GetTime();

    if (showfps && (tm > oldtime + 1000)) {
        printf("\rFPS: %2.2f", ((float) frames * 1000) / (tm - oldtime));
        fflush(stdout);
        oldtime = tm;
        frames = 0;
    }
}

int main(int argc, char *argv[]) {
    int i, j;
    int mode;

    if (argc < 2) {
        fprintf(stderr, "%s", usage);
        exit(0);
    }

    Setting_Debugfile = 0;

    oldtime = IXDRV_GetTime();

    IXSND_SoundInit();

    for (i = 1; i < argc; i++) {
        if (argv[i][0] != '-') {
            strncpy(IXA_Filename, argv[i], 255);
        } else {
            switch (argv[i][1]) {
                case 't':
                    showfps = 1;
                    break;
#ifdef USE_GGI
                case 'a':
                    async = 0;
                    break;
#endif
#ifdef USE_SDL
                case 'F':
                    fullscreen = SDL_FULLSCREEN;
                    break;
#endif
                case 'm':
                    MIDASsetOption(MIDAS_OPTION_MIXRATE, atoi(&argv[i][2]));
                    break;

                case 'o':
                    mode = MIDAS_MODE_16BIT_STEREO;
                    for (j = 2; j < 4; j++) {
                        switch (argv[i][j]) {
                            case '1':
                                mode &= ~MIDAS_MODE_8BIT;
                                mode |= MIDAS_MODE_16BIT;
                                break;
                            case '8':
                                mode &= ~MIDAS_MODE_16BIT;
                                mode |= MIDAS_MODE_8BIT;
                                break;
                            case 's':
                                break;
                            case 'm':
                                mode &= ~MIDAS_MODE_STEREO;
                                mode |= MIDAS_MODE_MONO;
                                break;
                        }
                    }
                    MIDASsetOption(MIDAS_OPTION_OUTPUTMODE, mode);
                    break;

                case 'i':
                    MIDASsetOption(MIDAS_OPTION_MIXING_MODE,
                            MIDAS_MIX_HIGH_QUALITY);
                    break;

                case 'f':
                    MIDASsetOption(MIDAS_OPTION_FILTER_MODE, atoi(&argv[i][2]));
                    break;

                case 's':
                    sscanf(&argv[i][2], "%ix%i", &fx, &fy);
                    break;

                case 'd':
                    Setting_Debugfile = 1;
            }
        }
    }

    MIDASsetOption(MIDAS_OPTION_MIXBUFLEN, 50);

    if (Setting_Debugfile) {
        debugfp = fopen("debug.txt", "w");
        if (!debugfp) Setting_Debugfile = 0;
    }
    DemoMain();
    if (Setting_Debugfile) fclose(debugfp);

    return 0;
};
