/* FCEUmm - NES/Famicom Emulator
 *
 * Copyright notice for this file:
 *  Copyright (C) 2012 CaH4e3
 *  Copyright (C) 2019 Libretro Team
 *  Copyright (C) 2020
 *
 * 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
 *
 * TXC/Micro Genius simplified mapper
 * updated 06-2019 http://wiki.nesdev.com/w/index.php/INES_Mapper_036
 *
 * Known games:
 * - Strike Wolf (Asia) (Unl)
 * - Policeman (Gluk Video) (unl)
 * - F-15 City War (Spain) (Gluk Video) (Unl)
 *
 * TXC mappers, originally much complex banksitching
 *
 * 01-22111-000 (05-00002-010) (132, 22211) - MGC-001 Qi Wang
 * 01-22110-000 (52S         )              - MGC-002 2-in-1 Gun
 * 01-22111-100 (02-00002-010) (173       ) - MGC-008 Mahjong Block
 *                             (079       ) - MGC-012 Poke Block
 * 01-22110-200 (05-00002-010) (036       ) - MGC-014 Strike Wolf
 * 01-22000-400 (05-00002-010) (036       ) - MGC-015 Policeman
 * 01-22017-000 (05-PT017-080) (189       ) - MGC-017 Thunder Warrior
 * 01-11160-000 (04-02310-000) (   , 11160) - MGC-023 6-in-1
 * 01-22270-000 (05-00002-010) (132, 22211) - MGC-xxx Creatom
 * 01-22200-400 (------------) (079       ) - ET.03   F-15 City War
 *                             (172       ) -         1991 Du Ma Racing
 *
 */

/* added 2020-2-16
 * Updated based on latest source
 * Mappers 36, 132, 173
 * Mappers 136, 147, 172
 */

#include "mapinc.h"

typedef struct {
	uint8 mask;
	uint8 isJV001;
	uint8 accumulator;
	uint8 inverter;
	uint8 staging;
	uint8 output;
	uint8 increase;
	uint8 Y;
	uint8 invert;
} TXC;

static TXC txc;

static void Dummyfunc(void) { }
static void (*WSync)(void) = Dummyfunc;

static SFORMAT StateRegs[] =
{
	{ &txc.accumulator, 1, "ACC0" },
	{ &txc.inverter,    1, "INVR" },
	{ &txc.staging,     1, "STG0" },
	{ &txc.output,      1, "OUT0" },
	{ &txc.increase,    1, "INC0" },
	{ &txc.Y,        1, "YFLG" },
	{ &txc.invert,      1, "INVT" },
	{ 0 }
};

static uint8 TXC_CMDRead(void) {
	uint8 ret = ((txc.accumulator & txc.mask) | ((txc.inverter ^ txc.invert) & ~txc.mask));
	txc.Y = !txc.invert || ((ret & 0x10) != 0);
	WSync();
	return ret;
}

static DECLFW(TXC_CMDWrite) {
	if (A & 0x8000) {
	  if (txc.isJV001)
		 txc.output = (txc.accumulator & 0x0F) | (txc.inverter & 0xF0);
	  else
		 txc.output = (txc.accumulator & 0x0F) | ((txc.inverter << 1) & 0x10);
	} else {
	  switch (A & 0x103) {
	  case 0x100:
		 if (txc.increase)
			txc.accumulator++;
		 else
			txc.accumulator = ((txc.accumulator & ~txc.mask) | ((txc.staging ^ txc.invert) & txc.mask));
		 break;
	  case 0x101:
		 txc.invert = (V & 0x01) ? 0xFF : 0x00;
		 break;
	  case 0x102:
		 txc.staging = V & txc.mask;
		 txc.inverter = V & ~txc.mask;
		 break;
	  case 0x103:
		 txc.increase = ((V & 0x01) != 0);
		 break;
	  }
	}
	txc.Y = !txc.invert || ((V & 0x10) != 0);
	WSync();
}

static void TXCRegReset(void) {
	txc.output      = 0;
	txc.accumulator = 0;
	txc.inverter    = 0;
	txc.staging     = 0;
	txc.increase    = 0;
	txc.Y       = 0;
	txc.mask        = txc.isJV001 ? 0x0F : 0x07;
	txc.invert      = txc.isJV001 ? 0xFF : 0x00;

	WSync();
}

static void GenTXCPower(void) {
	TXCRegReset();
}

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

static void GenTXC_Init(CartInfo *info, void (*proc)(void), uint32 jv001) {
	txc.isJV001 = jv001;
	WSync   = proc;
	GameStateRestore = StateRestore;
	AddExState(StateRegs, ~0, 0, 0);
}

static int CheckHash(CartInfo *info) {
	int x = 0;
	uint64 partialmd5 = 0;

	/* These carts do not work with new mapper implementation.
	* This is a hack to use previous mapper implementation for such carts. */
	for (x = 0; x < 8; x++)
	  partialmd5 |= (uint64)info->MD5[15 - x] << (x * 8);
	switch (partialmd5) {
	case 0x2dd8f958850f21f4LL: /* Jin Gwok Sei Chuen Saang (Ch) [U][!] */
	  FCEU_printf(" WARNING: Using alternate mapper implementation.\n");
	  UNL22211_Init(info);
	  return 1;
	}
	return 0;
}

/* --------------- Mapper 36 --------------- */

static uint8 creg = 0;

static void M36Sync(void) {
	setprg32(0x8000, txc.output & 0x03);
	setchr8(creg & 0x0F);
}

static DECLFW(M36Write) {
	if ((A & 0xF200) == 0x4200) creg = V;
	TXC_CMDWrite(A, (V >> 4) & 0x03);
}

static DECLFR(M36Read) {
	uint8 ret = X.DB;
	if ((A & 0x103) == 0x100)
	  ret = (X.DB & 0xCF) | ((TXC_CMDRead() << 4) & 0x30);
	return ret;
}

static void M36Power(void) {
	creg = 0;
	GenTXCPower();
	SetReadHandler(0x8000, 0xFFFF, CartBR);
	SetReadHandler(0x4100, 0x5FFF, M36Read);
	SetWriteHandler(0x4100, 0xFFFF, M36Write);
}

void Mapper36_Init(CartInfo *info) {
	GenTXC_Init(info, M36Sync, 0);
	info->Power = M36Power;
	AddExState(&creg, 1, 0, "CREG");
}

/* --------------- Mapper 132 --------------- */

static void M132Sync(void) {
	setprg32(0x8000, (txc.output >> 2) & 0x01);
	setchr8(txc.output & 0x03);
}

static DECLFW(M132Write) {
	TXC_CMDWrite(A, V & 0x0F);
}

static DECLFR(M132Read) {
	uint8 ret = X.DB;
	if ((A & 0x103) == 0x100)
	  ret = ((X.DB & 0xF0) | (TXC_CMDRead() & 0x0F));
	return ret;
}

static void M132Power(void) {
	GenTXCPower();
	SetReadHandler(0x8000, 0xFFFF, CartBR);
	SetReadHandler(0x4100, 0x5FFF, M132Read);
	SetWriteHandler(0x4100, 0xFFFF, M132Write);
}

void Mapper132_Init(CartInfo *info) {
	if (CheckHash(info) != 0) return;
	GenTXC_Init(info, M132Sync, 0);
	info->Power = M132Power;
}

/* --------------- Mapper 173 --------------- */

static void M173Sync(void) {
	setprg32(0x8000, 0);
	if (CHRsize[0] > 0x2000)
	  setchr8(((txc.output & 0x01) | (txc.Y ? 0x02 : 0x00) | ((txc.output & 2) << 0x01)));
	else
	  setchr8(0);
}

void Mapper173_Init(CartInfo *info) {
	GenTXC_Init(info, M173Sync, 0);
	info->Power = M132Power;
}

/* ---------------- Joy/Van ----------------- */

/* --------------- Mapper 136 --------------- */

static void M136Sync(void) {
	setprg32(0x8000, (txc.output >> 4) & 0x01);
	setchr8(txc.output & 0x07);
}

static DECLFW(M136Write) {
	TXC_CMDWrite(A, V & 0x3F);
}

static DECLFR(M136Read) {
	uint8 ret = X.DB;
	if ((A & 0x103) == 0x100)
	  ret = ((X.DB & 0xC0) | (TXC_CMDRead() & 0x3F));
	return ret;
}

static void M136Power(void) {
	GenTXCPower();
	SetReadHandler(0x8000, 0xFFFF, CartBR);
	SetReadHandler(0x4100, 0x5FFF, M136Read);
	SetWriteHandler(0x4100, 0xFFFF, M136Write);
}

void Mapper136_Init(CartInfo *info) {
	GenTXC_Init(info, M136Sync, 1);
	info->Power = M136Power;
}

/* --------------- Mapper 147 --------------- */

static void M147Sync(void) {
	setprg32(0x8000, ((txc.output >> 4) & 0x02) | (txc.output & 0x01));
	setchr8((txc.output >> 1) & 0x0F);
}

static DECLFW(M147Write) {
	TXC_CMDWrite(A, ((V >> 2) & 0x3F) | ((V << 6) & 0xC0));
}

static DECLFR(M147Read) {
	uint8 ret = X.DB;
	if ((A & 0x103) == 0x100) {
	  uint8 value = TXC_CMDRead();
	  ret = ((value << 2) & 0xFC) | ((value >> 6) & 0x03);
	}
	return ret;
}

static void M147Power(void) {
	GenTXCPower();
	SetReadHandler(0x8000, 0xFFFF, CartBR);
	SetReadHandler(0x4100, 0x5FFF, M147Read);
	SetWriteHandler(0x4100, 0xFFFF, M147Write);
}

void Mapper147_Init(CartInfo *info) {
	GenTXC_Init(info, M147Sync, 1);
	info->Power = M147Power;
}

/* --------------- Mapper 172 --------------- */

static void M172Sync(void) {
	setprg32(0x8000, 0);
	setchr8(txc.output);
	setmirror(txc.invert ? 1 : 0);
}

static uint8 GetValue(uint8 value) {
	return (((value << 5) & 0x20) | ((value << 3) & 0x10) | ((value << 1) & 0x08) |
		 ((value >> 1) & 0x04) | ((value >> 3) & 0x02) | ((value >> 5) & 0x01));
}

static DECLFW(M172Write) {
	TXC_CMDWrite(A, GetValue(V));
}

static DECLFR(M172Read) {
	uint8 ret = X.DB;
	if ((A & 0x103) == 0x100)
	  ret = (X.DB & 0xC0) | GetValue(TXC_CMDRead());
	return ret;
}

static void M172Power(void) {
	GenTXCPower();
	SetReadHandler(0x8000, 0xFFFF, CartBR);
	SetReadHandler(0x4100, 0x5FFF, M172Read);
	SetWriteHandler(0x4100, 0xFFFF, M172Write);
}

void Mapper172_Init(CartInfo *info) {
	GenTXC_Init(info, M172Sync, 1);
	info->Power = M172Power;
}

/* === LEGACY MAPPER IMPLEMENTATION === */

static uint8 reg[4], cmd, is172, is173;

static SFORMAT UNL22211StateRegs[] =
{
	{ reg, 4, "REGS" },
	{ &cmd, 1, "CMD" },
	{ 0 }
};

static void UNL22211Sync(void) {
	setprg32(0x8000, (reg[2] >> 2) & 1);
	if (is172)
	  setchr8((((cmd ^ reg[2]) >> 3) & 2) | (((cmd ^ reg[2]) >> 5) & 1));	/* 1991 DU MA Racing probably CHR bank sequence is WRONG, so it is possible to
														  * rearrange CHR banks for normal UNIF board and mapper 172 is unneccessary */
	else
	  setchr8(reg[2] & 3);
}

static DECLFW(UNL22211WriteLo) {
/*	FCEU_printf("bs %04x %02x\n",A,V); */
	reg[A & 3] = V;
}

static DECLFW(UNL22211WriteHi) {
/*	FCEU_printf("bs %04x %02x\n",A,V); */
	cmd = V;
	UNL22211Sync();
}

static DECLFR(UNL22211ReadLo) {
	return (reg[1] ^ reg[2]) | (is173 ? 0x01 : 0x40);
#if 0
	if(reg[3])
	  return reg[2];
	else
	  return X.DB;
#endif
}

static void UNL22211Power(void) {
	UNL22211Sync();
	SetReadHandler(0x8000, 0xFFFF, CartBR);
	SetReadHandler(0x4100, 0x4100, UNL22211ReadLo);
	SetWriteHandler(0x4100, 0x4103, UNL22211WriteLo);
	SetWriteHandler(0x8000, 0xFFFF, UNL22211WriteHi);
}

static void UNL22211StateRestore(int version) {
	UNL22211Sync();
}

void UNL22211_Init(CartInfo *info) {
	is172 = 0;
	is173 = 0;
	info->Power = UNL22211Power;
	GameStateRestore = UNL22211StateRestore;
	AddExState(&UNL22211StateRegs, ~0, 0, 0);
}
