

#include "rockmacros.h"

#include "defs.h"
#include "hw.h"
#include "regs.h"
#include "mem.h"
#include "rtc-gb.h"
#include "lcd-gb.h"
#include "lcdc.h"
#include "sound.h"

struct mbc mbc IBSS_ATTR;
struct rom rom IBSS_ATTR;
struct ram ram;


/*
 * In order to make reads and writes efficient, we keep tables
 * (indexed by the high nibble of the address) specifying which
 * regions can be read/written without a function call. For such
 * ranges, the pointer in the map table points to the base of the
 * region in host system memory. For ranges that require special
 * processing, the pointer is NULL.
 *
 * mem_updatemap is called whenever bank changes or other operations
 * make the old maps potentially invalid.
 */

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
void mem_updatemap(void)
{
    int n;
    static byte **map;

    map = mbc.rmap;
    map[0x0] = rom.bank[0];
    map[0x1] = rom.bank[0];
    map[0x2] = rom.bank[0];
    map[0x3] = rom.bank[0];
    if (mbc.rombank < mbc.romsize)
    {
        map[0x4] = rom.bank[mbc.rombank] - 0x4000;
        map[0x5] = rom.bank[mbc.rombank] - 0x4000;
        map[0x6] = rom.bank[mbc.rombank] - 0x4000;
        map[0x7] = rom.bank[mbc.rombank] - 0x4000;
    }
    else map[0x4] = map[0x5] = map[0x6] = map[0x7] = NULL;
    if (0 && (R_STAT & 0x03) == 0x03)
    {
        map[0x8] = NULL;
        map[0x9] = NULL;
    }
    else
    {
        map[0x8] = lcd.vbank[R_VBK & 1] - 0x8000;
        map[0x9] = lcd.vbank[R_VBK & 1] - 0x8000;
    }

    if (mbc.enableram && !(rtc.sel&8))
    {
        map[0xA] = ram.sbank[mbc.rambank] - 0xA000;
        map[0xB] = ram.sbank[mbc.rambank] - 0xA000;
    }
    else map[0xA] = map[0xB] = NULL;
    map[0xC] = ram.ibank[0] - 0xC000; // XXX
    n = R_SVBK & 0x07;
    map[0xD] = ram.ibank[n?n:1] - 0xD000;
    map[0xE] = ram.ibank[0] - 0xE000; // XXX
    map[0xF] = NULL;

    map = mbc.wmap;
    map[0x0] = map[0x1] = map[0x2] = map[0x3] = NULL;
    map[0x4] = map[0x5] = map[0x6] = map[0x7] = NULL;
    map[0x8] = map[0x9] = NULL;
    if (mbc.enableram && !(rtc.sel&8))
    {
        map[0xA] = ram.sbank[mbc.rambank] - 0xA000;
        map[0xB] = ram.sbank[mbc.rambank] - 0xA000;
    }
    else map[0xA] = map[0xB] = NULL;
    map[0xC] = ram.ibank[0] - 0xC000; // XXX
    n = R_SVBK & 0x07;
    map[0xD] = ram.ibank[n?n:1] - 0xD000;
    map[0xE] = ram.ibank[0] - 0xE000; // XXX
    map[0xF] = NULL;
}
#pragma GCC diagnostic pop

/*
 * ioreg_write handles output to io registers in the FF00-FF7F,FFFF
 * range. It takes the register number (low byte of the address) and a
 * byte value to be written.
 */

static void ioreg_write(byte r, byte b) ICODE_ATTR;
static void ioreg_write(byte r, byte b)
{
    if (!hw.cgb)
    {

        switch (r)
        {
        case RI_VBK:
        case RI_BCPS:
        case RI_OCPS:
        case RI_BCPD:
        case RI_OCPD:
        case RI_SVBK:
        case RI_KEY1:
        case RI_HDMA1:
        case RI_HDMA2:
        case RI_HDMA3:
        case RI_HDMA4:
        case RI_HDMA5:
            return;
        }
    }
    switch(r)
    {
    case RI_TIMA:
    case RI_TMA:
    case RI_TAC:
    case RI_SCY:
    case RI_SCX:
    case RI_WY:
    case RI_WX:
        REG(r) = b;
        break;
    case RI_BGP:
        if (R_BGP == b) break;
        pal_write_dmg(0, 0, b); 
        pal_write_dmg(8, 1, b); 
        R_BGP = b;
        break;
    case RI_OBP0:
        if (R_OBP0 == b) break;
        pal_write_dmg(64, 2, b);
        R_OBP0 = b;
        break;
    case RI_OBP1:
        if (R_OBP1 == b) break;
        pal_write_dmg(72, 3, b);
        R_OBP1 = b;
        break;
    case RI_IF:
    case RI_IE:
        REG(r) = b & 0x1F;
        break;
    case RI_P1:
        REG(r) = b;
        pad_refresh();
        break;
    case RI_SC:
        /* FIXME - this is a hack for stupid roms that probe serial */
        if ((b & 0x81) == 0x81)
        {
            R_SB = 0xff;
            hw_interrupt(IF_SERIAL, IF_SERIAL);
            hw_interrupt(0, IF_SERIAL);
        }
        R_SC = b; /* & 0x7f; */
        break;
    case RI_DIV:
        REG(r) = 0;
        break;
    case RI_LCDC:
        lcdc_change(b);
        break;
    case RI_STAT:
		REG(r) = (REG(r) & 0x07) | (b & 0x78);
		stat_trigger();
        break;
    case RI_LYC:
        REG(r) = b;
        stat_trigger();
        break;
    case RI_VBK:
        REG(r) = b | 0xFE;
        mem_updatemap();
        break;
    case RI_BCPS:
        R_BCPS = b & 0xBF;
        R_BCPD = lcd.pal[b & 0x3F];
        break;
    case RI_OCPS:
        R_OCPS = b & 0xBF;
        R_OCPD = lcd.pal[64 + (b & 0x3F)];
        break;
    case RI_BCPD:
        R_BCPD = b;
        pal_write(R_BCPS & 0x3F, b);
        if (R_BCPS & 0x80) R_BCPS = (R_BCPS+1) & 0xBF;
        break;
    case RI_OCPD:
        R_OCPD = b;
        pal_write(64 + (R_OCPS & 0x3F), b);
        if (R_OCPS & 0x80) R_OCPS = (R_OCPS+1) & 0xBF;
        break;
    case RI_SVBK:
        REG(r) = b & 0x07;
        mem_updatemap();
        break;
    case RI_DMA:
        hw_dma(b);
        break;
    case RI_KEY1:
        REG(r) = (REG(r) & 0x80) | (b & 0x01);
        break;
    case RI_HDMA1:
        REG(r) = b;
        break;
    case RI_HDMA2:
        REG(r) = b & 0xF0;
        break;
    case RI_HDMA3:
        REG(r) = b & 0x1F;
        break;
    case RI_HDMA4:
        REG(r) = b & 0xF0;
        break;
    case RI_HDMA5:
        hw_hdma_cmd(b);
        break;
    }
}


static byte ioreg_read(byte r)
{
    switch(r)
    {
    case RI_SC:
        r = R_SC;
        R_SC &= 0x7f;
        return r;
    case RI_P1:
    case RI_SB:
    case RI_DIV:
    case RI_TIMA:
    case RI_TMA:
    case RI_TAC:
    case RI_LCDC:
    case RI_STAT:
    case RI_SCY:
    case RI_SCX:
    case RI_LY:
    case RI_LYC:
    case RI_BGP:
    case RI_OBP0:
    case RI_OBP1:
    case RI_WY:
    case RI_WX:
    case RI_IE:
    case RI_IF:
        return REG(r);
    case RI_VBK:
    case RI_BCPS:
    case RI_OCPS:
    case RI_BCPD:
    case RI_OCPD:
    case RI_SVBK:
    case RI_KEY1:
    case RI_HDMA1:
    case RI_HDMA2:
    case RI_HDMA3:
    case RI_HDMA4:
    case RI_HDMA5:
        if (hw.cgb) return REG(r);
        /* Intentional fallthrough */
    default:
        return 0xff;
    }
}



/*
 * Memory bank controllers typically intercept write attempts to
 * 0000-7FFF, using the address and byte written as instructions to
 * change rom or sram banks, control special hardware, etc.
 *
 * mbc_write takes an address (which should be in the proper range)
 * and a byte value written to the address.
 */

static void mbc_write(int a, byte b) ICODE_ATTR;
static void mbc_write(int a, byte b)
{
    byte ha = (a>>12);

    /* printf("mbc %d: rom bank %02X -[%04X:%02X]-> ", mbc.type, mbc.rombank, a, b); */
    switch (mbc.type)
    {
    case MBC_MBC1:
        switch (ha & 0xE)
        {
        case 0x0:
            mbc.enableram = ((b & 0x0F) == 0x0A);
            break;
        case 0x2:
            if ((b & 0x1F) == 0) b = 0x01;
            mbc.rombank = (mbc.rombank & 0x60) | (b & 0x1F);
            break;
        case 0x4:
            if (mbc.model)
            {
                mbc.rambank = b & 0x03;
                break;
            }
            mbc.rombank = (mbc.rombank & 0x1F) | ((int)(b&3)<<5);
            break;
        case 0x6:
            mbc.model = b & 0x1;
            break;
        }
        break;
    case MBC_MBC2: /* is this at all right? */
        if ((a & 0x0100) == 0x0000)
        {
            mbc.enableram = ((b & 0x0F) == 0x0A);
            break;
        }
        if ((a & 0xE100) == 0x2100)
        {
            mbc.rombank = b & 0x0F;
            break;
        }
        break;
    case MBC_MBC3:
        switch (ha & 0xE)
        {
        case 0x0:
            mbc.enableram = ((b & 0x0F) == 0x0A);
            break;
        case 0x2:
            if ((b & 0x7F) == 0) b = 0x01;
            mbc.rombank = b & 0x7F;
            break;
        case 0x4:
            rtc.sel = b & 0x0f;
            mbc.rambank = b & 0x03;
            break;
        case 0x6:
            rtc_latch(b);
            break;
        }
        break;
    case MBC_RUMBLE:
        switch (ha & 0xF)
        {
        case 0x4:
        case 0x5:
            /* FIXME - save high bit as rumble state */
            /* mask off high bit */
            b &= 0x7;
            break;
        }
        /* fall thru */
    case MBC_MBC5:
        switch (ha & 0xF)
        {
        case 0x0:
        case 0x1:
            mbc.enableram = ((b & 0x0F) == 0x0A);
            break;
        case 0x2:
            if ((b & 0xFF) == 0) b = 0x01;
            mbc.rombank = (mbc.rombank & 0x100) | (b & 0xFF);
            break;
        case 0x3:
            mbc.rombank = (mbc.rombank & 0xFF) | ((int)(b&1)<<8);
            break;
        case 0x4:
        case 0x5:
            mbc.rambank = b & 0x0f;
            break;
        }
        break;
    case MBC_HUC1: /* FIXME - this is all guesswork -- is it right??? */
        switch (ha & 0xE)
        {
        case 0x0:
            mbc.enableram = ((b & 0x0F) == 0x0A);
            break;
        case 0x2:
            if ((b & 0x1F) == 0) b = 0x01;
            mbc.rombank = (mbc.rombank & 0x60) | (b & 0x1F);
            break;
        case 0x4:
            if (mbc.model)
            {
                mbc.rambank = b & 0x03;
                break;
            }
            mbc.rombank = (mbc.rombank & 0x1F) | ((int)(b&3)<<5);
            break;
        case 0x6:
            mbc.model = b & 0x1;
            break;
        }
        break;
	case MBC_HUC3: /* FIXME - this is all guesswork -- is it right??? */
		switch (ha & 0xE)
		{
		case 0x0:
			mbc.enableram = ((b & 0x0F) == 0x0A);
			break;
		case 0x2:
			if (!b) b = 1;
			mbc.rombank = b;
			break;
		case 0x4:
			if (mbc.model)
			{
				mbc.rambank = b & 0x03;
				break;
			}
			break;
		case 0x6:
			mbc.model = b & 1;
			break;
		}
		break;
	}
    mbc.rombank &= (mbc.romsize - 1);
    mbc.rambank &= (mbc.ramsize - 1);
    mem_updatemap();
}


/*
 * mem_write is the basic write function. Although it should only be
 * called when the write map contains a NULL for the requested address
 * region, it accepts writes to any address.
 */

void mem_write(int a, byte b)
{
    int n;
    byte ha = (a>>12) & 0xE;

    /* printf("write to 0x%04X: 0x%02X\n", a, b); */
    switch (ha)
    {
    case 0x0:
    case 0x2:
    case 0x4:
    case 0x6:
        mbc_write(a, b);
        break;
    case 0x8:
        /* if ((R_STAT & 0x03) == 0x03) break; */
        vram_write(a & 0x1FFF, b);
        break;
    case 0xA:
        if (!mbc.enableram) break;
        if (rtc.sel&8)
        {
            rtc_write(b);
            break;
        }
        ram.sbank[mbc.rambank][a & 0x1FFF] = b;
        break;
    case 0xC:
        if ((a & 0xF000) == 0xC000)
        {
            ram.ibank[0][a & 0x0FFF] = b;
            break;
        }
        n = R_SVBK & 0x07;
        ram.ibank[n?n:1][a & 0x0FFF] = b;
        break;
    case 0xE:
        if (a < 0xFE00)
        {
            mem_write(a & 0xDFFF, b);
            break;
        }
        if ((a & 0xFF00) == 0xFE00)
        {
            /* if (R_STAT & 0x02) break; */
            if (a < 0xFEA0) lcd.oam.mem[a & 0xFF] = b;
            break;
        }
        /* return writehi(a & 0xFF, b); */
        if (a >= 0xFF10 && a <= 0xFF3F)
        {
            if(options.sound)
                sound_write(a & 0xFF, b);
            break;
        }
        if ((a & 0xFF80) == 0xFF80 && a != 0xFFFF)
        {
            ram.hi[a & 0xFF] = b;
            break;
        }
        ioreg_write(a & 0xFF, b);
    }
}


/*
 * mem_read is the basic read function...not useful for much anymore
 * with the read map, but it's still necessary for the final messy
 * region.
 */

byte mem_read(int a)
{
    int n;
    byte ha = (a>>12) & 0xE;

    /* printf("read %04x\n", a); */
    switch (ha)
    {
    case 0x0:
    case 0x2:
        return rom.bank[0][a];
    case 0x4:
    case 0x6:
        return rom.bank[mbc.rombank][a & 0x3FFF];
    case 0x8:
        /* if ((R_STAT & 0x03) == 0x03) return 0xFF; */
        return lcd.vbank[R_VBK&1][a & 0x1FFF];
	case 0xA:
		if (!mbc.enableram)
			return 0xFF;
		if (rtc.sel&8)
			return rtc.regs[rtc.sel&7];
		return ram.sbank[mbc.rambank][a & 0x1FFF];
    case 0xC:
        if ((a & 0xF000) == 0xC000)
            return ram.ibank[0][a & 0x0FFF];
        n = R_SVBK & 0x07;
        return ram.ibank[n?n:1][a & 0x0FFF];
    case 0xE:
        if (a < 0xFE00) return mem_read(a & 0xDFFF);
        if ((a & 0xFF00) == 0xFE00)
        {
            /* if (R_STAT & 0x02) return 0xFF; */
            if (a < 0xFEA0) return lcd.oam.mem[a & 0xFF];
            return 0xFF;
        }
        /* return readhi(a & 0xFF); */
        if (a == 0xFFFF) return REG(0xFF);
        if (a >= 0xFF10 && a <= 0xFF3F)
        {
            if(options.sound)
                return sound_read(a & 0xFF);
            else
                return 1;
        }
        if ((a & 0xFF80) == 0xFF80)
            return ram.hi[a & 0xFF];
        return ioreg_read(a & 0xFF);
    }
    return 0xff; /* not reached */
}

void mbc_reset(void)
{
    mbc.rombank = 1;
    mbc.rambank = 0;
    mbc.enableram = 0;
    mem_updatemap();
}
