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

  coleco.c

  Driver file to handle emulation of the Colecovision.

  Marat Fayzullin (ColEm source)
  Marcel de Kogel (AdamEm source)
  Mike Balfour
  Ben Bruscella
  Sean Young

  NEWS:
    - Modified memory map, now it has only 1k of RAM mapped on 8k Slot
    - Modified I/O map, now it is handled as on a real ColecoVision:
        The I/O map is broken into 4 write and 4 read ports:
            80-9F (W) = Set both controllers to keypad mode
            80-9F (R) = Not Connected

            A0-BF (W) = Video Chip (TMS9928A), A0=0 -> Write Register 0 , A0=1 -> Write Register 1
            A0-BF (R) = Video Chip (TMS9928A), A0=0 -> Read Register 0 , A0=1 -> Read Register 1

            C0-DF (W) = Set both controllers to joystick mode 
            C0-DF (R) = Not Connected

            E0-FF (W) = Sound Chip (SN76496)
            E0-FF (R) = Read Controller data, A1=0 -> read controller 1, A1=1 -> read controller 2

    - Modified paddle handler, now it is handled as on a real ColecoVision
    - Added support for a Driving Controller (Expansion Module #2), enabled via configuration
    - Added support for a Roller Controller (Trackball), enabled via configuration
    - Added support for two Super Action Controller, enabled via configuracion

    EXTRA CONTROLLERS INFO:

        -Driving Controller (Expansion Module #2). It consist of a steering wheel and a gas pedal. Only one
         can be used on a real ColecoVision. The gas pedal is not analog, internally it is just a switch.
         On a real ColecoVision, when the Driving Controller is enabled, the controller 1 do not work because 
         have been replaced by the Driving Controller, and controller 2 have to be used to start game, gear 
         shift, etc.
         Driving Controller is just a spinner on controller 1 socket similar to the one on Roller Controller
         and Super Action Controllers so you can use Roller Controller or Super Action Controllers to play 
         games requiring Driving Controller.
         
        -Roller Controller. Basically a trackball with four buttons (the two fire buttons from player 1 and
         the two fire buttons from player 2). Only one Roller Controller can be used on a real ColecoVision.
	 Roller Controller is connected to both controller sockets and both controllers are conected to the Roller
	 Controller, it uses the spinner pins of both sockets to generate the X and Y signals (X from controller 1 
	 and the Y from controller 2)

        -Super Action Controllers. It is a hand controller with a keypad, four buttons (the two from
         the player pad and two more), and a spinner. This was made primarily for two player sport games, but
         will work for every other ColecoVision game.

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

#include "driver.h"
#include "sound/sn76496.h"
#include "vidhrdw/tms9928a.h"
#include "includes/coleco.h"
#include "devices/cartslot.h"

READ8_HANDLER(coleco_video_r)
{  
    return ((offset & 0x01) ? TMS9928A_register_r(1) : TMS9928A_vram_r(0));
}

WRITE8_HANDLER(coleco_video_w)
{
    (offset & 0x01) ? TMS9928A_register_w(1, data) : TMS9928A_vram_w(0, data);
}

static ADDRESS_MAP_START( coleco_map, ADDRESS_SPACE_PROGRAM, 8 )
	AM_RANGE(0x0000, 0x1fff) AM_ROM
	AM_RANGE(0x2000, 0x5fff) AM_NOP
	AM_RANGE(0x6000, 0x63ff) AM_RAM AM_MIRROR(0x1c00)
	AM_RANGE(0x8000, 0xffff) AM_ROM
ADDRESS_MAP_END

static ADDRESS_MAP_START( coleco_io_map, ADDRESS_SPACE_IO, 8 )
	ADDRESS_MAP_FLAGS( AMEF_ABITS(8) )
	AM_RANGE(0x80, 0x9f) AM_WRITE(coleco_paddle_toggle_off)
	AM_RANGE(0xa0, 0xbf) AM_READWRITE(coleco_video_r, coleco_video_w)
	AM_RANGE(0xc0, 0xdf) AM_WRITE(coleco_paddle_toggle_on)
	AM_RANGE(0xe0, 0xff) AM_READWRITE(coleco_paddle_r, SN76496_0_w)
ADDRESS_MAP_END

INPUT_PORTS_START( coleco )
    PORT_START_TAG("IN0")
    PORT_BIT( 0x01, IP_ACTIVE_LOW, IPT_KEYBOARD ) PORT_NAME("0 (pad 1)") PORT_CODE(KEYCODE_0)
    PORT_BIT( 0x02, IP_ACTIVE_LOW, IPT_KEYBOARD ) PORT_NAME("1 (pad 1)") PORT_CODE(KEYCODE_1)
    PORT_BIT( 0x04, IP_ACTIVE_LOW, IPT_KEYBOARD ) PORT_NAME("2 (pad 1)") PORT_CODE(KEYCODE_2)
    PORT_BIT( 0x08, IP_ACTIVE_LOW, IPT_KEYBOARD ) PORT_NAME("3 (pad 1)") PORT_CODE(KEYCODE_3)
    PORT_BIT( 0x10, IP_ACTIVE_LOW, IPT_KEYBOARD ) PORT_NAME("4 (pad 1)") PORT_CODE(KEYCODE_4)
    PORT_BIT( 0x20, IP_ACTIVE_LOW, IPT_KEYBOARD ) PORT_NAME("5 (pad 1)") PORT_CODE(KEYCODE_5)
    PORT_BIT( 0x40, IP_ACTIVE_LOW, IPT_KEYBOARD ) PORT_NAME("6 (pad 1)") PORT_CODE(KEYCODE_6)
    PORT_BIT( 0x80, IP_ACTIVE_LOW, IPT_KEYBOARD ) PORT_NAME("7 (pad 1)") PORT_CODE(KEYCODE_7)

    PORT_START_TAG("IN1")
    PORT_BIT( 0x01, IP_ACTIVE_LOW, IPT_KEYBOARD ) PORT_NAME("8 (pad 1)") PORT_CODE(KEYCODE_8)
    PORT_BIT( 0x02, IP_ACTIVE_LOW, IPT_KEYBOARD ) PORT_NAME("9 (pad 1)") PORT_CODE(KEYCODE_9)
    PORT_BIT( 0x04, IP_ACTIVE_LOW, IPT_KEYBOARD ) PORT_NAME("# (pad 1)") PORT_CODE(KEYCODE_MINUS)
    PORT_BIT( 0x08, IP_ACTIVE_LOW, IPT_KEYBOARD ) PORT_NAME(". (pad 1)") PORT_CODE(KEYCODE_EQUALS)
    PORT_BIT( 0x40, IP_ACTIVE_LOW, IPT_BUTTON2 )
    PORT_BIT( 0xb0, IP_ACTIVE_LOW, IPT_UNKNOWN )

    PORT_START_TAG("IN2")
    PORT_BIT( 0x01, IP_ACTIVE_LOW, IPT_JOYSTICK_UP )
    PORT_BIT( 0x02, IP_ACTIVE_LOW, IPT_JOYSTICK_RIGHT )
    PORT_BIT( 0x04, IP_ACTIVE_LOW, IPT_JOYSTICK_DOWN )
    PORT_BIT( 0x08, IP_ACTIVE_LOW, IPT_JOYSTICK_LEFT )
    PORT_BIT( 0x40, IP_ACTIVE_LOW, IPT_BUTTON1 )
    PORT_BIT( 0xb0, IP_ACTIVE_LOW, IPT_UNKNOWN )

    PORT_START_TAG("IN3")
    PORT_BIT( 0x01, IP_ACTIVE_LOW, IPT_KEYBOARD ) PORT_NAME("0 (pad 2)") PORT_CODE(KEYCODE_0_PAD)
    PORT_BIT( 0x02, IP_ACTIVE_LOW, IPT_KEYBOARD ) PORT_NAME("1 (pad 2)") PORT_CODE(KEYCODE_1_PAD)
    PORT_BIT( 0x04, IP_ACTIVE_LOW, IPT_KEYBOARD ) PORT_NAME("2 (pad 2)") PORT_CODE(KEYCODE_2_PAD)
    PORT_BIT( 0x08, IP_ACTIVE_LOW, IPT_KEYBOARD ) PORT_NAME("3 (pad 2)") PORT_CODE(KEYCODE_3_PAD)
    PORT_BIT( 0x10, IP_ACTIVE_LOW, IPT_KEYBOARD ) PORT_NAME("4 (pad 2)") PORT_CODE(KEYCODE_4_PAD)
    PORT_BIT( 0x20, IP_ACTIVE_LOW, IPT_KEYBOARD ) PORT_NAME("5 (pad 2)") PORT_CODE(KEYCODE_5_PAD)
    PORT_BIT( 0x40, IP_ACTIVE_LOW, IPT_KEYBOARD ) PORT_NAME("6 (pad 2)") PORT_CODE(KEYCODE_6_PAD)
    PORT_BIT( 0x80, IP_ACTIVE_LOW, IPT_KEYBOARD ) PORT_NAME("7 (pad 2)") PORT_CODE(KEYCODE_7_PAD)

    PORT_START_TAG("IN4")
    PORT_BIT( 0x01, IP_ACTIVE_LOW, IPT_KEYBOARD ) PORT_NAME("8 (pad 2)") PORT_CODE(KEYCODE_8_PAD)
    PORT_BIT( 0x02, IP_ACTIVE_LOW, IPT_KEYBOARD ) PORT_NAME("9 (pad 2)") PORT_CODE(KEYCODE_9_PAD)
    PORT_BIT( 0x04, IP_ACTIVE_LOW, IPT_KEYBOARD ) PORT_NAME("# (pad 2)") PORT_CODE(KEYCODE_MINUS_PAD)
    PORT_BIT( 0x08, IP_ACTIVE_LOW, IPT_KEYBOARD ) PORT_NAME(". (pad 2)") PORT_CODE(KEYCODE_PLUS_PAD)
    PORT_BIT( 0x40, IP_ACTIVE_LOW, IPT_BUTTON2 )		PORT_PLAYER(2)
    PORT_BIT( 0xb0, IP_ACTIVE_LOW, IPT_UNKNOWN )

    PORT_START_TAG("IN5")
    PORT_BIT( 0x01, IP_ACTIVE_LOW, IPT_JOYSTICK_UP )	PORT_PLAYER(2)
    PORT_BIT( 0x02, IP_ACTIVE_LOW, IPT_JOYSTICK_RIGHT )	PORT_PLAYER(2)
    PORT_BIT( 0x04, IP_ACTIVE_LOW, IPT_JOYSTICK_DOWN )	PORT_PLAYER(2)
    PORT_BIT( 0x08, IP_ACTIVE_LOW, IPT_JOYSTICK_LEFT )	PORT_PLAYER(2)
    PORT_BIT( 0x40, IP_ACTIVE_LOW, IPT_BUTTON1 )		PORT_PLAYER(2)
    PORT_BIT( 0xb0, IP_ACTIVE_LOW, IPT_UNKNOWN )

    PORT_START_TAG("IN6")
    PORT_DIPNAME( 0x07, 0x00, "Extra Controllers" )
    PORT_DIPSETTING(	0x00, DEF_STR( None ) )
    PORT_DIPSETTING(	0x01, "Driving Controller" )
    PORT_DIPSETTING(	0x02, "Roller Controller" )
    PORT_DIPSETTING(	0x04, "Super Action Controllers" )
    PORT_BIT( 0x10, IP_ACTIVE_LOW, IPT_BUTTON3 ) PORT_NAME("SAC Blue Button P1")	PORT_CODE(KEYCODE_Z)
    PORT_BIT( 0x20, IP_ACTIVE_LOW, IPT_BUTTON4 ) PORT_NAME("SAC Purple Button P1")	PORT_CODE(KEYCODE_X)
    PORT_BIT( 0x40, IP_ACTIVE_LOW, IPT_BUTTON3 ) PORT_NAME("SAC Blue Button P2")	PORT_CODE(KEYCODE_Q) PORT_PLAYER(2)
    PORT_BIT( 0x80, IP_ACTIVE_LOW, IPT_BUTTON4 ) PORT_NAME("SAC Purple Button P2")	PORT_CODE(KEYCODE_W) PORT_PLAYER(2)

    PORT_START_TAG("IN7")	// Extra Controls (Driving Controller, SAC P1 slider, Roller Controller X Axis)
    PORT_BIT( 0x0f, 0x00, IPT_TRACKBALL_X ) PORT_SENSITIVITY(20) PORT_KEYDELTA(10) PORT_MINMAX(0, 0) PORT_CODE_DEC(KEYCODE_L) PORT_CODE_INC(KEYCODE_J) PORT_RESET

    PORT_START_TAG("IN8")	// Extra Controls (SAC P2 slider, Roller Controller Y Axis)
    PORT_BIT( 0x0f, 0x00, IPT_TRACKBALL_Y ) PORT_SENSITIVITY(20) PORT_KEYDELTA(10) PORT_MINMAX(0, 0) PORT_CODE_DEC(KEYCODE_I) PORT_CODE_INC(KEYCODE_K) PORT_RESET PORT_PLAYER(2)
INPUT_PORTS_END



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

  The interrupts come from the vdp. The vdp (tms9928a) interrupt can go up
  and down; the Coleco only uses nmi interrupts (which is just a pulse). They
  are edge-triggered: as soon as the vdp interrupt line goes up, an interrupt
  is generated. Nothing happens when the line stays up or goes down.

  To emulate this correctly, we set a callback in the tms9928a (they
  can occur mid-frame). At every frame we call the TMS9928A_interrupt
  because the vdp needs to know when the end-of-frame occurs, but we don't
  return an interrupt.

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

extern int JoyStat[2];

static INTERRUPT_GEN( coleco_interrupt )
{
    TMS9928A_interrupt();
}

static void coleco_vdp_interrupt (int state)
{
	static int last_state = 0;

    // only if it goes up
	if (state && !last_state) cpunum_set_input_line(0, INPUT_LINE_NMI, PULSE_LINE);
	last_state = state;
}

void paddle_callback (int param)
{
    int port7 = readinputportbytag("IN7");
    int port8 = readinputportbytag("IN8");

    if (port7 == 0)
		JoyStat[0] = 0;
    else if (port7 & 0x08)
		JoyStat[0] = -1;
    else
		JoyStat[0] = 1;

    if (port8 == 0)
		JoyStat[1] = 0;
    else if (port8 & 0x08)
		JoyStat[1] = -1;
    else
		JoyStat[1] = 1;

    if (JoyStat[0] || JoyStat[1]) 
		cpunum_set_input_line(0, 0, HOLD_LINE);
}

static MACHINE_INIT(coleco)
{
    cpunum_set_input_line_vector(0, 0, 0xff);
	memset(&memory_region(REGION_CPU1)[0x6000], 0xff, 0x400);	// initialize RAM
    timer_pulse(TIME_IN_MSEC(20), 0, paddle_callback);
}

static const TMS9928a_interface tms9928a_interface =
{
	TMS99x8A,
	0x4000,
	coleco_vdp_interrupt
};

static MACHINE_DRIVER_START( coleco )
	// basic machine hardware
	MDRV_CPU_ADD(Z80, 7159090/2)	// 3.579545 MHz
	MDRV_CPU_PROGRAM_MAP(coleco_map, 0)
	MDRV_CPU_IO_MAP(coleco_io_map, 0)
	MDRV_CPU_VBLANK_INT(coleco_interrupt, 1)
	MDRV_FRAMES_PER_SECOND(60)
	MDRV_VBLANK_DURATION(DEFAULT_REAL_60HZ_VBLANK_DURATION)

	MDRV_MACHINE_INIT(coleco)

    // video hardware
	MDRV_TMS9928A(&tms9928a_interface)

	// sound hardware
	MDRV_SPEAKER_STANDARD_MONO("mono")
	MDRV_SOUND_ADD(SN76496, 7159090/2)	/* 3.579545 MHz */
	MDRV_SOUND_ROUTE(ALL_OUTPUTS, "mono", 1.00)
MACHINE_DRIVER_END

ROM_START (coleco)
    ROM_REGION( 0x10000, REGION_CPU1, 0 )
    ROM_LOAD( "coleco.rom", 0x0000, 0x2000, CRC(3aa93ef3) SHA1(45bedc4cbdeac66c7df59e9e599195c778d86a92) )
ROM_END

ROM_START (colecoa)
    // differences to 0x3aa93ef3 modified characters, added a pad 2 related fix
    ROM_REGION( 0x10000, REGION_CPU1, 0 )
    ROM_LOAD( "colecoa.rom", 0x0000, 0x2000, CRC(39bb16fc) SHA1(99ba9be24ada3e86e5c17aeecb7a2d68c5edfe59) )
ROM_END

ROM_START (colecob)
    ROM_REGION( 0x10000, REGION_CPU1, 0 )
    ROM_LOAD( "svi603.rom", 0x0000, 0x2000, CRC(19e91b82) SHA1(8a30abe5ffef810b0f99b86db38b1b3c9d259b78) )
ROM_END

static void coleco_cartslot_getinfo(struct IODevice *dev)
{
	/* cartslot */
	cartslot_device_getinfo(dev);
	dev->count = 1;
	dev->file_extensions = "rom\0col\0bin\0";
	dev->load = device_load_coleco_cart;
	dev->imgverify = coleco_cart_verify;
}

SYSTEM_CONFIG_START(coleco)
	CONFIG_DEVICE(coleco_cartslot_getinfo)
SYSTEM_CONFIG_END

//    YEAR  NAME      PARENT	COMPAT	MACHINE   INPUT     INIT	CONFIG	COMPANY   FULLNAME
CONS( 1982, coleco,   0,		0,	coleco,   coleco,   0,		coleco,	"Coleco", "ColecoVision" )
CONS( 1982, colecoa,  coleco,	0,		coleco,   coleco,   0,		coleco,	"Coleco", "ColecoVision (Thick Characters)" )
CONS( 1983, colecob,  coleco,	0,		coleco,   coleco,   0,		coleco,	"Spectravideo", "SVI-603 Coleco Game Adapter" )
