/* FCE Ultra - NES/Famicom Emulator
 *
 * Copyright notice for this file:
 *  Copyright (C) 2011 CaH4e3
 *
 * 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; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * SL12 Protected 3-in-1 mapper hardware (VRC2, MMC3, MMC1)
 * the same as 603-5052 board (TODO: add reading registers, merge)
 * SL1632 2-in-1 protected board, similar to SL12 (TODO: find difference)
 *
 * Known PCB:
 *
 * Garou Densetsu Special (G0904.PCB, Huang-1, GAL dip: W conf.)
 * Kart Fighter (008, Huang-1, GAL dip: W conf.)
 * Somari (008, C5052-13, GAL dip: P conf., GK2-P/GK2-V maskroms)
 * Somari (008, Huang-1, GAL dip: W conf., GK1-P/GK1-V maskroms)
 * AV Mei Shao Nv Zhan Shi (aka AV Pretty Girl Fighting) (SL-12 PCB, Hunag-1, GAL dip: unk conf. SL-11A/SL-11B maskroms)
 * Samurai Spirits (Full version) (Huang-1, GAL dip: unk conf. GS-2A/GS-4A maskroms)
 * Contra Fighter (603-5052 PCB, C5052-3, GAL dip: unk conf. SC603-A/SCB603-B maskroms)
 *
 */

#include "mapinc.h"

static uint8 mode;
static uint8 vrc2_chr[8], vrc2_prg[2], vrc2_mirr;
static uint8 mmc3_regs[10], mmc3_ctrl, mmc3_mirr;
static uint8 IRQCount, IRQLatch, IRQa;
static uint8 IRQReload;
static uint8 mmc1_regs[4], mmc1_buffer, mmc1_shift;

static SFORMAT StateRegs[] =
{
	{ &mode, 1, "MODE" },
	{ vrc2_chr, 8, "VRCC" },
	{ vrc2_prg, 2, "VRCP" },
	{ &vrc2_mirr, 1, "VRCM" },
	{ mmc3_regs, 10, "M3RG" },
	{ &mmc3_ctrl, 1, "M3CT" },
	{ &mmc3_mirr, 1, "M3MR" },
	{ &IRQReload, 1, "IRQR" },
	{ &IRQCount, 1, "IRQC" },
	{ &IRQLatch, 1, "IRQL" },
	{ &IRQa, 1, "IRQA" },
	{ mmc1_regs, 4, "M1RG" },
	{ &mmc1_buffer, 1, "M1BF" },
	{ &mmc1_shift, 1, "M1MR" },
	{ 0 }
};

static void SyncPRG(void) {
	switch (mode & 3) {
	case 0:
		setprg8(0x8000, vrc2_prg[0]);
		setprg8(0xA000, vrc2_prg[1]);
		setprg8(0xC000, ~1);
		setprg8(0xE000, ~0);
		break;
	case 1:
	{
		uint32 swap = (mmc3_ctrl >> 5) & 2;
		setprg8(0x8000, mmc3_regs[6 + swap]);
		setprg8(0xA000, mmc3_regs[7]);
		setprg8(0xC000, mmc3_regs[6 + (swap ^ 2)]);
		setprg8(0xE000, mmc3_regs[9]);
		break;
	}
	case 2:
	case 3:
	{
		uint8 bank = mmc1_regs[3] & 0xF;
		if (mmc1_regs[0] & 8) {
			if (mmc1_regs[0] & 4) {
				setprg16(0x8000, bank);
				setprg16(0xC000, 0x0F);
			} else {
				setprg16(0x8000, 0);
				setprg16(0xC000, bank);
			}
		} else
			setprg32(0x8000, bank >> 1);
		break;
	}
	}
}

static void SyncCHR(void) {
	uint32 base = (mode & 4) << 6;
	switch (mode & 3) {
	case 0:
		setchr1(0x0000, base | vrc2_chr[0]);
		setchr1(0x0400, base | vrc2_chr[1]);
		setchr1(0x0800, base | vrc2_chr[2]);
		setchr1(0x0c00, base | vrc2_chr[3]);
		setchr1(0x1000, base | vrc2_chr[4]);
		setchr1(0x1400, base | vrc2_chr[5]);
		setchr1(0x1800, base | vrc2_chr[6]);
		setchr1(0x1c00, base | vrc2_chr[7]);
		break;
	case 1: {
		uint32 swap = (mmc3_ctrl & 0x80) << 5;
		setchr1(0x0000 ^ swap, base | ((mmc3_regs[0]) & 0xFE));
		setchr1(0x0400 ^ swap, base | (mmc3_regs[0] | 1));
		setchr1(0x0800 ^ swap, base | ((mmc3_regs[1]) & 0xFE));
		setchr1(0x0c00 ^ swap, base | (mmc3_regs[1] | 1));
		setchr1(0x1000 ^ swap, base | mmc3_regs[2]);
		setchr1(0x1400 ^ swap, base | mmc3_regs[3]);
		setchr1(0x1800 ^ swap, base | mmc3_regs[4]);
		setchr1(0x1c00 ^ swap, base | mmc3_regs[5]);
		break;
	}
	case 2:
	case 3:
		if (mmc1_regs[0] & 0x10) {
			setchr4(0x0000, mmc1_regs[1]);
			setchr4(0x1000, mmc1_regs[2]);
		} else
			setchr8(mmc1_regs[1] >> 1);
		break;
	}
}

static void SyncMIR(void) {
	switch (mode & 3) {
	case 0: {
		setmirror((vrc2_mirr & 1) ^ 1);
		break;
	}
	case 1: {
		setmirror((mmc3_mirr & 1) ^ 1);
		break;
	}
	case 2:
	case 3: {
		switch (mmc1_regs[0] & 3) {
		case 0: setmirror(MI_0); break;
		case 1: setmirror(MI_1); break;
		case 2: setmirror(MI_V); break;
		case 3: setmirror(MI_H); break;
		}
		break;
	}
	}
}

static void Sync(void) {
	SyncPRG();
	SyncCHR();
	SyncMIR();
}

static DECLFW(UNLSL12ModeWrite) {
//  FCEU_printf("%04X:%02X\n",A,V);
	if ((A & 0x4100) == 0x4100) {
		mode = V;
		if (A & 1) {	// hacky hacky, there are two configuration modes on SOMARI HUANG-1 PCBs
						// Solder pads with P1/P2 shorted called SOMARI P,
						// Solder pads with W1/W2 shorted called SOMARI W
						// Both identical 3-in-1 but W wanted MMC1 registers
						// to be reset when switch to MMC1 mode P one - doesn't
						// There is issue with W version of Somari at starting copyrights
			mmc1_regs[0] = 0xc;
			mmc1_regs[3] = 0;
			mmc1_buffer = 0;
			mmc1_shift = 0;
		}
		Sync();
	}
}

static DECLFW(UNLSL12Write) {
//  FCEU_printf("%04X:%02X\n",A,V);
	switch (mode & 3) {
	case 0: {
		if ((A >= 0xB000) && (A <= 0xE003)) {
			int32 ind = ((((A & 2) | (A >> 10)) >> 1) + 2) & 7;
			int32 sar = ((A & 1) << 2);
			vrc2_chr[ind] = (vrc2_chr[ind] & (0xF0 >> sar)) | ((V & 0x0F) << sar);
			SyncCHR();
		} else
			switch (A & 0xF000) {
			case 0x8000: vrc2_prg[0] = V; SyncPRG(); break;
			case 0xA000: vrc2_prg[1] = V; SyncPRG(); break;
			case 0x9000: vrc2_mirr = V; SyncMIR(); break;
			}
		break;
	}
	case 1: {
		switch (A & 0xE001) {
		case 0x8000: {
			uint8 old_ctrl = mmc3_ctrl;
			mmc3_ctrl = V;
			if ((old_ctrl & 0x40) != (mmc3_ctrl & 0x40))
				SyncPRG();
			if ((old_ctrl & 0x80) != (mmc3_ctrl & 0x80))
				SyncCHR();
			break;
		}
		case 0x8001:
			mmc3_regs[mmc3_ctrl & 7] = V;
			if ((mmc3_ctrl & 7) < 6)
				SyncCHR();
			else
				SyncPRG();
			break;
		case 0xA000:
			mmc3_mirr = V;
			SyncMIR();
			break;
		case 0xC000:
			IRQLatch = V;
			break;
		case 0xC001:
			IRQReload = 1;
			break;
		case 0xE000:
			X6502_IRQEnd(FCEU_IQEXT);
			IRQa = 0;
			break;
		case 0xE001:
			IRQa = 1;
			break;
		}
		break;
	}
	case 2:
	case 3: {
		if (V & 0x80) {
			mmc1_regs[0] |= 0xc;
			mmc1_buffer = mmc1_shift = 0;
			SyncPRG();
		} else {
			uint8 n = (A >> 13) - 4;
			mmc1_buffer |= (V & 1) << (mmc1_shift++);
			if (mmc1_shift == 5) {
				mmc1_regs[n] = mmc1_buffer;
				mmc1_buffer = mmc1_shift = 0;
				switch (n) {
				case 0: SyncMIR();
				case 2: SyncCHR();
				case 3:
				case 1: SyncPRG();
				}
			}
		}
		break;
	}
	}
}

static void UNLSL12HBIRQ(void) {
	if ((mode & 3) == 1) {
		int32 count = IRQCount;
		if (!count || IRQReload) {
			IRQCount = IRQLatch;
			IRQReload = 0;
		} else
			IRQCount--;
		if (!IRQCount) {
			if (IRQa)
				X6502_IRQBegin(FCEU_IQEXT);
		}
	}
}

static void StateRestore(int version) {
	Sync();
}

static void UNLSL12Power(void) {
	mode = 0;
	vrc2_chr[0] = ~0;
	vrc2_chr[1] = ~0;
	vrc2_chr[2] = ~0;
	vrc2_chr[3] = ~0; // W conf. of Somari wanted CHR3 has to be set to BB bank (or similar), but doesn't do that directly
	vrc2_chr[4] = 4;
	vrc2_chr[5] = 5;
	vrc2_chr[6] = 6;
	vrc2_chr[7] = 7;
	vrc2_prg[0] = 0;
	vrc2_prg[1] = 1;
	vrc2_mirr = 0;
	mmc3_regs[0] = 0;
	mmc3_regs[1] = 2;
	mmc3_regs[2] = 4;
	mmc3_regs[3] = 5;
	mmc3_regs[4] = 6;
	mmc3_regs[5] = 7;
	mmc3_regs[6] = ~3;
	mmc3_regs[7] = ~2;
	mmc3_regs[8] = ~1;
	mmc3_regs[9] = ~0;
	mmc3_ctrl = mmc3_mirr = IRQCount = IRQLatch = IRQa = 0;
	mmc1_regs[0] = 0xc;
	mmc1_regs[1] = 0;
	mmc1_regs[2] = 0;
	mmc1_regs[3] = 0;
	mmc1_buffer = 0;
	mmc1_shift = 0;
	Sync();
	SetReadHandler(0x8000, 0xFFFF, CartBR);
	SetWriteHandler(0x4100, 0x7FFF, UNLSL12ModeWrite);
	SetWriteHandler(0x8000, 0xFFFF, UNLSL12Write);
}

void UNLSL12_Init(CartInfo *info) {
	info->Power = UNLSL12Power;
	GameHBIRQHook = UNLSL12HBIRQ;
	GameStateRestore = StateRestore;
	AddExState(&StateRegs, ~0, 0, 0);
}
