/* FCE Ultra - NES/Famicom Emulator
 *
 * Copyright notice for this file:
 *  Copyright (C) 2006 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
 */

#include "mapinc.h"

static uint16 latche, latcheinit;
static uint16 addrreg0, addrreg1;
static uint8 dipswitch;
static void (*WSync)(void);
static readfunc defread;
static uint8 *WRAM = NULL;
static uint32 WRAMSIZE;

static DECLFW(LatchWrite) {
	latche = A;
	WSync();
}

static void LatchReset(void) {
	latche = latcheinit;
	WSync();
}

static void LatchPower(void) {
	latche = latcheinit;
	WSync();
	if (WRAM) {
		SetReadHandler(0x6000, 0xFFFF, CartBR);
		SetWriteHandler(0x6000, 0x7FFF, CartBW);
		FCEU_CheatAddRAM(WRAMSIZE >> 10, 0x6000, WRAM);
	} else
		SetReadHandler(0x6000, 0xFFFF, defread);
	SetWriteHandler(addrreg0, addrreg1, LatchWrite);
}

static void LatchClose(void) {
	if (WRAM)
		FCEU_gfree(WRAM);
	WRAM = NULL;
}

static void StateRestore(int version) {
	WSync();
}

static void Latch_Init(CartInfo *info, void (*proc)(void), readfunc func, uint16 linit, uint16 adr0, uint16 adr1, uint8 wram) {
	latcheinit = linit;
	addrreg0 = adr0;
	addrreg1 = adr1;
	WSync = proc;
	if (func != NULL)
		defread = func;
	else
		defread = CartBROB;
	info->Power = LatchPower;
	info->Reset = LatchReset;
	info->Close = LatchClose;
	if (wram) {
		WRAMSIZE = 8192;
		WRAM = (uint8*)FCEU_gmalloc(WRAMSIZE);
		SetupCartPRGMapping(0x10, WRAM, WRAMSIZE, 1);
		if (info->battery) {
			info->SaveGame[0] = WRAM;
			info->SaveGameLen[0] = WRAMSIZE;
		}
		AddExState(WRAM, WRAMSIZE, 0, "WRAM");
	}
	GameStateRestore = StateRestore;
	AddExState(&latche, 2, 0, "LATC");
}

//------------------ BMCD1038 ---------------------------

static void BMCD1038Sync(void) {
	if (latche & 0x80) {
		setprg16(0x8000, (latche & 0x70) >> 4);
		setprg16(0xC000, (latche & 0x70) >> 4);
	} else
		setprg32(0x8000, (latche & 0x60) >> 5);
	setchr8(latche & 7);
	setmirror(((latche & 8) >> 3) ^ 1);
}

static DECLFR(BMCD1038Read) {
	if (latche & 0x100)
		return dipswitch;
	else
		return CartBR(A);
}

static void BMCD1038Reset(void) {
	dipswitch++;
	dipswitch &= 3;
}

void BMCD1038_Init(CartInfo *info) {
	Latch_Init(info, BMCD1038Sync, BMCD1038Read, 0x0000, 0x8000, 0xFFFF, 0);
	info->Reset = BMCD1038Reset;
	AddExState(&dipswitch, 1, 0, "DIPSW");
}

//------------------ UNL43272 ---------------------------
// mapper much complex, including 16K bankswitching
static void UNL43272Sync(void) {
	if ((latche & 0x81) == 0x81) {
		setprg32(0x8000, (latche & 0x38) >> 3);
	} else
		FCEU_printf("unrecognized command %04!\n", latche);
	setchr8(0);
	setmirror(0);
}

static DECLFR(UNL43272Read) {
	if (latche & 0x400)
		return CartBR(A & 0xFE);
	else
		return CartBR(A);
}

static void UNL43272Reset(void) {
	latche = 0;
	UNL43272Sync();
}

void UNL43272_Init(CartInfo *info) {
	Latch_Init(info, UNL43272Sync, UNL43272Read, 0x0081, 0x8000, 0xFFFF, 0);
	info->Reset = UNL43272Reset;
	AddExState(&dipswitch, 1, 0, "DIPSW");
}

//------------------ Map 058 ---------------------------

static void BMCGK192Sync(void) {
	if (latche & 0x40) {
		setprg16(0x8000, latche & 7);
		setprg16(0xC000, latche & 7);
	} else
		setprg32(0x8000, (latche >> 1) & 3);
	setchr8((latche >> 3) & 7);
	setmirror(((latche & 0x80) >> 7) ^ 1);
}

void BMCGK192_Init(CartInfo *info) {
	Latch_Init(info, BMCGK192Sync, NULL, 0x0000, 0x8000, 0xFFFF, 0);
}

//------------------ Map 059 ---------------------------
// One more forgotten mapper
static void M59Sync(void) {
	setprg32(0x8000, (latche >> 4) & 7);
	setchr8(latche & 0x7);
	setmirror((latche >> 3) & 1);
}

static DECLFR(M59Read) {
	if (latche & 0x100)
		return 0;
	else
		return CartBR(A);
}

void Mapper59_Init(CartInfo *info) {
	Latch_Init(info, M59Sync, M59Read, 0x0000, 0x8000, 0xFFFF, 0);
}

//------------------ Map 061 ---------------------------
static void M61Sync(void) {
	if (((latche & 0x10) << 1) ^ (latche & 0x20)) {
		setprg16(0x8000, ((latche & 0xF) << 1) | (((latche & 0x20) >> 4)));
		setprg16(0xC000, ((latche & 0xF) << 1) | (((latche & 0x20) >> 4)));
	} else
		setprg32(0x8000, latche & 0xF);
	setchr8(0);
	setmirror(((latche >> 7) & 1) ^ 1);
}

void Mapper61_Init(CartInfo *info) {
	Latch_Init(info, M61Sync, NULL, 0x0000, 0x8000, 0xFFFF, 0);
}

//------------------ Map 092 ---------------------------
// Another two-in-one mapper, two Jaleco carts uses similar
// hardware, but with different wiring.
// Original code provided by LULU
// Additionally, PCB contains DSP extra sound chip, used for voice samples (unemulated)

static void M92Sync(void) {
	uint8 reg = latche & 0xF0;
	setprg16(0x8000, 0);
	if (latche >= 0x9000) {
		switch (reg) {
		case 0xD0: setprg16(0xc000, latche & 15); break;
		case 0xE0: setchr8(latche & 15); break;
		}
	} else {
		switch (reg) {
		case 0xB0: setprg16(0xc000, latche & 15); break;
		case 0x70: setchr8(latche & 15); break;
		}
	}
}

void Mapper92_Init(CartInfo *info) {
	Latch_Init(info, M92Sync, NULL, 0x80B0, 0x8000, 0xFFFF, 0);
}

//------------------ Map 200 ---------------------------

static void M200Sync(void) {
	setprg16(0x8000, latche & 7);
	setprg16(0xC000, latche & 7);
	setchr8(latche & 7);
	setmirror((latche & 8) >> 3);
}

void Mapper200_Init(CartInfo *info) {
	Latch_Init(info, M200Sync, NULL, 0xFFFF, 0x8000, 0xFFFF, 0);
}

//------------------ Map 201 ---------------------------

static void M201Sync(void) {
	if (latche & 8) {
		setprg32(0x8000, latche & 3);
		setchr8(latche & 3);
	} else {
		setprg32(0x8000, 0);
		setchr8(0);
	}
}

void Mapper201_Init(CartInfo *info) {
	Latch_Init(info, M201Sync, NULL, 0xFFFF, 0x8000, 0xFFFF, 0);
}

//------------------ Map 202 ---------------------------

static void M202Sync(void) {
	// According to more carefull hardware tests and PCB study
	int32 mirror = latche & 1;
	int32 bank = (latche >> 1) & 0x7;
	int32 select = (mirror & (bank >> 2));
	setprg16(0x8000, select ? (bank & 6) | 0 : bank);
	setprg16(0xc000, select ? (bank & 6) | 1 : bank);
	setmirror(mirror ^ 1);
	setchr8(bank);
}

void Mapper202_Init(CartInfo *info) {
	Latch_Init(info, M202Sync, NULL, 0x0000, 0x8000, 0xFFFF, 0);
}

//------------------ Map 204 ---------------------------

static void M204Sync(void) {
	int32 tmp2 = latche & 0x6;
	int32 tmp1 = tmp2 + ((tmp2 == 0x6) ? 0 : (latche & 1));
	setprg16(0x8000, tmp1);
	setprg16(0xc000, tmp2 + ((tmp2 == 0x6) ? 1 : (latche & 1)));
	setchr8(tmp1);
	setmirror(((latche >> 4) & 1) ^ 1);
}

void Mapper204_Init(CartInfo *info) {
	Latch_Init(info, M204Sync, NULL, 0xFFFF, 0x8000, 0xFFFF, 0);
}

//------------------ Map 212 ---------------------------

static DECLFR(M212Read) {
	uint8 ret = CartBROB(A);
	if ((A & 0xE010) == 0x6000)
		ret |= 0x80;
	return ret;
}

static void M212Sync(void) {
	if (latche & 0x4000) {
		setprg32(0x8000, (latche >> 1) & 3);
	} else {
		setprg16(0x8000, latche & 7);
		setprg16(0xC000, latche & 7);
	}
	setchr8(latche & 7);
	setmirror(((latche >> 3) & 1) ^ 1);
}

void Mapper212_Init(CartInfo *info) {
	Latch_Init(info, M212Sync, M212Read, 0x0000, 0x8000, 0xFFFF, 0);
}

//------------------ Map 213 ---------------------------

static void M213Sync(void) {
	if(latche & 0x40) {
		setprg16(0x8000, (latche & 7));
		setprg16(0xC000, (latche & 7));
	} else {
		setprg32(0x8000, (latche >> 1) & 3);
	}
	setchr8((latche >> 3) & 7);
	setmirror(((latche & 1)^((latche >> 6) & 1)) ^ 1);
}

void Mapper213_Init(CartInfo *info) {
	Latch_Init(info, M213Sync, NULL, 0x0000, 0x8000, 0xFFFF, 0);
}

//------------------ Map 214 ---------------------------

static void M214Sync(void) {
	setprg16(0x8000, (latche >> 2) & 3);
	setprg16(0xC000, (latche >> 2) & 3);
	setchr8(latche & 3);
}

void Mapper214_Init(CartInfo *info) {
	Latch_Init(info, M214Sync, NULL, 0x0000, 0x8000, 0xFFFF, 0);
}

//------------------ Map 217 ---------------------------

static void M217Sync(void) {
	setprg32(0x8000, (latche >> 2) & 3);
	setchr8(latche & 7);
}

void Mapper217_Init(CartInfo *info) {
	Latch_Init(info, M217Sync, NULL, 0x0000, 0x8000, 0xFFFF, 0);
}

//------------------ Map 227 ---------------------------

static void M227Sync(void) {
	uint32 S = latche & 1;
	uint32 p = ((latche >> 2) & 0x1F) + ((latche & 0x100) >> 3);
	uint32 L = (latche >> 9) & 1;

// ok, according to nesdev wiki (refrenced to the nesdev dumping thread) there is a CHR write protection bit7.
// however, this bit clearly determined a specific PRG layout for some game but does not meant to have additional
// functionality. as I see from the menu code, it disables the chr writing before run an actual game.
// this fix here makes happy both waixing rpgs and multigame menus at once. can't veryfy it on a hardware
// but if I find some i'll definitly do this.

	if ((latche & 0xF000) == 0xF000)
		SetupCartCHRMapping(0, CHRptr[0], 0x2000, 0);
	else
		SetupCartCHRMapping(0, CHRptr[0], 0x2000, 1);

	if ((latche >> 7) & 1) {
		if (S) {
			setprg32(0x8000, p >> 1);
		} else {
			setprg16(0x8000, p);
			setprg16(0xC000, p);
		}
	} else {
		if (S) {
			if (L) {
				setprg16(0x8000, p & 0x3E);
				setprg16(0xC000, p | 7);
			} else {
				setprg16(0x8000, p & 0x3E);
				setprg16(0xC000, p & 0x38);
			}
		} else {
			if (L) {
				setprg16(0x8000, p);
				setprg16(0xC000, p | 7);
			} else {
				setprg16(0x8000, p);
				setprg16(0xC000, p & 0x38);
			}
		}
	}

	setmirror(((latche >> 1) & 1) ^ 1);
	setchr8(0);
	setprg8r(0x10, 0x6000, 0);
}

void Mapper227_Init(CartInfo *info) {
	Latch_Init(info, M227Sync, NULL, 0x0000, 0x8000, 0xFFFF, 1);
}

//------------------ Map 229 ---------------------------

static void M229Sync(void) {
	setchr8(latche);
	if (!(latche & 0x1e))
		setprg32(0x8000, 0);
	else {
		setprg16(0x8000, latche & 0x1F);
		setprg16(0xC000, latche & 0x1F);
	}
	setmirror(((latche >> 5) & 1) ^ 1);
}

void Mapper229_Init(CartInfo *info) {
	Latch_Init(info, M229Sync, NULL, 0x0000, 0x8000, 0xFFFF, 0);
}

//------------------ Map 231 ---------------------------

static void M231Sync(void) {
	setchr8(0);
	if (latche & 0x20)
		setprg32(0x8000, (latche >> 1) & 0x0F);
	else {
		setprg16(0x8000, latche & 0x1E);
		setprg16(0xC000, latche & 0x1E);
	}
	setmirror(((latche >> 7) & 1) ^ 1);
}

void Mapper231_Init(CartInfo *info) {
	Latch_Init(info, M231Sync, NULL, 0x0000, 0x8000, 0xFFFF, 0);
}

//------------------ Map 242 ---------------------------

static void M242Sync(void) {
	setchr8(0);
	setprg8r(0x10, 0x6000, 0);
	setprg32(0x8000, (latche >> 3) & 0xf);
	setmirror(((latche >> 1) & 1) ^ 1);
}

void Mapper242_Init(CartInfo *info) {
	Latch_Init(info, M242Sync, NULL, 0x0000, 0x8000, 0xFFFF, 1);
}

//------------------ 190in1 ---------------------------

static void BMC190in1Sync(void) {
	setprg16(0x8000, (latche >> 2) & 7);
	setprg16(0xC000, (latche >> 2) & 7);
	setchr8((latche >> 2) & 7);
	setmirror((latche & 1) ^ 1);
}

void BMC190in1_Init(CartInfo *info) {
	Latch_Init(info, BMC190in1Sync, NULL, 0x0000, 0x8000, 0xFFFF, 0);
}

//-------------- BMC810544-C-A1 ------------------------

static void BMC810544CA1Sync(void) {
	uint32 bank = latche >> 7;
	if (latche & 0x40)
		setprg32(0x8000, bank);
	else {
		setprg16(0x8000, (bank << 1) | ((latche >> 5) & 1));
		setprg16(0xC000, (bank << 1) | ((latche >> 5) & 1));
	}
	setchr8(latche & 0x0f);
	setmirror(((latche >> 4) & 1) ^ 1);
}

void BMC810544CA1_Init(CartInfo *info) {
	Latch_Init(info, BMC810544CA1Sync, NULL, 0x0000, 0x8000, 0xFFFF, 0);
}

//-------------- BMCNTD-03 ------------------------

static void BMCNTD03Sync(void) {
	// 1PPP Pmcc spxx xccc
	// 1000 0000 0000 0000 v
	// 1001 1100 0000 0100 h
	// 1011 1010 1100 0100
	uint32 prg = ((latche >> 10) & 0x1e);
	uint32 chr = ((latche & 0x0300) >> 5) | (latche & 7);
	if (latche & 0x80) {
		setprg16(0x8000, prg | ((latche >> 6) & 1));
		setprg16(0xC000, prg | ((latche >> 6) & 1));
	} else
		setprg32(0x8000, prg >> 1);
	setchr8(chr);
	setmirror(((latche >> 10) & 1) ^ 1);
}

void BMCNTD03_Init(CartInfo *info) {
	Latch_Init(info, BMCNTD03Sync, NULL, 0x0000, 0x8000, 0xFFFF, 0);
}

//-------------- BMCG-146 ------------------------

static void BMCG146Sync(void) {
	setchr8(0);
	if (latche & 0x800) {		// UNROM mode
		setprg16(0x8000, (latche & 0x1F) | (latche & ((latche & 0x40) >> 6)));
		setprg16(0xC000, (latche & 0x18) | 7);
	} else {
		if (latche & 0x40) {	// 16K mode
			setprg16(0x8000, latche & 0x1F);
			setprg16(0xC000, latche & 0x1F);
		} else {
			setprg32(0x8000, (latche >> 1) & 0x0F);
		}
	}
	setmirror(((latche & 0x80) >> 7) ^ 1);
}

void BMCG146_Init(CartInfo *info) {
	Latch_Init(info, BMCG146Sync, NULL, 0x0000, 0x8000, 0xFFFF, 0);
}
