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

PC Engine CD HW notes:

TODO:
- Dragon Ball Z: ADPCM dies after the first upload;
- Dragon Slayer - The Legend of Heroes: black screen;
- Mirai Shonen Conan: dies at new game selection;
- Snatcher: black screen after Konami logo, tries set up CD-DA
            while transferring data?
- Steam Heart's: needs transfer ready irq to get past the
                 gameplay hang, don't know exactly when it should fire
- Steam Heart's: bad ADPCM irq, dialogue is cutted due of it;

=============================================================

CD Interface Register 0x00 - CDC status
x--- ---- busy signal
-x-- ---- request signal
---x ---- cd signal
---- x--- i/o signal

CD Interface Register 0x03 - BRAM lock / CD status
-x-- ---- acknowledge signal
--x- ---- done signal
---x ---- bram signal
---- x--- ADPCM 2
---- -x-- ADPCM 1
---- --x- CDDA left/right speaker select

CD Interface Register 0x05 - CD-DA Volume low 8-bit port

CD Interface Register 0x06 - CD-DA Volume high 8-bit port

CD Interface Register 0x07 - BRAM unlock / CD status
x--- ---- Enables BRAM

CD Interface Register 0x0c - ADPCM status
x--- ---- ADPCM is reading data
---- x--- ADPCM playback (0) stopped (1) currently playing
---- -x-- pending ADPCM data write
---- ---x ADPCM playback (1) stopped (0) currently playing

CD Interface Register 0x0d - ADPCM address control
x--- ---- ADPCM reset
-x-- ---- ADPCM play
--x- ---- ADPCM repeat
---x ---- ADPCM set length
---- x--- ADPCM set read address
---- --xx ADPCM set write address
(note: some games reads bit 5 and wants it to be low otherwise they hangs, surely NOT an ADPCM repeat flag read because it doesn't make sense)

CD Interface Register 0x0e - ADPCM playback rate

CD Interface Register 0x0f - ADPCM fade in/out register
---- xxxx command setting:
0x00 ADPCM/CD-DA Fade-in
0x01 CD-DA fade-in
0x08 CD-DA fade-out (short) ADPCM fade-in
0x09 CD-DA fade-out (long)
0x0a ADPCM fade-out (long)
0x0c CD-DA fade-out (short) ADPCM fade-in
0x0d CD-DA fade-out (short)
0x0e ADPCM fade-out (short)

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

#include "emu.h"
#include "cpu/h6280/h6280.h"
#include "includes/pce.h"


/* joystick related data*/

#define JOY_CLOCK   0x01
#define JOY_RESET   0x02



DRIVER_INIT_MEMBER(pce_state,mess_pce)
{
	m_io_port_options = PCE_JOY_SIG | CONST_SIG;
}

DRIVER_INIT_MEMBER(pce_state,tg16)
{
	m_io_port_options = TG_16_JOY_SIG | CONST_SIG;
}

DRIVER_INIT_MEMBER(pce_state,sgx)
{
	m_io_port_options = PCE_JOY_SIG | CONST_SIG;
}

MACHINE_START_MEMBER(pce_state,pce)
{
	if (m_cd)
		m_cd->late_setup();

	// saving is only partially supported: it should be fine with cart games
	// OTOH CD states are saved but not correctly restored!
	save_item(NAME(m_io_port_options));
	save_item(NAME(m_acard));
	save_item(NAME(m_joystick_port_select));
	save_item(NAME(m_joystick_data_select));
	save_item(NAME(m_joy_6b_packet));
}

MACHINE_RESET_MEMBER(pce_state,mess_pce)
{
	for (int joy_i = 0; joy_i < 5; joy_i++)
		m_joy_6b_packet[joy_i] = 0;

	/* Note: Arcade Card BIOS contents are the same as System 3, only internal HW differs.
	   We use a category to select between modes (some games can be run in either S-CD or A-CD modes) */
	m_acard = ioport("A_CARD")->read() & 1;

	if (m_cartslot->get_type() == PCE_CDSYS3J)
	{
		m_sys3_card = 1;
		m_maincpu->space(AS_PROGRAM).install_readwrite_handler(0x080000, 0x087fff, read8_delegate(FUNC(pce_state::pce_cd_acard_wram_r),this),write8_delegate(FUNC(pce_state::pce_cd_acard_wram_w),this));
	}

	if (m_cartslot->get_type() == PCE_CDSYS3U)
	{
		m_sys3_card = 3;
		m_maincpu->space(AS_PROGRAM).install_readwrite_handler(0x080000, 0x087fff, read8_delegate(FUNC(pce_state::pce_cd_acard_wram_r),this),write8_delegate(FUNC(pce_state::pce_cd_acard_wram_w),this));
	}
}

/* todo: how many input ports does the PCE have? */
WRITE8_MEMBER(pce_state::mess_pce_joystick_w)
{
	int joy_i;
	UINT8 joy_type = ioport("JOY_TYPE")->read();

	m_maincpu->io_set_buffer(data);

	/* bump counter on a low-to-high transition of bit 1 */
	if ((!m_joystick_data_select) && (data & JOY_CLOCK))
	{
		m_joystick_port_select = (m_joystick_port_select + 1) & 0x07;
	}

	/* do we want buttons or direction? */
	m_joystick_data_select = data & JOY_CLOCK;

	/* clear counter if bit 2 is set */
	if (data & JOY_RESET)
	{
		m_joystick_port_select = 0;

		for (joy_i = 0; joy_i < 5; joy_i++)
		{
			if (((joy_type >> (joy_i*2)) & 3) == 2)
				m_joy_6b_packet[joy_i] ^= 1;
		}
	}
}

READ8_MEMBER(pce_state::mess_pce_joystick_r)
{
	static const char *const joyname[4][5] = {
		{ "JOY_P1", "JOY_P2", "JOY_P3", "JOY_P4", "JOY_P5" },
		{ },
		{ "JOY6B_P1", "JOY6B_P2", "JOY6B_P3", "JOY6B_P4", "JOY6B_P5" },
		{ }
	};
	UINT8 joy_type = ioport("JOY_TYPE")->read();
	UINT8 ret, data;

	if (m_joystick_port_select <= 4)
	{
		switch ((joy_type >> (m_joystick_port_select*2)) & 3)
		{
			case 0: //2-buttons pad
				data = ioport(joyname[0][m_joystick_port_select])->read();
				break;
			case 2: //6-buttons pad
				/*
				Two packets:
				1st packet: directions + I, II, Run, Select
				2nd packet: 6 buttons "header" (high 4 bits active low) + III, IV, V, VI
				Note that six buttons pad just doesn't work with (almost?) every single 2-button-only games, it's really just an after-thought and it is like this
				on real HW.
				*/
				data = ioport(joyname[2][m_joystick_port_select])->read() >> (m_joy_6b_packet[m_joystick_port_select]*8);
				break;
			default:
				data = 0xff;
				break;
		}
	}
	else
		data = 0xff;


	if (m_joystick_data_select)
		data >>= 4;

	ret = (data & 0x0f) | m_io_port_options;
#ifdef UNIFIED_PCE
	ret &= ~0x40;
#endif

	return (ret);
}


WRITE8_MEMBER(pce_state::pce_cd_intf_w)
{
	m_cd->update();

	if (offset & 0x200 && m_sys3_card && m_acard) // route Arcade Card handling ports
		return m_cd->acard_w(space, offset, data);

	m_cd->intf_w(space, offset, data);

	m_cd->update();
}

READ8_MEMBER(pce_state::pce_cd_intf_r)
{
	m_cd->update();

	if (offset & 0x200 && m_sys3_card && m_acard) // route Arcade Card handling ports
		return m_cd->acard_r(space, offset);

	if ((offset & 0xc0) == 0xc0 && m_sys3_card) //System 3 Card header handling
	{
		switch (offset & 0xcf)
		{
			case 0xc1: return 0xaa;
			case 0xc2: return 0x55;
			case 0xc3: return 0x00;
			case 0xc5: return (m_sys3_card & 2) ? 0x55 : 0xaa;
			case 0xc6: return (m_sys3_card & 2) ? 0xaa : 0x55;
			case 0xc7: return 0x03;
		}
	}

	return m_cd->intf_r(space, offset);
}


READ8_MEMBER(pce_state::pce_cd_acard_wram_r)
{
	return pce_cd_intf_r(space, 0x200 | (offset & 0x6000) >> 9);
}

WRITE8_MEMBER(pce_state::pce_cd_acard_wram_w)
{
	pce_cd_intf_w(space, 0x200 | (offset & 0x6000) >> 9, data);
}
