/* FCE Ultra - NES/Famicom Emulator
 *
 * Copyright notice for this file:
 *  Copyright (C) 2005-2019 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
 *
 * VRC-V (CAI Shogakko no Sansu)
 *
 */

#include "mapinc.h"

//#define CAI_DEBUG

// main tiles RAM is 8K in size, but unless other non-CHR ROM type carts,
// this one accesses the $0000 and $1000 pages based on extra NT RAM on board
// which is similar to MMC5 but much simpler because there are no additional
// bankings here.
// extra NT RAM handling is in PPU code now.

static uint16 CHRSIZE = 8192;
// there are two separate WRAMs 8K each, on main system cartridge (not battery
// backed), and one on the daughter cart (with battery). both are accessed
// via the same registers with additional selector flags.
static uint16 WRAMSIZE = 8192 + 8192;
static uint8 *CHRRAM = NULL;
static uint8 *WRAM = NULL;

static uint8 IRQa, K4IRQ;
static uint32 IRQLatch, IRQCount;

// some kind of 16-bit text  encoding (actually 14-bit) used in game resources
// may be converted by the hardware into the tile indexes for internal CHR ROM
// not sure whey they made it hardware, because most of calculations are just
// bit shifting. the main purpose of this table is to calculate actual CHR ROM
// bank for every character. there is a some kind of regularity, so this table
// may be calculated in software easily.

// table read out from hardware registers as is

///*
static uint8 conv_tbl[4][8] = {
	{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
	{ 0x00, 0x00, 0x40, 0x10, 0x28, 0x00, 0x18, 0x30 },
	{ 0x00, 0x00, 0x48, 0x18, 0x30, 0x08, 0x20, 0x38 },
	{ 0x00, 0x00, 0x80, 0x20, 0x38, 0x10, 0x28, 0xB0 }
};
//*/
/*
static uint8 conv_tbl[64][4] = {
	{ 0x40, 0x40, 0x40, 0x40 }, // 00  | A  - 40 41 42 43 44 45 46 47
	{ 0x41, 0x41, 0x41, 0x41 }, // 02  | B  - 48 49 4A 4B 4C 4D 4E 4F
	{ 0x42, 0x42, 0x42, 0x42 }, // 04  | C  - 50 51 52 53 54 55 56 57
	{ 0x43, 0x43, 0x43, 0x43 }, // 06  | D  - 58 59 5A 5B 5C 5D 5E 5F
	{ 0x44, 0x44, 0x44, 0x44 }, // 08  | E  - 60 61 62 63 64 65 66 67
	{ 0x45, 0x45, 0x45, 0x45 }, // 0A  | F  - 68 69 6A 6B 6C 6D 6E 6F
	{ 0x46, 0x46, 0x46, 0x46 }, // 0C  | G  - 70 71 72 73 74 75 76 77
	{ 0x47, 0x47, 0x47, 0x47 }, // 0E  | H  - 78 79 7A 7B 7C 7D 7E 7F
	{ 0x40, 0x40, 0x40, 0x40 }, // 10  |
	{ 0x41, 0x41, 0x41, 0x41 }, // 12  +----------------------------
	{ 0x42, 0x42, 0x42, 0x42 }, // 14  | A  A  A  A
	{ 0x43, 0x43, 0x43, 0x43 }, // 16  | A  A  A  A
	{ 0x44, 0x44, 0x44, 0x44 }, // 18  | A  A' B' A"
	{ 0x45, 0x45, 0x45, 0x45 }, // 1A  | A  C  D  E
	{ 0x46, 0x46, 0x46, 0x46 }, // 1C  | A  F  G  H
	{ 0x47, 0x47, 0x47, 0x47 }, // 1E  | A  A  B  C
	{ 0x40, 0x40, 0x48, 0x44 }, // 20  | A  D  E  F
	{ 0x41, 0x41, 0x49, 0x45 }, // 22  | A  G  H  G"
	{ 0x42, 0x42, 0x4A, 0x46 }, // 24  +----------------------------
	{ 0x43, 0x43, 0x4B, 0x47 }, // 26  | A' - 40 41 42 43 40 41 42 43
	{ 0x44, 0x40, 0x48, 0x44 }, // 28  | A" - 44 45 46 47 44 45 46 47
	{ 0x45, 0x41, 0x49, 0x45 }, // 2A  | B' - 48 49 4A 4B 48 49 4A 4B
	{ 0x46, 0x42, 0x4A, 0x46 }, // 2C  | G" - 74 75 76 77 74 75 76 77
	{ 0x47, 0x43, 0x4B, 0x47 }, // 2E
	{ 0x40, 0x50, 0x58, 0x60 }, // 30
	{ 0x41, 0x51, 0x59, 0x61 }, // 32
	{ 0x42, 0x52, 0x5A, 0x62 }, // 34
	{ 0x43, 0x53, 0x5B, 0x63 }, // 36
	{ 0x44, 0x54, 0x5C, 0x64 }, // 38
	{ 0x45, 0x55, 0x5D, 0x65 }, // 3A
	{ 0x46, 0x56, 0x5E, 0x66 }, // 3C
	{ 0x47, 0x57, 0x5F, 0x67 }, // 3E
	{ 0x40, 0x68, 0x70, 0x78 }, // 40
	{ 0x41, 0x69, 0x71, 0x79 }, // 42
	{ 0x42, 0x6A, 0x72, 0x7A }, // 44
	{ 0x43, 0x6B, 0x73, 0x7B }, // 46
	{ 0x44, 0x6C, 0x74, 0x7C }, // 48
	{ 0x45, 0x6D, 0x75, 0x7D }, // 4A
	{ 0x46, 0x6E, 0x76, 0x7E }, // 4C
	{ 0x47, 0x6F, 0x77, 0x7F }, // 4E
	{ 0x40, 0x40, 0x48, 0x50 }, // 50
	{ 0x41, 0x41, 0x49, 0x51 }, // 52
	{ 0x42, 0x42, 0x4A, 0x52 }, // 54
	{ 0x43, 0x43, 0x4B, 0x53 }, // 56
	{ 0x44, 0x44, 0x4C, 0x54 }, // 58
	{ 0x45, 0x45, 0x4D, 0x55 }, // 5A
	{ 0x46, 0x46, 0x4E, 0x56 }, // 5C
	{ 0x47, 0x47, 0x4F, 0x57 }, // 5E
	{ 0x40, 0x58, 0x60, 0x68 }, // 60
	{ 0x41, 0x59, 0x61, 0x69 }, // 62
	{ 0x42, 0x5A, 0x62, 0x6A }, // 64
	{ 0x43, 0x5B, 0x63, 0x6B }, // 66
	{ 0x44, 0x5C, 0x64, 0x6C }, // 68
	{ 0x45, 0x5D, 0x65, 0x6D }, // 6A
	{ 0x46, 0x5E, 0x66, 0x6E }, // 6C
	{ 0x47, 0x5F, 0x67, 0x6F }, // 6E
	{ 0x40, 0x70, 0x78, 0x74 }, // 70
	{ 0x41, 0x71, 0x79, 0x75 }, // 72
	{ 0x42, 0x72, 0x7A, 0x76 }, // 74
	{ 0x43, 0x73, 0x7B, 0x77 }, // 76
	{ 0x44, 0x74, 0x7C, 0x74 }, // 78
	{ 0x45, 0x75, 0x7D, 0x75 }, // 7A
	{ 0x46, 0x76, 0x7E, 0x76 }, // 7C
	{ 0x47, 0x77, 0x7F, 0x77 }, // 7E
};
*/

static uint8 regs[16];
static SFORMAT StateRegs[] =
{
	{ &IRQCount, 1, "IRQC" },
	{ &IRQLatch, 1, "IRQL" },
	{ &IRQa, 1, "IRQA" },
	{ &K4IRQ, 1, "KIRQ" },
	{ regs, 16, "REGS" },
	{ 0 }
};

static void chrSync(void) {
	setchr4r(0x10, 0x0000, regs[5] & 1);
	// 30.06.19 CaH4e3 there is much more complicated behaviour with second banking register, you may actually
	// view the content of the internal character CHR rom via this window, but it is useless because hardware
	// does not use this area to access the internal ROM. not sure why they did this, but I see no need to
	// emulate this behaviour carefully, unless I find something that I missed...
	setchr4r(0x10, 0x1000, 1);
}

static void Sync(void) {
	chrSync();
	setprg4r(0x10, 0x6000, (regs[0] & 1) | (regs[0] >> 2));	// two 4K banks are identical, either internal or excernal
	setprg4r(0x10, 0x7000, (regs[1] & 1) | (regs[1] >> 2)); // SRAMs may be mapped in any bank independently
	if (PRGptr[1] == NULL) {	// for iNES 2.0 version it even more hacky lol
		setprg8(0x8000, (regs[2] & 0x3F) + ((regs[2] & 0x40) >> 2));
		setprg8(0xA000, (regs[3] & 0x3F) + ((regs[3] & 0x40) >> 2));
		setprg8(0xC000, (regs[4] & 0x3F) + ((regs[4] & 0x40) >> 2));
		setprg8(0xE000, 0x10 + 0x3F);
	} else {
		setprg8r((regs[2] >> 6) & 1, 0x8000, (regs[2] & 0x3F));
		setprg8r((regs[3] >> 6) & 1, 0xA000, (regs[3] & 0x3F));
		setprg8r((regs[4] >> 6) & 1, 0xC000, (regs[4] & 0x3F));
		setprg8r(1, 0xE000, ~0);	// always sees the last bank of the external cart, so can't be booted without it.
	}
	setmirror(((regs[0xA]&2)>>1)^1);
}

static DECLFW(QTAiWrite) {
	regs[(A & 0x0F00) >> 8] = V;	// IRQ pretty the same as in other VRC mappers by Konami
	switch (A) {
	case 0xd600: IRQLatch &= 0xFF00; IRQLatch |= V; break;
	case 0xd700: IRQLatch &= 0x00FF; IRQLatch |= V << 8; break;
	case 0xd900: IRQCount = IRQLatch; IRQa = V & 2; K4IRQ = V & 1; X6502_IRQEnd(FCEU_IQEXT); break;
	case 0xd800: IRQa = K4IRQ; X6502_IRQEnd(FCEU_IQEXT); break;
	case 0xda00: qtaintramreg = regs[0xA] & 3;	break; // register shadow to share it with ppu
	}
	Sync();
}

static DECLFR(QTAiRead) {

//	uint8 res1 = conv_tbl[(regs[0xD] & 0x7F) >> 1][(regs[0xC] >> 5) & 3];
//	uint8 res2 = ((regs[0xD] & 1) << 7) | ((regs[0xC] & 0x1F) << 2) | (regs[0xB] & 3);

	uint8 tabl = conv_tbl[(regs[0xC] >> 5) & 3][(regs[0xD] & 0x7F) >> 4];
	uint8 res1 = 0x40 | (tabl & 0x3F) | ((regs[0xD] >> 1) & 7) | ((regs[0xB] & 4) << 5);
	uint8 res2 = ((regs[0xD] & 1) << 7) | ((regs[0xC] & 0x1F) << 2) | (regs[0xB] & 3);

	if (tabl & 0x40)
		res1 &= 0xFB;
	else if (tabl & 0x80)
		res1 |= 0x04;

	if (A == 0xDD00) {
		return res1;
	} else if (A == 0xDC00) {
#ifdef CAI_DEBUG
		FCEU_printf("%02x:%02x+%d -> %02x:%02x\n", regs[0xD], regs[0xC], regs[0xB], res1, res2);
#endif
		return res2;
	} else
		return 0;
}

static void VRC5IRQ(int a) {
	if (IRQa) {
		IRQCount += a;
		if (IRQCount & 0x10000) {
			X6502_IRQBegin(FCEU_IQEXT);
			IRQCount = IRQLatch;
		}
	}
}

static void QTAiPower(void) {
	SetReadHandler(0x6000, 0xFFFF, CartBR);
	SetWriteHandler(0x6000, 0x7FFF, CartBW);
	SetWriteHandler(0x8000, 0xFFFF, QTAiWrite);
	SetReadHandler(0xDC00, 0xDC00, QTAiRead);
	SetReadHandler(0xDD00, 0xDD00, QTAiRead);
	FCEU_CheatAddRAM(WRAMSIZE >> 10, 0x6000, WRAM);
	Sync();
}

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

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

void QTAi_Init(CartInfo *info) {
	QTAIHack = 1;

	info->Power = QTAiPower;
	info->Close = QTAiClose;
	GameStateRestore = StateRestore;

	MapIRQHook = VRC5IRQ;

	CHRRAM = (uint8*)FCEU_gmalloc(CHRSIZE);
	SetupCartCHRMapping(0x10, CHRRAM, CHRSIZE, 1);
	AddExState(CHRRAM, CHRSIZE, 0, "CRAM");

	WRAM = (uint8*)FCEU_gmalloc(WRAMSIZE);
	SetupCartPRGMapping(0x10, WRAM, WRAMSIZE, 1);
	AddExState(WRAM, WRAMSIZE, 0, "WRAM");

	if (info->battery) {
		info->SaveGame[0] = WRAM;
		// note, only extrnal cart's SRAM is battery backed, the the part on the main cartridge is just
		// an additional work ram. so we may save only half here, but I forgot what part is saved lol, will
		// find out later.
		info->SaveGameLen[0] = WRAMSIZE;
	}

	AddExState(&StateRegs, ~0, 0, 0);
}
