// license:BSD-3-Clause
// copyright-holders:Fabio Priuli
/***********************************************************************************************************


 NES/Famicom cartridge emulation for Sunsoft DCS PCBs


 Here we emulate the Sunsoft Dual Cassette System (DCS) PCB used by Nantettate!! Baseball + subslot


 ***********************************************************************************************************/


#include "emu.h"
#include "sunsoft_dcs.h"

#ifdef NES_PCB_DEBUG
#define VERBOSE 1
#else
#define VERBOSE 0
#endif

#define LOG_MMC(x) do { if (VERBOSE) logerror x; } while (0)


//-----------------------------------------------
//
//  Nantettate!! Baseball Cartslot implementation
//
//-----------------------------------------------

//-------------------------------------------------
//  sub-cart interface
//-------------------------------------------------

ntb_cart_interface::ntb_cart_interface(const machine_config &mconfig, device_t &device)
	: device_interface(device, "ntbcart")
	, m_rom(nullptr)
{
}

ntb_cart_interface::~ntb_cart_interface()
{
}

//-------------------------------------------------
//  sub-cart slot device
//-------------------------------------------------

DEFINE_DEVICE_TYPE(NES_NTB_SLOT, nes_ntb_slot_device, "nes_ntb_slot", "NES NTB Cartridge Slot")

nes_ntb_slot_device::nes_ntb_slot_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: device_t(mconfig, NES_NTB_SLOT, tag, owner, clock)
	, device_cartrom_image_interface(mconfig, *this)
	, device_single_card_slot_interface<ntb_cart_interface>(mconfig, *this)
	, m_cart(nullptr)
{
}

nes_ntb_slot_device::~nes_ntb_slot_device()
{
}


void nes_ntb_slot_device::device_start()
{
	m_cart = get_card_device();
}

uint8_t nes_ntb_slot_device::read(offs_t offset)
{
	if (m_cart)
		return m_cart->read(offset);
	else
		return 0xff;
}


image_init_result nes_ntb_slot_device::call_load()
{
	if (m_cart)
	{
		uint8_t *ROM = m_cart->get_cart_base();

		if (!ROM)
			return image_init_result::FAIL;

		if (!loaded_through_softlist())
		{
			if (length() != 0x4000)
				return image_init_result::FAIL;

			fread(&ROM, 0x4000);
		}
		else
		{
			if (get_software_region_length("rom") != 0x4000)
				return image_init_result::FAIL;

			memcpy(ROM, get_software_region("rom"), 0x4000);
		}
	}

	return image_init_result::PASS;
}


std::string nes_ntb_slot_device::get_default_card_software(get_default_card_software_hook &hook) const
{
	return software_get_default_slot("ntbrom");
}

//-----------------------------------------------
//
//  Nantettate!! Baseball Minicart implementation
//
//-----------------------------------------------

ROM_START( ntb_rom )
	ROM_REGION(0x4000, "ntbrom", ROMREGION_ERASEFF)
ROM_END

DEFINE_DEVICE_TYPE(NES_NTB_ROM, nes_ntb_rom_device, "nes_ntbrom", "NES NTB ROM")

nes_ntb_rom_device::nes_ntb_rom_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: device_t(mconfig, NES_NTB_ROM, tag, owner, clock)
	, ntb_cart_interface(mconfig, *this)
{
}

void nes_ntb_rom_device::device_start()
{
	m_rom = (uint8_t*)memregion("ntbrom")->base();
}

const tiny_rom_entry *nes_ntb_rom_device::device_rom_region() const
{
	return ROM_NAME( ntb_rom );
}

uint8_t *nes_ntb_rom_device::get_cart_base()
{
	return m_rom;
}



//------------------------------------------------
//
//  Nantettate!! Baseball base cart implementation
//  a.k.a. Sunsoft Dual Cassette System
//  (variant of Sunsoft-4 PCB)
//
//------------------------------------------------

DEFINE_DEVICE_TYPE(NES_SUNSOFT_DCS, nes_sunsoft_dcs_device, "nes_dcs", "NES Cart Sunsoft DCS PCB")


nes_sunsoft_dcs_device::nes_sunsoft_dcs_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: nes_sunsoft_4_device(mconfig, NES_SUNSOFT_DCS, tag, owner, clock), m_timer_on(0), m_exrom_enable(0)
	, m_subslot(*this, "ntb_slot"), ntb_enable_timer(nullptr)
{
}


void nes_sunsoft_dcs_device::device_start()
{
	nes_sunsoft_4_device::device_start();

	ntb_enable_timer = timer_alloc(FUNC(nes_sunsoft_dcs_device::protect_tick), this);
	ntb_enable_timer->reset();
	timer_freq = clocks_to_attotime(107520);

	save_item(NAME(m_exrom_enable));
	save_item(NAME(m_timer_on));
}

void nes_sunsoft_dcs_device::pcb_reset()
{
	nes_sunsoft_4_device::pcb_reset();
	m_exrom_enable = 0;
	m_timer_on = 0;
}


/*-------------------------------------------------
 mapper specific handlers
 -------------------------------------------------*/

/*-------------------------------------------------

 Sunsoft-DCS board emulation

 Games: Nantettatte!! Baseball

 This board feature a subslot to load minicart containing
 updated data?

 iNES: mapper 68

 -------------------------------------------------*/

void nes_sunsoft_dcs_device::write_h(offs_t offset, uint8_t data)
{
	LOG_MMC(("Sunsoft DCS write_h, offset %04x, data: %02x\n", offset, data));

	switch (offset & 0x7000)
	{
		case 0x7000:
			prg16_89ab(data & 0x07);
			m_exrom_enable = !BIT(data, 3);
			m_wram_enable = BIT(data, 4);
			break;
		default:
			sun4_write(offset, data);
			break;
	}
}

uint8_t nes_sunsoft_dcs_device::read_h(offs_t offset)
{
	LOG_MMC(("Sunsoft DCS read_h, offset: %04x\n", offset));

	if (m_exrom_enable && m_subslot->m_cart && offset < 0x4000)
	{
		if (m_timer_on)
			return m_subslot->m_cart->read(offset);
		else
			return get_open_bus();   // after the timer is off, this returns open bus...
	}
	else
		return hi_access_rom(offset);
}

void nes_sunsoft_dcs_device::write_m(offs_t offset, uint8_t data)
{
	LOG_MMC(("Sunsoft DCS write_m, offset: %04x, data: %02x\n", offset, data));

	if (!m_battery.empty() && m_wram_enable)
		m_battery[offset & (m_battery.size() - 1)] = data;
	if (!m_prgram.empty() && m_wram_enable)
		m_prgram[offset & (m_prgram.size() - 1)] = data;
	if (!m_wram_enable && !m_timer_on)
	{
		m_timer_on = 1;
		// start NTB-ROM timer
		ntb_enable_timer->adjust(timer_freq);
	}
}

uint8_t nes_sunsoft_dcs_device::read_m(offs_t offset)
{
	LOG_MMC(("Sunsoft DCS read_m, offset: %04x\n", offset));

	if (!m_battery.empty() && m_wram_enable)
		return m_battery[offset & (m_battery.size() - 1)];
	if (!m_prgram.empty() && m_wram_enable)
		return m_prgram[offset & (m_prgram.size() - 1)];

	return get_open_bus();
}


static void ntb_cart(device_slot_interface &device)
{
	device.option_add_internal("ntbrom", NES_NTB_ROM);
}




//-------------------------------------------------
//  device_add_mconfig - add device configuration
//-------------------------------------------------

void nes_sunsoft_dcs_device::device_add_mconfig(machine_config &config)
{
	NES_NTB_SLOT(config, m_subslot, ntb_cart);
}


//-------------------------------------------------
//  protect_tick - handler NTB-ROM timer elapsing
//-------------------------------------------------

TIMER_CALLBACK_MEMBER(nes_sunsoft_dcs_device::protect_tick)
{
	m_timer_on = 0;
	ntb_enable_timer->reset();
}
