#include "config.h"
#include "types.h"

#include "sys.h"

#include "memory.h"
#include "mapper.h"
#include "vdp.h"
#include "vdp_pal.h"
#include "vdp_spr.h"
#include "psg.h"
#include "ym2612.h"
#include "joy.h"
#include "z80_ctrl.h"
#include "maths.h"
#include "bmp.h"
#include "timer.h"
#include "string.h"
#include "vdp.h"
#include "vdp_bg.h"
#include "vdp_pal.h"
#include "sound.h"
#include "xgm.h"
#include "dma.h"
#include "sram.h"
#include "sprite_eng.h"

#include "tools.h"
#include "kdebug.h"

#if (ENABLE_LOGO != 0)
#define LOGO_SIZE                   64

#include "libres.h"
#endif


#define IN_VINT                     1
//#define IN_HINT                     2
//#define IN_EXTINT                   4

#define SHOW_FRAME_LOAD             (1 << 0)
#define SHOW_FRAME_LOAD_MEAN        (2 << 0)

#define VINT_ALLOWED_LINE_DELAY     4

#define LOAD_MEAN_FRAME_NUM         8


// we don't want to share them
extern u16 randbase;
extern u16 currentDriver;
// last V-Counter on VDP_waitVSync() / VDP_waitVInt() call
extern u16 lastVCnt;

// extern library callback function (we don't want to share them)
extern void BMP_doVBlankProcess();
extern void XGM_doVBlankProcess();
extern bool MAP_doVBlankProcess();
extern bool VDP_doVBlankScrollProcess();

// we don't want to share that method
extern void MEM_init();

// main function
extern int main(bool hardReset);

// forward
static void internal_reset();
// this one can't be static (used by vdp.c)
bool addFrameLoad(u16 frameLoad, u32 vtime);

// exception callbacks
VoidCallback *busErrorCB;
VoidCallback *addressErrorCB;
VoidCallback *illegalInstCB;
VoidCallback *zeroDivideCB;
VoidCallback *chkInstCB;
VoidCallback *trapvInstCB;
VoidCallback *privilegeViolationCB;
VoidCallback *traceCB;
VoidCallback *line1x1xCB;
VoidCallback *errorExceptionCB;
VoidCallback *intCB;

// user V-Int, H-Int and Ext-Int callbacks
VoidCallback *vintCB;
VoidCallback *hintCB;
VoidCallback *eintCB;

// user VBlank callbacks
VoidCallback *vblankCB;

// exception state consumes 78 bytes of memory
__attribute__((externally_visible)) u32 registerState[8+8];
__attribute__((externally_visible)) u32 pcState;
__attribute__((externally_visible)) u32 addrState;
__attribute__((externally_visible)) u16 ext1State;
__attribute__((externally_visible)) u16 ext2State;
__attribute__((externally_visible)) u16 srState;

__attribute__((externally_visible)) vu16 VBlankProcess;
__attribute__((externally_visible)) vu16 intTrace;

// need to be accessed from external
u16 intLevelSave;
static s16 disableIntStack;
static u16 flags;

// store last frames CPU load (in [0..255] range), need to shared as it can be updated by vdp.c unit
static u16 frameLoads[LOAD_MEAN_FRAME_NUM];
static u16 frameLoadIndex;
static u16 cpuFrameLoad;
static u32 frameCnt;
static u32 lastSubTick;


static void addValueU8(char *dst, char *str, u8 value)
{
    char v[16];

    strcat(dst, str);
    intToHex(value, v, 2);
    strcat(dst, v);
}

static void addValueU16(char *dst, char *str, u16 value)
{
    char v[16];

    strcat(dst, str);
    intToHex(value, v, 4);
    strcat(dst, v);
}

static void addValueU32(char *dst, char *str, u32 value)
{
    char v[16];

    strcat(dst, str);
    intToHex(value, v, 8);
    strcat(dst, v);
}

static u16 showValueU32U16(char *str1, u32 value1, char *str2, u16 value2, u16 pos)
{
    char s[64];

    strclr(s);
    addValueU32(s, str1, value1);
    addValueU16(s, str2, value2);

    VDP_drawText(s, 0, pos);

    return pos + 1;
}

static u16 showValueU16U32U16(char *str1, u16 value1, char *str2, u32 value2, char *str3, u16 value3, u16 pos)
{
    char s[64];

    strclr(s);
    addValueU16(s, str1, value1);
    addValueU32(s, str2, value2);
    addValueU16(s, str3, value3);

    VDP_drawText(s, 0, pos);

    return pos + 1;
}

static u16 showValueU32U16U16(char *str1, u32 value1, char *str2, u16 value2, char *str3, u16 value3, u16 pos)
{
    char s[64];

    strclr(s);
    addValueU32(s, str1, value1);
    addValueU16(s, str2, value2);
    addValueU16(s, str3, value3);

    VDP_drawText(s, 0, pos);

    return pos + 1;
}

static u16 showValueU32U32(char *str1, u32 value1, char *str2, u32 value2, u16 pos)
{
    char s[64];

    strclr(s);
    addValueU32(s, str1, value1);
    addValueU32(s, str2, value2);

    VDP_drawText(s, 0, pos);

    return pos + 1;
}

static u16 showValueU32U32U32(char *str1, u32 value1, char *str2, u32 value2, char *str3, u32 value3, u16 pos)
{
    char s[64];

    strclr(s);
    addValueU32(s, str1, value1);
    addValueU32(s, str2, value2);
    addValueU32(s, str3, value3);

    VDP_drawText(s, 0, pos);

    return pos + 1;
}

//static u16 showValueU32U32U32U32(char *str1, u32 value1, char *str2, u32 value2, char *str3, u32 value3, char *str4, u32 value4, u16 pos)
//{
//    char s[64];
//
//    strclr(s);
//    addValueU32(s, str1, value1);
//    addValueU32(s, str2, value2);
//    addValueU32(s, str3, value3);
//    addValueU32(s, str4, value4);
//
//    VDP_drawText(s, 0, pos);
//
//    return pos + 1;
//}

static u16 showRegisterState(u16 pos)
{
    u16 y = pos;

    y = showValueU32U32U32("D0=", registerState[0], " D1=", registerState[1], " D2=", registerState[2], y);
    y = showValueU32U32U32("D3=", registerState[3], " D4=", registerState[4], " D5=", registerState[5], y);
    y = showValueU32U32("D6=", registerState[6], " D7=", registerState[7], y);
    y = showValueU32U32U32("A0=", registerState[8], " A1=", registerState[9], " A2=", registerState[10], y);
    y = showValueU32U32U32("A3=", registerState[11], " A4=", registerState[12], " A5=", registerState[13], y);
    y = showValueU32U32("A6=", registerState[14], " A7=", registerState[15], y);

    return y;
}

static u16 showStackState(u16 pos)
{
    char s[64];
    u16 y = pos;
    u32 *sp = (u32*) registerState[15];

    u16 i = 0;
    while(i < 24)
    {
        strclr(s);
        addValueU8(s, "SP+", i * 4);
        strcat(s, " ");
        y = showValueU32U32(s, *(sp + (i + 0)), " ", *(sp + (i + 1)), y);
        i += 2;
    }

    return y;
}

static u16 showExceptionDump(u16 pos)
{
    u16 y = pos;

    y = showValueU32U16("PC=", pcState, " SR=", srState, y) + 1;
    y = showRegisterState(y) + 1;
    y = showStackState(y);

    return y;
}

static u16 showException4WDump(u16 pos)
{
    u16 y = pos;

    y = showValueU32U16U16("PC=", pcState, " SR=", srState, " VO=", ext1State, y) + 1;
    y = showRegisterState(y) + 1;
    y = showStackState(y);

    return y;
}

static u16 showBusAddressErrorDump(u16 pos)
{
    u16 y = pos;

    y = showValueU16U32U16("FUNC=", ext1State, " ADDR=", addrState, " INST=", ext2State, y);
    y = showValueU32U16("PC=", pcState, " SR=", srState, y) + 1;
    y = showRegisterState(y) + 1;
    y = showStackState(y);

    return y;
}


// bus error default callback
void _buserror_callback()
{
    SYS_setInterruptMaskLevel(7);
    VDP_init();
    VDP_drawText("BUS ERROR !", 10, 3);

    showBusAddressErrorDump(5);

    while(1);
}

// address error default callback
void _addresserror_callback()
{
    SYS_setInterruptMaskLevel(7);
    VDP_init();
    VDP_drawText("ADDRESS ERROR !", 10, 3);

    showBusAddressErrorDump(5);

    while(1);
}

// illegal instruction exception default callback
void _illegalinst_callback()
{
    SYS_setInterruptMaskLevel(7);
    VDP_init();
    VDP_drawText("ILLEGAL INSTRUCTION !", 7, 3);

    showException4WDump(5);

    while(1);
}

// division by zero exception default callback
void _zerodivide_callback()
{
    SYS_setInterruptMaskLevel(7);
    VDP_init();
    VDP_drawText("DIVIDE BY ZERO !", 10, 3);

    showExceptionDump(5);

    while(1);
}

// CHK instruction default callback
void _chkinst_callback()
{
    SYS_setInterruptMaskLevel(7);
    VDP_init();
    VDP_drawText("CHK INSTRUCTION EXCEPTION !", 5, 10);

    showException4WDump(12);

    while(1);
}

// TRAPV instruction default callback
void _trapvinst_callback()
{
    SYS_setInterruptMaskLevel(7);
    VDP_init();
    VDP_drawText("TRAPV INSTRUCTION EXCEPTION !", 5, 3);

    showException4WDump(5);

    while(1);
}

// privilege violation exception default callback
void _privilegeviolation_callback()
{
    SYS_setInterruptMaskLevel(7);
    VDP_init();
    VDP_drawText("PRIVILEGE VIOLATION !", 5, 3);

    showExceptionDump(5);

    while(1);
}

// trace default callback
void _trace_callback()
{

}

// line 1x1x exception default callback
void _line1x1x_callback()
{

}

// error exception default callback
void _errorexception_callback()
{
    SYS_setInterruptMaskLevel(7);
    VDP_init();
    VDP_drawText("EXCEPTION ERROR !", 5, 3);

    showExceptionDump(5);

    while(1);
}

// level interrupt default callback
void _int_callback()
{
    //
}


// Dummy V-Blank Callback
void _vblank_dummy_callback()
{
    //
}

// Dummy V-Int Callback
void _vint_dummy_callback()
{
    //
}

// Dummy H-Int Callback
void _hint_dummy_callback()
{
    //
}

// Dummy Ext-Int Callback
void _extint_dummy_callback()
{
    //
}


void _start_entry()
{
    u32 banklimit;
    u16* src;
    u16* dst;
    u16 len;

    // clear all RAM (DO NOT USE FUNCTION HERE as we clear all RAM so the stack as well)
    dst = (u16*) RAM;
    len = 0x8000;
    while(len--) *dst++ = 0;

    // then do variables initialization (those which have specific value)

    // point to start of RO initialized data
    src = (u16*) &_stext;
    // point to start initialized variable (always start at beginning of ram)
    dst = (u16*) RAM;
    // get number of byte to copy
    len = (u16)(u32)(&_sdata);
    // convert to word
    len = (len + 1) / 2;

    // get bank limit in word (bank size is 512KB)
    banklimit = (0x80000 - (((u32)src) & 0x7FFFF)) >> 1;
    // bank limit exceeded ?
    if (len > banklimit)
    {
        // we first do the second bank part
        memcpyU16(dst + banklimit, FAR(src + banklimit), len - banklimit);
        // adjust len
        len = banklimit;
    }
    // initialize "initialized variables"
    memcpyU16(dst, FAR(src), len);

    // initialize random number generator
    setRandomSeed(0xC427);
    vtimer = 0;

    // default interrupt callback
    busErrorCB = _buserror_callback;
    addressErrorCB = _addresserror_callback;
    illegalInstCB = _illegalinst_callback;
    zeroDivideCB = _zerodivide_callback;
    chkInstCB = _chkinst_callback;
    trapvInstCB = _trapvinst_callback;
    privilegeViolationCB = _privilegeviolation_callback;
    traceCB = _trace_callback;
    line1x1xCB = _line1x1x_callback;
    errorExceptionCB = _errorexception_callback;
    intCB = _int_callback;

    internal_reset();

#if (ENABLE_LOGO != 0)
    {
        Bitmap *logo = unpackBitmap(&sgdk_logo, NULL);

        // correctly unpacked
        if (logo)
        {
            const Palette *logo_pal = logo->palette;

            // display logo (use BMP mode for that)
            BMP_init(TRUE, BG_A, PAL0, FALSE);

    #if (ZOOMING_LOGO != 0)
            // init fade in to 30 step
            if (VDP_initFading(0, logo_pal->length - 1, palette_black, logo_pal->data, 30))
            {
                // prepare zoom
                u16 size = LOGO_SIZE;

                // while zoom not completed
                while(size > 0)
                {
                    // sort of log decrease
                    if (size > 20) size = size - (size / 6);
                    else if (size > 5) size -= 5;
                    else size = 0;

                    // get new size
                    const u32 w = LOGO_SIZE - size;

                    // adjust palette for fade
                    VDP_doFadingStep();

                    // zoom logo
                    BMP_loadAndScaleBitmap(logo, 128 - (w >> 1), 80 - (w >> 1), w, w, FALSE);
                    // flip to screen
                    BMP_flip(FALSE);
                }

                // while fade not completed
                while(VDP_doFadingStep());
            }

            // wait 1 second
            waitTick(TICKPERSECOND * 1);
    #else
            // set palette 0 to black
            PAL_setPalette(PAL0, palette_black);

            // don't load the palette immediatly
            BMP_loadBitmap(logo, 128 - (LOGO_SIZE / 2), 80 - (LOGO_SIZE / 2), FALSE);
            // flip
            BMP_flip(0);

            // fade in logo
            PAL_fade((PAL0 << 4), (PAL0 << 4) + (logo_pal->length - 1), palette_black, logo_pal->data, 30, FALSE);

            // wait 1.5 second
            waitTick(TICKPERSECOND * 1.5);
    #endif
            // fade out logo
            PAL_fadeOutPalette(PAL0, 20, FALSE);

            // wait 0.5 second
            waitTick(TICKPERSECOND * 0.5);

            // shut down bmp mode
            BMP_end();
            // release bitmap memory
            MEM_free(logo);
            // reinit vdp before program execution
            VDP_init();
        }
    }
#endif

    // let's the fun go on !
    main(TRUE);

    // for safety
    while(TRUE) SYS_doVBlankProcess();
}

void _reset_entry()
{
    internal_reset();

    main(FALSE);

    // for safety
    while(TRUE) SYS_doVBlankProcess();
}

static void internal_reset()
{
    // disable SRAM just in case (if it was enabled on reset)
    SRAM_disable();

#if (ENABLE_BANK_SWITCH != 0)
    // reset banks
    u16 len = 8;
    while(--len) SYS_setBank(len, len);
#endif

    vblankCB = _vblank_dummy_callback;
    vintCB = _vint_dummy_callback;
    hintCB = _hint_dummy_callback;
    eintCB = _extint_dummy_callback;
    VBlankProcess = 0;
    intTrace = 0;
    intLevelSave = 0;
    disableIntStack = 0;

    // default
    flags = 0;

    // reset frame load monitor
    memsetU16(frameLoads, 0, LOAD_MEAN_FRAME_NUM);
    frameLoadIndex = 0;
    cpuFrameLoad = 0;
    frameCnt = 0;
    lastSubTick = 0;

    // safe to check for DMA completion before dealing with VDP (this also clear internal VDP latch)
    // WARNING: it's important to not access the VDP too soon or you can lock the system (it's why we do it just here) !
    while(GET_VDPSTATUS(VDP_DMABUSY_FLAG));

    // init part (always do MEM_init() first)
    MEM_init();
    // need to be reseted before first DMA_init()
    dmaQueues = NULL;
    dmaDataBuffer = NULL;
    DMA_init();
    DMA_setMaxTransferSizeToDefault();
    VDP_init();
    PSG_init();
    JOY_init();
    // reseting z80 also reset the ym2612
    Z80_init();

    // Sprite engine variables reset (we use to know if sprite engine is initialized)
    spritesBank = NULL;
    spriteVramSize = 0;

    // enable interrupts
    SYS_setInterruptMaskLevel(3);
}

bool SYS_doVBlankProcess()
{
    return SYS_doVBlankProcessEx(ON_VBLANK_START);
}

bool SYS_doVBlankProcessEx(VBlankProcessTime processTime)
{
    if (processTime != IMMEDIATELY)
    {
        // wait for VBlank
        if (VDP_waitVBlank(processTime == ON_VBLANK_START))
        {
            // frame late/miss detection and VBlank process forced on VBlank start ?
            if (processTime == ON_VBLANK_START)
            {
                // SYS_doVBlankProcess() was called from V-Int callback ?
                if (SYS_isInVInt())
                    // we need to return from interrupt as we don't have anyway to clear the new pending interrupt
                    // so we will take it again immediately but should be in time for this one :)
                    return FALSE;
            }
        }
    }

    u16 vbp = VBlankProcess;
#if (LIB_LOG_LEVEL >= LOG_LEVEL_WARNING)
    u16 vcnt = 0;
#endif

    // dma processing
    if (vbp & PROCESS_DMA_TASK)
    {
#if (LIB_LOG_LEVEL >= LOG_LEVEL_WARNING)
        u16 dmaSize = DMA_getQueueTransferSize();
#endif

        // DMA protection for XGM driver
        if (currentDriver == Z80_DRIVER_XGM)
        {
            XGM_set68KBUSProtection(TRUE);

            // delay enabled ? --> wait a bit to improve PCM playback (test on SOR2)
            if (XGM_getForceDelayDMA()) waitSubTick(10);
            DMA_flushQueue();

            XGM_set68KBUSProtection(FALSE);
        }
        else
            DMA_flushQueue();

#if (LIB_LOG_LEVEL >= LOG_LEVEL_WARNING)
        vcnt = GET_VCOUNTER;

        // above scanline 2 ? better to warn about DMA overrun..
        if ((vcnt < 224) && (vcnt > 2))
            KLog_U3("Warning: DMA task (", dmaSize, " bytes) completed outside VBlank area. Scanline after completion = ", vcnt, " on frame #", vtimer);
#endif
    }

    // VDP scroll process (async scroll update)
    if (vbp & PROCESS_VDP_SCROLL_TASK)
    {
        if (!VDP_doVBlankScrollProcess()) vbp &= ~PROCESS_VDP_SCROLL_TASK;

#if (LIB_LOG_LEVEL >= LOG_LEVEL_WARNING)
        // previous v-counter was ok ?
        if ((vcnt >= 224) || (vcnt < 3))
        {
            vcnt = GET_VCOUNTER;

            // above scanline 2 ? better to warn about frame overrun..
            if ((vcnt < 224) && (vcnt > 2))
                KLog_U2("Warning: Scroll task completed outside VBlank area. Scanline after completion = ", vcnt, " on frame #", vtimer);
        }
#endif
    }

    // palette fading process
    if (vbp & PROCESS_PALETTE_FADING)
    {
        if (!PAL_doFadeStep()) vbp &= ~PROCESS_PALETTE_FADING;

#if (LIB_LOG_LEVEL >= LOG_LEVEL_WARNING)
        // previous v-counter was ok ?
        if ((vcnt >= 224) || (vcnt < 3))
        {
            vcnt = GET_VCOUNTER;

            // above scanline 2 ? better to warn about frame overrun..
            if ((vcnt < 224) && (vcnt > 2))
                KLog_U2("Warning: Palette fade task completed outside VBlank area. Scanline after completion = ", vcnt, " on frame #", vtimer);
        }
#endif
    }

    // store back
    VBlankProcess = vbp;

    // user VBlank callback
    (*vblankCB)();

    // frame load display enabled ?
    if (flags & SHOW_FRAME_LOAD)
    {
        // use internal sprite 0 to show cursor
        VDPSprite* vdpSprite = &vdpSpriteCache[0];

        // use CPU load display instead (mean)
        if (flags & SHOW_FRAME_LOAD_MEAN)
        {
            // get CPU load (0-255)
            u16 load = cpuFrameLoad / LOAD_MEAN_FRAME_NUM;

            if (load > 224) vdpSprite->y = 220 + 0x80;
            else vdpSprite->y = load + (0x80 - 4);
        }
        // directly use VCounter
        else
        {
            // update position relative to last stored VCounter
            if ((lastVCnt > 224) || (lastVCnt < 4)) vdpSprite->y = 0x80;
            else if (lastVCnt > 220) vdpSprite->y = 220 + 0x80;
            else vdpSprite->y = lastVCnt + (0x80 - 4);
        }

        // write immediately in VRAM the sprite position change
        vu16* pw = (u16 *) GFX_DATA_PORT;
        vu32* pl = (u32 *) GFX_CTRL_PORT;

        *pl = GFX_WRITE_VRAM_ADDR(VDP_SPRITE_TABLE);
        *pw = vdpSprite->y;
    }

    // joy state refresh
    JOY_update();

    return TRUE;
}

void SYS_disableInts()
{
    // in interrupt --> return
    if (intTrace != 0)
    {
#if (LIB_LOG_LEVEL >= LOG_LEVEL_WARNING)
        // KDebug_Alert("SYS_disableInts() warning: call during interrupt (ignored)");
#endif

        return;
    }

    // disable interrupts
    if (disableIntStack++ == 0)
        intLevelSave = SYS_getAndSetInterruptMaskLevel(7);
#if (LIB_LOG_LEVEL >= LOG_LEVEL_WARNING)
    else
    {
        if (disableIntStack <= 0)
            KLog_S1_("SYS_disableInts() fails: need ", (-disableIntStack) + 1, " more");
    #if (LIB_LOG_LEVEL >= LOG_LEVEL_INFO)
        else
            KLog_S1("SYS_disableInts() info: inner call = ", disableIntStack);
    #endif
    }
#endif
}

void SYS_enableInts()
{
    // in interrupt --> return
    if (intTrace != 0)
    {
#if (LIB_LOG_LEVEL >= LOG_LEVEL_WARNING)
        // KDebug_Alert("SYS_enableInts() fails: call during interrupt");
#endif

        return;
    }

    // reenable interrupts
    if (--disableIntStack == 0)
        SYS_setInterruptMaskLevel(intLevelSave);
#if (LIB_LOG_LEVEL >= LOG_LEVEL_WARNING)
    else
    {
        if (disableIntStack < 0)
            KLog_S1("SYS_enableInts() fails: already enabled = ", disableIntStack);
    #if (LIB_LOG_LEVEL >= LOG_LEVEL_INFO)
        else
            KLog_S1_("SYS_enableInts() info: inner call, need ", disableIntStack, " more");
    #endif
    }
#endif
}

void SYS_setVBlankCallback(VoidCallback *CB)
{
    if (CB) vblankCB = CB;
    else vblankCB = _vblank_dummy_callback;
}

void SYS_setVIntCallback(VoidCallback *CB)
{
    if (CB) vintCB = CB;
    else vintCB = _vint_dummy_callback;
}

void SYS_setHIntCallback(VoidCallback *CB)
{
    if (CB) hintCB = CB;
    else hintCB = _hint_dummy_callback;
}

void SYS_setExtIntCallback(VoidCallback *CB)
{
    if (CB) eintCB = CB;
    else eintCB = _extint_dummy_callback;
}

void SYS_setVIntAligned(bool value)
{
    // deprecated
}

bool SYS_isVIntAligned()
{
    return FALSE;
}

void SYS_showFrameLoad(bool mean)
{
    if (mean) flags |= (SHOW_FRAME_LOAD | SHOW_FRAME_LOAD_MEAN);
    else flags |= SHOW_FRAME_LOAD;

    // use internal sprite 0 to show cursor
    VDPSprite* vdpSprite = &vdpSpriteCache[0];
    vdpSprite->y = 0;
    vdpSprite->size = SPRITE_SIZE(1, 1);
    // point on left cursor tile in font
    vdpSprite->attribut = TILE_ATTR_FULL(PAL0, TRUE, FALSE, FALSE, TILE_FONTINDEX + 94);
    vdpSprite->x = 0x80;

    // apply changes immediately in VRAM
    vu16* pw = (u16 *) GFX_DATA_PORT;
    vu32* pl = (u32 *) GFX_CTRL_PORT;

    // prepare write to sprite #0
    *pl = GFX_WRITE_VRAM_ADDR(VDP_SPRITE_TABLE);

    // write fields in correct order
    *pw = vdpSprite->y;
    *pw = vdpSprite->size_link;
    *pw = vdpSprite->attribut;
    *pw = vdpSprite->x;
}

void SYS_hideFrameLoad()
{
    flags &= ~(SHOW_FRAME_LOAD | SHOW_FRAME_LOAD_MEAN);

    // use internal sprite 0 to show cursor
    VDPSprite* vdpSprite = &vdpSpriteCache[0];
    // hide it
    vdpSprite->y = 0;

    // apply changes immediately in VRAM
    vu16* pw = (u16 *) GFX_DATA_PORT;
    vu32* pl = (u32 *) GFX_CTRL_PORT;

    // prepare write to sprite #0
    *pl = GFX_WRITE_VRAM_ADDR(VDP_SPRITE_TABLE);
    // no need to write more
    *pw = vdpSprite->y;
}

u16 SYS_isInVIntCallback()
{
    return intTrace & IN_VINT;
}

u16 SYS_isInHIntCallback()
{
    return 0;
}

u16 SYS_isInExtIntCallback()
{
    return 0;
}

u16 SYS_isInInterrupt()
{
    return SYS_isInVInt();
}


bool SYS_isInVInt()
{
    return (intTrace & IN_VINT)?TRUE:FALSE;
}

u16 SYS_isNTSC()
{
    return !IS_PALSYSTEM;
}

u16 SYS_isPAL()
{
    return IS_PALSYSTEM;
}


u32 SYS_getFPS()
{
    static s32 result;
    const u32 current = getSubTick();
    u32 delta = current - lastSubTick;

    if ((delta > 19200) && ((frameCnt > (76800 * 5)) || (delta > 76800)))
    {
        result = frameCnt / delta;
        if (result > 999) result = 999;
        lastSubTick = current;
        frameCnt = 76800;
    }
    else frameCnt += 76800;

    return result;
}

fix32 SYS_getFPSAsFloat()
{
    static fix32 result;
    const s32 current = getSubTick();
    u32 delta = current - lastSubTick;

    if ((delta > 19200) && ((frameCnt > (76800 * 5)) || (delta > 76800)))
    {
        if (frameCnt > (250 * 76800)) result = FIX32((u32) 999);
        else
        {
            result = (frameCnt << FIX16_FRAC_BITS) / delta;
            if (result > (999 << FIX16_FRAC_BITS)) result = FIX32((u32)999);
            else result <<= (FIX32_FRAC_BITS - FIX16_FRAC_BITS);
        }

        lastSubTick = current;
        frameCnt = 76800;
    }
    else frameCnt += 76800;

    return result;
}


// used to compute average frame load on 8 frames
bool addFrameLoad(u16 frameLoad, u32 vtime)
{
    static u16 lastVTimer = 0;
    u16 deltaFrame = vtime - lastVTimer;
    bool miss;
    u16 v;

    // frame miss ?
    if (deltaFrame > 1)
    {
        // force frame load to 255
        v = ((deltaFrame - 1) << 8) + frameLoad;
        miss = TRUE;
    }
    else
    {
        miss = FALSE;
        v = frameLoad;
    }

    cpuFrameLoad -= frameLoads[frameLoadIndex];
    frameLoads[frameLoadIndex] = v;
    cpuFrameLoad += v;
    frameLoadIndex = (frameLoadIndex + 1) & (LOAD_MEAN_FRAME_NUM - 1);
    lastVTimer = vtime;

    return miss;
}

u16 SYS_getCPULoad()
{
   return (cpuFrameLoad * ((u16) 100)) / (u16) (LOAD_MEAN_FRAME_NUM * 256);
}


u16 SYS_computeChecksum()
{
    u32 adr;
    u32 chk;

    chk = 0;
    for(adr = 0; adr < ROM_SIZE; adr += BANK_SIZE)
    {
        // dword x8 checksum
        u16 len = BANK_SIZE / (4 * 8);
        // get source pointer
        u32* src = (u32 *) FAR(adr);

        while(len--)
        {
          chk ^= *src++;
          chk ^= *src++;
          chk ^= *src++;
          chk ^= *src++;
          chk ^= *src++;
          chk ^= *src++;
          chk ^= *src++;
          chk ^= *src++;
        }
    }

    // pack 32 bit checksum on 16 bit
    return chk ^ (chk >> 16);
}

bool SYS_isChecksumOk()
{
    u16 checksum = SYS_computeChecksum();

    // remove checksum from it
    return (checksum ^ rom_header.checksum) == rom_header.checksum;
}


void SYS_die(char *err)
{
    SYS_setInterruptMaskLevel(7);
    VDP_init();
    VDP_drawText("A fatal error occured !", 2, 2);
    VDP_drawText("cannot continue...", 4, 3);
    if (err) VDP_drawText(err, 0, 5);

    while(1);
}
