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

    Driver for Casio PV-1000

***************************************************************************/
#define ADDRESS_MAP_MODERN

#include "emu.h"
#include "cpu/z80/z80.h"
#include "imagedev/cartslot.h"


DECLARE_LEGACY_SOUND_DEVICE(PV1000,pv1000_sound);
DEFINE_LEGACY_SOUND_DEVICE(PV1000,pv1000_sound);


class pv1000_state : public driver_device
{
public:
	pv1000_state(const machine_config &mconfig, device_type type, const char *tag)
		: driver_device(mconfig, type, tag) { }

	DECLARE_WRITE8_MEMBER(pv1000_io_w);
	DECLARE_READ8_MEMBER(pv1000_io_r);
	DECLARE_WRITE8_MEMBER(pv1000_gfxram_w);
	UINT8	m_io_regs[8];
	UINT8	m_fd_data;
	struct
	{
		UINT32	count;
		UINT16	period;
		UINT8	val;
	}		m_voice[4];

	sound_stream	*m_sh_channel;
	emu_timer		*m_irq_on_timer;
	emu_timer		*m_irq_off_timer;

	device_t *m_maincpu;
	screen_device *m_screen;
	UINT8 *m_p_videoram;
};


static ADDRESS_MAP_START( pv1000, AS_PROGRAM, 8, pv1000_state )
	AM_RANGE( 0x0000, 0x3fff ) AM_MIRROR( 0x4000 ) AM_ROM AM_REGION( "cart", 0 )
	AM_RANGE( 0xb800, 0xbbff ) AM_RAM AM_BASE( m_p_videoram )
	AM_RANGE( 0xbc00, 0xbfff ) AM_RAM_WRITE( pv1000_gfxram_w ) AM_REGION( "gfxram", 0 )
ADDRESS_MAP_END


static ADDRESS_MAP_START( pv1000_io, AS_IO, 8, pv1000_state )
	ADDRESS_MAP_GLOBAL_MASK( 0xff )
	AM_RANGE( 0xf8, 0xff ) AM_READWRITE( pv1000_io_r, pv1000_io_w )
ADDRESS_MAP_END


WRITE8_MEMBER( pv1000_state::pv1000_gfxram_w )
{
	UINT8 *gfxram = machine().region( "gfxram" )->base();

	gfxram[ offset ] = data;
	gfx_element_mark_dirty(machine().gfx[1], offset/32);
}


WRITE8_MEMBER( pv1000_state::pv1000_io_w )
{
	switch ( offset )
	{
	case 0x00:
	case 0x01:
	case 0x02:
		//logerror("pv1000_io_w offset=%02x, data=%02x (%03d)\n", offset, data , data);
		m_voice[offset].period = data;
	break;

	case 0x03:
		//currently unknown use
	break;

	case 0x05:
		m_fd_data = 1;
		break;
	}

	m_io_regs[offset] = data;
}


READ8_MEMBER( pv1000_state::pv1000_io_r )
{
	UINT8 data = m_io_regs[offset];

//  logerror("pv1000_io_r offset=%02x\n", offset );

	switch ( offset )
	{
	case 0x04:
		/* Bit 1 = 1 => Data is available in port FD */
		/* Bit 0 = 1 => Buffer at port FD is empty */
		data = ( m_screen->vpos() >= 212 && m_screen->vpos() <= 220 ) ? 1 : 0;
		data |= m_fd_data ? 2 : 0;
		break;
	case 0x05:
		data = 0;
		if ( m_io_regs[5] & 0x08 )
		{
			data = input_port_read( machine(), "IN3" );
		}
		if ( m_io_regs[5] & 0x04 )
		{
			data = input_port_read( machine(), "IN2" );
		}
		if ( m_io_regs[5] & 0x02 )
		{
			data = input_port_read( machine(), "IN1" );
		}
		if ( m_io_regs[5] & 0x01 )
		{
			data = input_port_read( machine(), "IN0" );
		}
		m_fd_data = 0;
		break;
	}

	return data;
}


static INPUT_PORTS_START( pv1000 )
	PORT_START( "IN0" )
	PORT_BIT( 0x01, IP_ACTIVE_HIGH, IPT_SELECT ) PORT_PLAYER(1)
	PORT_BIT( 0x02, IP_ACTIVE_HIGH, IPT_START ) PORT_PLAYER(1)
	PORT_BIT( 0x04, IP_ACTIVE_HIGH, IPT_SELECT ) PORT_PLAYER(2)
	PORT_BIT( 0x08, IP_ACTIVE_HIGH, IPT_START ) PORT_PLAYER(2)

	PORT_START( "IN1" )
	PORT_BIT( 0x01, IP_ACTIVE_HIGH, IPT_JOYSTICK_DOWN ) PORT_PLAYER(1) PORT_8WAY
	PORT_BIT( 0x02, IP_ACTIVE_HIGH, IPT_JOYSTICK_RIGHT ) PORT_PLAYER(1) PORT_8WAY
	PORT_BIT( 0x04, IP_ACTIVE_HIGH, IPT_JOYSTICK_DOWN ) PORT_PLAYER(2) PORT_8WAY
	PORT_BIT( 0x08, IP_ACTIVE_HIGH, IPT_JOYSTICK_RIGHT ) PORT_PLAYER(2) PORT_8WAY

	PORT_START( "IN2" )
	PORT_BIT( 0x01, IP_ACTIVE_HIGH, IPT_JOYSTICK_LEFT ) PORT_PLAYER(1) PORT_8WAY
	PORT_BIT( 0x02, IP_ACTIVE_HIGH, IPT_JOYSTICK_UP ) PORT_PLAYER(1) PORT_8WAY
	PORT_BIT( 0x04, IP_ACTIVE_HIGH, IPT_JOYSTICK_LEFT ) PORT_PLAYER(2) PORT_8WAY
	PORT_BIT( 0x08, IP_ACTIVE_HIGH, IPT_JOYSTICK_UP ) PORT_PLAYER(2) PORT_8WAY

	PORT_START( "IN3" )
	PORT_BIT( 0x01, IP_ACTIVE_HIGH, IPT_BUTTON1 ) PORT_PLAYER(1)
	PORT_BIT( 0x02, IP_ACTIVE_HIGH, IPT_BUTTON2 ) PORT_PLAYER(1)
	PORT_BIT( 0x04, IP_ACTIVE_HIGH, IPT_BUTTON1 ) PORT_PLAYER(2)
	PORT_BIT( 0x08, IP_ACTIVE_HIGH, IPT_BUTTON2 ) PORT_PLAYER(2)
INPUT_PORTS_END


static PALETTE_INIT( pv1000 )
{
	palette_set_color_rgb( machine,  0,   0,   0,   0 );
	palette_set_color_rgb( machine,  1,   0,   0, 255 );
	palette_set_color_rgb( machine,  2,   0, 255,   0 );
	palette_set_color_rgb( machine,  3,   0, 255, 255 );
	palette_set_color_rgb( machine,  4, 255,   0,   0 );
	palette_set_color_rgb( machine,  5, 255,   0, 255 );
	palette_set_color_rgb( machine,  6, 255, 255,   0 );
	palette_set_color_rgb( machine,  7, 255, 255, 255 );
}


static DEVICE_IMAGE_LOAD( pv1000_cart )
{
	UINT8 *cart = image.device().machine().region("cart")->base();
	UINT32 size;

	if (image.software_entry() == NULL)
		size = image.length();
	else
		size = image.get_software_region_length("rom");


	if (size != 0x2000 && size != 0x4000)
	{
		image.seterror(IMAGE_ERROR_UNSPECIFIED, "Unsupported cartridge size");
		return IMAGE_INIT_FAIL;
	}

	if (image.software_entry() == NULL)
	{
		if (image.fread( cart, size) != size)
		{
			image.seterror(IMAGE_ERROR_UNSPECIFIED, "Unable to fully read from file");
			return IMAGE_INIT_FAIL;
		}
	}
	else
		memcpy(cart, image.get_software_region("rom"), size);


	/* Mirror 8KB rom */
	if (size == 0x2000)
		memcpy(cart + 0x2000, cart, 0x2000);

	return IMAGE_INIT_PASS;
}


static SCREEN_UPDATE( pv1000 )
{
	pv1000_state *state = screen->machine().driver_data<pv1000_state>();
	int x, y;

	for ( y = 0; y < 24; y++ )
	{
		for ( x = 0; x < 32; x++ )
		{
			UINT16 tile = state->m_p_videoram[ y * 32 + x ];

			if ( tile < 0xe0 )
			{
				tile += ( state->m_io_regs[7] * 8 );
				drawgfx_opaque( bitmap, cliprect, screen->machine().gfx[0], tile, 0, 0, 0, x*8, y*8 );
			}
			else
			{
				tile -= 0xe0;
				drawgfx_opaque( bitmap, cliprect, screen->machine().gfx[1], tile, 0, 0, 0, x*8, y*8 );
			}
		}
	}

	return 0;
}


/*
 plgDavid's audio implementation/analysis notes:

 Sound appears to be 3 50/50 pulse voices made by cutting the main clock by 1024,
 then by the value of the 6bit period registers.
 This creates a surprisingly accurate pitch range.

 Note: the register periods are inverted.
 */

static STREAM_UPDATE( pv1000_sound_update )
{
	pv1000_state *state = device->machine().driver_data<pv1000_state>();
	stream_sample_t *buffer = outputs[0];

	while ( samples > 0 )
	{

		*buffer=0;

		for (size_t i=0;i<3;i++)
		{
			UINT32 per = (0x3F-(state->m_voice[i].period & 0x3f));

			if( per != 0)//OFF!
				*buffer += state->m_voice[i].val * 8192;

			state->m_voice[i].count++;

			if (state->m_voice[i].count >= per)
			{
			   state->m_voice[i].count = 0;
			   state->m_voice[i].val = !state->m_voice[i].val;
			}
		}

		buffer++;
		samples--;
	}

}


static DEVICE_START( pv1000_sound )
{
	pv1000_state *state = device->machine().driver_data<pv1000_state>();
	state->m_sh_channel = device->machine().sound().stream_alloc(*device, 0, 1, device->clock()/1024, 0, pv1000_sound_update );
}


DEVICE_GET_INFO( pv1000_sound )
{
	switch (state)
	{
		/* --- the following bits of info are returned as pointers to data or functions --- */
		case DEVINFO_FCT_START:							info->start = DEVICE_START_NAME(pv1000_sound);	break;

		/* --- the following bits of info are returned as NULL-terminated strings --- */
		case DEVINFO_STR_NAME:							strcpy(info->s, "NEC D65010G031");				break;
		case DEVINFO_STR_SOURCE_FILE:					strcpy(info->s, __FILE__);						break;
	}
}


/* Interrupt is triggering 16 times during vblank. */
/* we have chosen to trigger on scanlines 195, 199, 203, 207, 211, 215, 219, 223, 227, 231, 235, 239, 243, 247, 251, 255 */
static TIMER_CALLBACK( d65010_irq_on_cb )
{
	pv1000_state *state = machine.driver_data<pv1000_state>();
	int vpos = state->m_screen->vpos();
	int next_vpos = vpos + 12;

	/* Set IRQ line and schedule release of IRQ line */
	device_set_input_line( state->m_maincpu, 0, ASSERT_LINE );
	state->m_irq_off_timer->adjust( state->m_screen->time_until_pos(vpos, 380/2 ) );

	/* Schedule next IRQ trigger */
	if ( vpos >= 255 )
	{
		next_vpos = 195;
	}
	state->m_irq_on_timer->adjust( state->m_screen->time_until_pos(next_vpos, 0 ) );
}


static TIMER_CALLBACK( d65010_irq_off_cb )
{
	pv1000_state *state = machine.driver_data<pv1000_state>();

	device_set_input_line( state->m_maincpu, 0, CLEAR_LINE );
}


static MACHINE_START( pv1000 )
{
	pv1000_state *state = machine.driver_data<pv1000_state>();

	state->m_irq_on_timer = machine.scheduler().timer_alloc(FUNC(d65010_irq_on_cb));
	state->m_irq_off_timer = machine.scheduler().timer_alloc(FUNC(d65010_irq_off_cb));
	state->m_maincpu = machine.device( "maincpu" );
	state->m_screen = machine.device<screen_device>("screen" );
}


static MACHINE_RESET( pv1000 )
{
	pv1000_state *state = machine.driver_data<pv1000_state>();

	state->m_io_regs[5] = 0;
	state->m_fd_data = 0;
	state->m_irq_on_timer->adjust( state->m_screen->time_until_pos(195, 0 ) );
	state->m_irq_off_timer->adjust( attotime::never );
}


static const gfx_layout pv1000_3bpp_gfx =
{
	8, 8,			/* 8x8 characters */
	RGN_FRAC(1,1),
	3,
	{ 0, 8*8, 16*8 },
	{ 0, 1, 2, 3, 4, 5, 6, 7 },
	{ 0*8, 1*8, 2*8, 3*8, 4*8, 5*8, 6*8, 7*8 },
	8*8*4
};


static GFXDECODE_START( pv1000 )
	GFXDECODE_ENTRY( "cart", 8, pv1000_3bpp_gfx, 0, 8 )
	GFXDECODE_ENTRY( "gfxram", 8, pv1000_3bpp_gfx, 0, 8 )
GFXDECODE_END


static MACHINE_CONFIG_START( pv1000, pv1000_state )

	MCFG_CPU_ADD( "maincpu", Z80, 17897725/5 )
	MCFG_CPU_PROGRAM_MAP( pv1000 )
	MCFG_CPU_IO_MAP( pv1000_io )

	MCFG_MACHINE_START( pv1000 )
	MCFG_MACHINE_RESET( pv1000 )

	/* D65010G031 - Video & sound chip */
	MCFG_SCREEN_ADD( "screen", RASTER )
	MCFG_SCREEN_FORMAT( BITMAP_FORMAT_INDEXED16 )
	MCFG_SCREEN_RAW_PARAMS( 17897725/3, 380, 0, 256, 262, 0, 192 )
	MCFG_SCREEN_UPDATE( pv1000 )

	MCFG_PALETTE_LENGTH( 8 )
	MCFG_PALETTE_INIT( pv1000 )
	MCFG_GFXDECODE( pv1000 )

	MCFG_SPEAKER_STANDARD_MONO("mono")
	MCFG_SOUND_ADD( "pv1000_sound", PV1000, 17897725 )
	MCFG_SOUND_ROUTE( ALL_OUTPUTS, "mono", 1.00 )

	/* Cartridge slot */
	MCFG_CARTSLOT_ADD("cart")
	MCFG_CARTSLOT_EXTENSION_LIST("bin")
	MCFG_CARTSLOT_MANDATORY
	MCFG_CARTSLOT_INTERFACE("pv1000_cart")
	MCFG_CARTSLOT_LOAD(pv1000_cart)

	/* Software lists */
	MCFG_SOFTWARE_LIST_ADD("cart_list","pv1000")
MACHINE_CONFIG_END


ROM_START( pv1000 )
	ROM_REGION( 0x4000, "cart", ROMREGION_ERASE00 )
	ROM_REGION( 0x400, "gfxram", ROMREGION_ERASE00 )
ROM_END


/*    YEAR  NAME     PARENT  COMPAT  MACHINE  INPUT  INIT    COMPANY   FULLNAME    FLAGS */
CONS( 1983, pv1000,  0,      0,      pv1000,  pv1000,   0,   "Casio",  "PV-1000",  GAME_NOT_WORKING )
