// DOS32 Exe Loader
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>

#include "midasdll.h"
#include "lzss.h"

#include "ixalance.h"



void decode_rle(unsigned char *input, unsigned char *output);

static FILE *ixafp;
static FILE *tempfp;
static char *tempname;

static int timerrate = 70;
int PARTMEM = 13 * 1024 * 1024;

struct IXAheaderstruct {
    char name[8];
    char demoname[32];
    int dirstart;
    int type;
}
IXAheader;

struct {
    int pos;
    int size;
    int fullsize;
}
IXAdir[256];
int IXAnumfiles;

char IXAscript[1024];
int IXAscriptpos;

// Prototypes
void UpdateMusic();

// Statics voor Midas
static int music2 = 0;
int MIDAS_playing;
int MIDAS_enabled;
MIDASmodule MIDAS_module;
MIDASmodulePlayHandle MIDAS_playHandle;

// Statics voor balmode zooi
unsigned short int saveFS, saveGS;
int runexe[2];
int herzcount;
int exerunning;
static unsigned int lasttime;

unsigned int temp1, temp2, temp3, temp4;
void *tmpptr1, *tmpptr2, *tmpptr3, *tmpptr4;

int hrmask, hgmask, hbmask;
int hrcomp, hgcomp, hbcomp;

char IXA_Name[256] = "iXalance";
char IXA_Filename[256];

Tgfxmodeinfo gfxmodeinfo;

struct {
    unsigned char timepos;
    unsigned char timerow;
}
mustime;

int saveESP;
void *balmodeinfo;
void *quitaddress;

int escaped;

static struct {
    int type;
    void *memptr;
    unsigned int eip;
    unsigned int esp;
} partstack[256];

int partpointer;

unsigned int startip;
unsigned int startsp;
void *relocofs;
int fixupsize;
void *exeofs;

void IXSND_SoundInit(void) {
    MIDASstartup();
}

void IXSND_SoundSetup(void) {
    /* MIDASconfig(); */
}

// ---------------------
// Partmemory functions
char *partmem;
int partmemused;

void *AllocPartmem(int mem) {
    void *adr;

    // Allocate everything on 4k aligned boundaries.
    mem += 4096;
    mem &= 0xfffff000;
    adr = partmem + partmemused;
    partmemused += mem;
    IXDRV_OutputDebug("Allocating %i bytes. Free: %i\n", mem, (PARTMEM) - partmemused);
    if (partmemused > PARTMEM) {
        IXDRV_OutputDebug("To much mem allocated! %i\n", mem);
        return 0;
    }
    return adr;
}

void FreeAllPartmem(void) {
    partmemused = 0;
}

// -----------------------------------
// Nasty callbacks used by the demos.
// Most of them are ignored. They do stuff that's not required anymore.
// All I do with them is just use them to do my own stuff, like update
// music positions and check for messages.

void basictramp(void);

void farbank() {
    IXDRV_CheckMessages();
    UpdateMusic();
};

// This one does something useful :)
// It uses the IXDRV_CopyScreen function to show the current 'lfb'
void farshowptramp(void);

void farshowp() {
    IXDRV_CheckMessages();
    IXDRV_CopyScreen();
    UpdateMusic();
};

// Memory allocation for parts + really dirty pollback to the system
// (Parts can allocate 0 bytes just to get music timer values updated.)
void farmalloctramp(void);

void farmalloc() {
    IXDRV_CheckMessages();
    UpdateMusic();
    if (temp1 > 0) {
        tmpptr1 = AllocPartmem(temp1);
    }
};

// Used to be to call dos interrupts, used now for some special options
// for the intros.

void fardoint() {
    IXDRV_CheckMessages();
    UpdateMusic();

    // EAX='TBL1' option. Start new XM
    // ESI = Offset to XM
    // ECX = Size of XM
    /*
    printf("fardoint\n");
    printf("temp1 = %x\n", temp1);
    printf("temp2 = %x\n", temp2);
    printf("temp3 = %x\n", temp3);
    printf("temp4 = %x\n", temp4);
     */
    if (temp1 == 'TBL1') {
        timerrate = 140;

        tempname = tempnam(NULL, "IXA");

        tempfp = fopen(tempname, "wb");

        fwrite((void *) temp3, temp4, 1, tempfp);
        fclose(tempfp);

        if (MIDAS_playing) {
            MIDASstopModule(MIDAS_playHandle);
            MIDASfreeModule(MIDAS_module);
            MIDASstopBackgroundPlay();
            MIDAS_playing = 0;
        }

        if (MIDAS_enabled) {
            IXDRV_OutputDebug("Starting Midas audio playback...\n");
            MIDASstartBackgroundPlay(70);
            MIDAS_module = MIDASloadModule(tempname);
            MIDAS_playHandle = MIDASplayModule(MIDAS_module, TRUE);
            MIDAS_playing = 1;
            music2 = 1;
        }

        remove(tempname);
        free(tempname);
    }        // EAX='TBL3' option. Get position in music.
    else if (temp1 == 'TBL3') {
        if (MIDAS_playing) {
            temp1 = (mustime.timerow + 1) + ((mustime.timepos) << 8);
        } else {
            temp1 = 0;
        }
    }
    // TBL2 and TBL4 exist as well, forgot what they do. Probably not
    // important.
};

#ifdef __GNUC__
void jumptramp(void) __attribute__((noreturn));
#else
extern void jumptramp(void);
#endif

void BreakPart() {
    escaped = 1;
    IXDRV_OutputDebug("Part aborted.\n");
    if (exerunning) {
        jumptramp();
    }
}

void ResumePart() {
    if (MIDAS_playing)
        MIDASresume();

    UpdateMusic();
}

void PausePart() {
    UpdateMusic();
    if (MIDAS_playing)
        MIDASsuspend();
}

void savesegs(void);
void reloc(void);

void PushExe(int filenum) {
    char headers[64];
    long int *hdr;
    int stubsize;
    int exestart;
    int exesize;
    char *lzssbuf;
    char *lzssbuf1;
    char *lzssbuf2;
    int readptr;
    unsigned int memrequired;

    IXDRV_OutputDebug("Loading exe: %i", filenum);
    IXDRV_OutputDebug("\n");

    // Start Loading EXE

    // Allocate memory for possible decompression
    lzssbuf2 = (char *) AllocPartmem(IXAdir[filenum].fullsize);
    lzssbuf1 = (char *) AllocPartmem(IXAdir[filenum].size);
    if (!(lzssbuf = (char *) IXDRV_Malloc(IXAdir[filenum].fullsize)))
        IXDRV_OutputDebug("Couldn't allocate memory for decompression!\n");

    fseek(ixafp, IXAdir[filenum].pos, SEEK_SET);
    fread(lzssbuf1, IXAdir[filenum].size, 1, ixafp);

    IXDRV_OutputDebug("Decompressing...\n");
    Expand((long *) lzssbuf1, lzssbuf2);
    decode_rle((unsigned char *) lzssbuf2, (unsigned char *) lzssbuf);

    FreeAllPartmem();

    // Read normal EXE header
    readptr = 0;

    memcpy(headers, lzssbuf + readptr, 64);
    readptr += 64;

    stubsize = ((*((short int *) headers + 1))) +
            ((*((short int *) headers + 2) - 1) * 512);

    // Read DOS32 EXE header
    readptr = stubsize;

    memcpy(headers, lzssbuf + readptr, 64);
    readptr += 64;

    hdr = (long*) headers;

    exestart = hdr[4];
    startip = hdr[5];
    memrequired = hdr[6];
    startsp = hdr[7];
    startsp = (startsp + 3) & ~3;
    exesize = hdr[8];
    fixupsize = hdr[2] - exesize;
    balmodeinfo = &gfxmodeinfo;

    // Allocate memory for EXE
    if (!(exeofs = IXDRV_Malloc(memrequired)))
        IXDRV_OutputDebug("Virtual Alloc (for final exe memory) Failed!\n");

    startip += (unsigned int) exeofs;
    startsp += (unsigned int) exeofs;

    readptr = stubsize + exestart;
    memcpy(exeofs, lzssbuf + readptr, exesize);
    readptr += exesize;

    // Save segment registers
    savesegs();

    // Load relocation items
    relocofs = lzssbuf + readptr;

    reloc();

    IXDRV_Free(lzssbuf);
    partstack[partpointer].type = 1;
    partstack[partpointer].memptr = exeofs;
    partstack[partpointer].eip = startip;
    partstack[partpointer].esp = startsp;
    partpointer++;
}

void startdemo();

static void PopExe() {
    unsigned int allocsp;
    void *exeofs;

    IXDRV_SetVideoResolution(320, 200, 16);
    IXDRV_OutputDebug("Initialising screen...\n");

    startip = partstack[partpointer].eip;
    startsp = partstack[partpointer].esp;
    exeofs = partstack[partpointer].memptr;

    allocsp = (unsigned int) IXDRV_Malloc(64000);

    startsp = allocsp + 63900;

    IXDRV_OutputDebug("Popexe: %i\n", partpointer);
    IXDRV_OutputDebug("Starting at: %x\n", startip);
    IXDRV_OutputDebug("Stack at: %x\n", startsp);

    startdemo();

    IXDRV_Free(exeofs);
    IXDRV_Free((void *) allocsp);
    FreeAllPartmem();
};

void WaitMusic(int pos, int row) {
    IXDRV_OutputDebug("Waiting for pos %i and row %i...", pos, row);
    while (((pos * 256 + row) > (((int) mustime.timepos) * 256 + mustime.timerow)) && (!escaped)) {
        UpdateMusic();
        IXDRV_CheckMessages();
    }
}

void PopPicture(void) {
    unsigned char *picture = (unsigned char *) partstack[partpointer].memptr;
    int xsize, ysize, y, x;

    xsize = ((int) picture[8]) * 256 + picture[9];
    ysize = ((int) picture[10]) * 256 + picture[11];

    IXDRV_SetVideoResolution(xsize, ysize, 16);
    IXDRV_OutputDebug("Initialising screen...\n");

    for (y = 0; y < ysize; y++) {
        for (x = 0; x < xsize; x++)
            ((short *) gfxmodeinfo.tclfb)[y * xsize + x] = ((short *) picture)[y * xsize + x + 16];
    }
    IXDRV_Free(partstack[partpointer].memptr);
    IXDRV_CopyScreen();
}

void PopPart() {
    partpointer--;
    if (partstack[partpointer].type == 1)
        PopExe();
    if (partstack[partpointer].type == 2)
        PopPicture();
}

void
RunExe(int filenum) {
    // 0 - Adam
    // 4 - DOS32 version
    // 6 - DLINK version
    // 8 - Size of executable part+Fixups
    // c - Size of header+exe
    // 10 - Start of exe
    // 14 - Initial IP
    // 18 - Memory required
    // 1c - Initial SP
    // 20 - Size of executable part
    // 24 -
    // 28 -
    PushExe(filenum);
    PopPart();
};

void
PlayMusic(int filenum) {
    char *tempname;
    char *lzssbuf, *lzssbuf1, *lzssbuf2;
    FILE *tempfp;

    IXDRV_OutputDebug("Loading music file: %i", filenum);
    IXDRV_OutputDebug("\n");

    // Allocate memory for possible decompression
    lzssbuf2 = (char *) AllocPartmem(IXAdir[filenum].fullsize);
    lzssbuf1 = (char *) AllocPartmem(IXAdir[filenum].size);

    fseek(ixafp, IXAdir[filenum].pos, SEEK_SET);
    fread(lzssbuf1, IXAdir[filenum].size, 1, ixafp);

    IXDRV_OutputDebug("Decompressing...\n");
    Expand((long *) lzssbuf1, lzssbuf2);
    if (!(lzssbuf = (char *) IXDRV_Malloc(IXAdir[filenum].fullsize)))
        IXDRV_OutputDebug("Couldn't allocate memory for decompression!\n");
    decode_rle((unsigned char *) lzssbuf2, (unsigned char *) lzssbuf);
    FreeAllPartmem();

    // What I do is a bit nasty. I save the XM to a temporary file, and make
    // Midas open it. You might have some better way to do this :)
    tempname = tempnam(NULL, "IXA");

    tempfp = fopen(tempname, "wb");

    fwrite(lzssbuf, IXAdir[filenum].fullsize, 1, tempfp);
    fclose(tempfp);
    IXDRV_Free(lzssbuf);

    if (MIDAS_enabled) {
        IXDRV_OutputDebug("Starting Midas audio playback...\n");
        MIDASstartBackgroundPlay(70);
        MIDAS_module = MIDASloadModule(tempname);

        MIDAS_playHandle = MIDASplayModule(MIDAS_module, TRUE);
        MIDAS_playing = 1;
    }

    remove(tempname);
    free(tempname);
}

void
PushPicture(int filenum) {
    int xsize, ysize, i;
    unsigned char *lzssbuf, *lzssbuf1, *lzssbuf2;

    IXDRV_OutputDebug("Loading picture file: %i", filenum);
    IXDRV_OutputDebug("\n");

    // Allocate memory for possible decompression
    lzssbuf2 = (unsigned char *) AllocPartmem(IXAdir[filenum].fullsize);
    lzssbuf1 = (unsigned char *) AllocPartmem(IXAdir[filenum].size);

    fseek(ixafp, IXAdir[filenum].pos, SEEK_SET);
    fread(lzssbuf1, IXAdir[filenum].size, 1, ixafp);

    IXDRV_OutputDebug("Decompressing...\n");
    Expand((long *) lzssbuf1, (char *) lzssbuf2);
    if (!(lzssbuf = (unsigned char *) IXDRV_Malloc(IXAdir[filenum].fullsize)))
        IXDRV_OutputDebug("Couldn't allocate memory for decompression!\n");
    decode_rle((unsigned char *) lzssbuf2, (unsigned char *) lzssbuf);
    FreeAllPartmem();

    xsize = ((int) lzssbuf[8]) * 256 + lzssbuf[9];
    ysize = ((int) lzssbuf[10]) * 256 + lzssbuf[11];

    for (i = 16; i < xsize * ysize + 16; i++) {
        int r = ((((short *) lzssbuf)[i] >> 11) & 31) << 3;
        int g = ((((short *) lzssbuf)[i] >> 5) & 63) << 2;
        int b = ((((short *) lzssbuf)[i]) & 31) << 3;

        r >>= 8 - hrmask;
        g >>= 8 - hgmask;
        b >>= 8 - hbmask;

        r <<= hrcomp;
        g <<= hgcomp;
        b <<= hbcomp;

        ((short *) lzssbuf)[i] = r | g | b;
    }
    partstack[partpointer].type = 2;
    partstack[partpointer].memptr = lzssbuf;
    partstack[partpointer].eip = 0;
    partstack[partpointer].esp = 0;
    partpointer++;
}

void
UpdateMusic() {
    unsigned int time = (IXDRV_GetTime() / (1000 / timerrate));

    if ((time - lasttime) > 0) {
        herzcount += (time - lasttime);
        lasttime = time;
    }
    if (MIDAS_playing) {
        MIDASplayStatus status;

        MIDASgetPlayStatus(MIDAS_playHandle, &status);
        mustime.timepos = status.position;
        mustime.timerow = status.row;
    } else {
        mustime.timepos = 0;
        mustime.timerow = 0;
    }
    if (herzcount < 0)
        herzcount = 0;
}


// iXalance entry point.

void DemoMain() {

    IXDRV_OutputDebug("loading %s\n", IXA_Filename);

    ixafp = fopen(IXA_Filename, "rb");
    if (!ixafp) {
        IXDRV_OutputDebug("Can't open IXA file!\n");
        fprintf(stderr, "Can't open IXA File!\n");
    } else {
        // Load directory
        fseek(ixafp, 0, SEEK_SET);
        fread(&IXAheader, sizeof (IXAheader), 1, ixafp);
        fseek(ixafp, IXAheader.dirstart, SEEK_SET);
        fread(&IXAnumfiles, 4, 1, ixafp);
        fread(IXAdir, IXAnumfiles, 12, ixafp);

        strcpy(IXA_Name, IXAheader.demoname);
        // Astral Blur needs less memory
        if (!strcmp(" Astral Blur", IXA_Name)) {
            PARTMEM = 1024 * 1024 * 6;
        }

        // Load scriptfile
        fseek(ixafp, IXAdir[0].pos, SEEK_SET);
        fread(IXAscript, IXAdir[0].size, 1, ixafp);
        IXAscriptpos = 0;

        if (!(partmem = (char *) IXDRV_Malloc(PARTMEM))) {
            IXDRV_OutputDebug("Couldn't allocate partmemory stack!\n");
            return;
        }
        FreeAllPartmem();
        IXDRV_OutputDebug("Doing graphics setup...\n");
        IXDRV_InitGfx();

        timerrate = 70;

#ifndef USE_SDL
        // To check for hires bitcomponents etc. (Astral Blur only)
        IXDRV_SetVideoResolution(800, 600, 16);
        IXDRV_OutputDebug("Checking hires 800*600.\n");
#endif

#ifndef USE_SDL
        IXDRV_SetVideoResolution(640, 480, 16);
        IXDRV_OutputDebug("Checking hires 640*480.\n");
#endif
        IXDRV_OutputDebug("Initialising screen...\n");
        IXDRV_SetVideoResolution(320, 200, 16);

        hrcomp = gfxmodeinfo.rcomp;
        hgcomp = gfxmodeinfo.gcomp;
        hbcomp = gfxmodeinfo.bcomp;

        hrmask = gfxmodeinfo.rmask;
        hgmask = gfxmodeinfo.gmask;
        hbmask = gfxmodeinfo.bmask;

        escaped = 0;
        partpointer = 0;

        MIDASsetOption(MIDAS_OPTION_FORCE_NO_SOUND, 0);
        if (MIDASinit()) {
            IXDRV_OutputDebug("Midas initialised.\n");
            MIDAS_enabled = 1;
        } else {
            IXDRV_OutputDebug("Midas init failed.\n");
            MIDASsetOption(MIDAS_OPTION_FORCE_NO_SOUND, 1);
            if (MIDASinit()) {
                IXDRV_OutputDebug("Midas initialised (nosound).\n");
                MIDAS_enabled = 1;
            } else {
                IXDRV_OutputDebug("Midas nosound init failed!");
                return;
            }
        }

        IXDRV_OutputDebug("Starting exe playback...\n");

        while ((IXAscriptpos < IXAdir[0].size) && (!escaped)) {
            if (IXAscript[IXAscriptpos] == 2) {
                PopPart();
                IXAscriptpos++;
            } else if (IXAscript[IXAscriptpos] == 1) {
                PushExe(IXAscript[IXAscriptpos + 1]);
                IXAscriptpos += 2;
            } else if (IXAscript[IXAscriptpos] == 3) {
                PlayMusic(IXAscript[IXAscriptpos + 1]);
                IXAscriptpos += 2;
            } else if (IXAscript[IXAscriptpos] == 4) {
                PushPicture(IXAscript[IXAscriptpos + 1]);
                IXAscriptpos += 2;
            } else if (IXAscript[IXAscriptpos] == 5) {
                WaitMusic(IXAscript[IXAscriptpos + 1], IXAscript[IXAscriptpos + 2]);
                IXAscriptpos += 3;
            }
            if (!escaped) {
                IXDRV_CheckMessages();
            }

        }

        IXDRV_Free(partmem);
        if (MIDAS_playing) {
            MIDASstopModule(MIDAS_playHandle);
            MIDASfreeModule(MIDAS_module);
            MIDASstopBackgroundPlay();
            MIDAS_playing = 0;
        }
        MIDASclose();
        IXDRV_QuitGfx();
    }
};
