/*##########################################################################

    atarigen.c

    General functions for Atari raster games.

##########################################################################*/


#include "driver.h"
#include "atarigen.h"
#include "slapstic.h"
#include "cpu/m6502/m6502.h"



/*##########################################################################
    CONSTANTS
##########################################################################*/

#define SOUND_INTERLEAVE_RATE		TIME_IN_USEC(50)
#define SOUND_INTERLEAVE_REPEAT		20



/*##########################################################################
    GLOBAL VARIABLES
##########################################################################*/

UINT8 				atarigen_scanline_int_state;
UINT8 				atarigen_sound_int_state;
UINT8 				atarigen_video_int_state;

const UINT16 *	atarigen_eeprom_default;
UINT16 *			atarigen_eeprom;
size_t 				atarigen_eeprom_size;

UINT8 				atarigen_cpu_to_sound_ready;
UINT8 				atarigen_sound_to_cpu_ready;

UINT16 *			atarigen_playfield;
UINT16 *			atarigen_playfield2;
UINT16 *			atarigen_playfield_upper;
UINT16 *			atarigen_alpha;
UINT16 *			atarigen_alpha2;
UINT16 *			atarigen_xscroll;
UINT16 *			atarigen_yscroll;

UINT32 *			atarigen_playfield32;
UINT32 *			atarigen_alpha32;

tilemap *			atarigen_playfield_tilemap;
tilemap *			atarigen_playfield2_tilemap;
tilemap *			atarigen_alpha_tilemap;
tilemap *			atarigen_alpha2_tilemap;

UINT16 *			atarivc_data;
UINT16 *			atarivc_eof_data;
struct atarivc_state_desc atarivc_state;



/*##########################################################################
    STATIC VARIABLES
##########################################################################*/

static atarigen_int_callback update_int_callback;
static void *		scanline_interrupt_timer;

static UINT8 		eeprom_unlocked;

static UINT8 		atarigen_slapstic_num;
static UINT16 *	atarigen_slapstic;
static int			atarigen_slapstic_bank;
static void *		atarigen_slapstic_bank0;

static UINT8 		sound_cpu_num;
static UINT8 		atarigen_cpu_to_sound;
static UINT8 		atarigen_sound_to_cpu;
static UINT8 		timed_int;
static UINT8 		ym2151_int;

static atarigen_scanline_callback scanline_callback;
static int 			scanlines_per_callback;
static double 		scanline_callback_period;
static int 			last_scanline;

static int 			actual_vc_latch0;
static int 			actual_vc_latch1;
static UINT8		atarivc_playfields;

static int			playfield_latch;
static int			playfield2_latch;



/*##########################################################################
    STATIC FUNCTION DECLARATIONS
##########################################################################*/

static void scanline_interrupt_callback(int param);

static void decompress_eeprom_word(const UINT16 *data);
static void decompress_eeprom_byte(const UINT16 *data);

static void update_6502_irq(void);
static void sound_comm_timer(int reps_left);
static void delayed_sound_reset(int param);
static void delayed_sound_w(int param);
static void delayed_6502_sound_w(int param);

static void atarigen_set_vol(int volume, int type);

static void vblank_timer(int param);
static void scanline_timer(int scanline);

static void atarivc_common_w(offs_t offset, UINT16 newword);

static void unhalt_cpu(int param);



/*##########################################################################
    INTERRUPT HANDLING
##########################################################################*/

/*---------------------------------------------------------------
    atarigen_interrupt_reset: Initializes the state of all
    the interrupt sources.
---------------------------------------------------------------*/

void atarigen_interrupt_reset(atarigen_int_callback update_int)
{
	/* set the callback */
	update_int_callback = update_int;

	/* reset the interrupt states */
	atarigen_video_int_state = atarigen_sound_int_state = atarigen_scanline_int_state = 0;
	scanline_interrupt_timer = NULL;

	/* create a timer for scanlines */
	scanline_interrupt_timer = timer_alloc(scanline_interrupt_callback);
}


/*---------------------------------------------------------------
    atarigen_update_interrupts: Forces the interrupt callback
    to be called with the current VBLANK and sound interrupt
    states.
---------------------------------------------------------------*/

void atarigen_update_interrupts(void)
{
	(*update_int_callback)();
}



/*---------------------------------------------------------------
    atarigen_scanline_int_set: Sets the scanline when the next
    scanline interrupt should be generated.
---------------------------------------------------------------*/

void atarigen_scanline_int_set(int scanline)
{
	timer_adjust(scanline_interrupt_timer, cpu_getscanlinetime(scanline), 0, 0);
}


/*---------------------------------------------------------------
    atarigen_scanline_int_gen: Standard interrupt routine
    which sets the scanline interrupt state.
---------------------------------------------------------------*/

INTERRUPT_GEN( atarigen_scanline_int_gen )
{
	atarigen_scanline_int_state = 1;
	(*update_int_callback)();
}


/*---------------------------------------------------------------
    atarigen_scanline_int_ack_w: Resets the state of the
    scanline interrupt.
---------------------------------------------------------------*/

WRITE16_HANDLER( atarigen_scanline_int_ack_w )
{
	atarigen_scanline_int_state = 0;
	(*update_int_callback)();
}

WRITE32_HANDLER( atarigen_scanline_int_ack32_w )
{
	atarigen_scanline_int_state = 0;
	(*update_int_callback)();
}


/*---------------------------------------------------------------
    atarigen_sound_int_gen: Standard interrupt routine which
    sets the sound interrupt state.
---------------------------------------------------------------*/

INTERRUPT_GEN( atarigen_sound_int_gen )
{
	atarigen_sound_int_state = 1;
	(*update_int_callback)();
}


/*---------------------------------------------------------------
    atarigen_sound_int_ack_w: Resets the state of the sound
    interrupt.
---------------------------------------------------------------*/

WRITE16_HANDLER( atarigen_sound_int_ack_w )
{
	atarigen_sound_int_state = 0;
	(*update_int_callback)();
}

WRITE32_HANDLER( atarigen_sound_int_ack32_w )
{
	atarigen_sound_int_state = 0;
	(*update_int_callback)();
}


/*---------------------------------------------------------------
    atarigen_video_int_gen: Standard interrupt routine which
    sets the video interrupt state.
---------------------------------------------------------------*/

INTERRUPT_GEN( atarigen_video_int_gen )
{
	atarigen_video_int_state = 1;
	(*update_int_callback)();
}


/*---------------------------------------------------------------
    atarigen_video_int_ack_w: Resets the state of the video
    interrupt.
---------------------------------------------------------------*/

WRITE16_HANDLER( atarigen_video_int_ack_w )
{
	atarigen_video_int_state = 0;
	(*update_int_callback)();
}

WRITE32_HANDLER( atarigen_video_int_ack32_w )
{
	atarigen_video_int_state = 0;
	(*update_int_callback)();
}


/*---------------------------------------------------------------
    scanline_interrupt_callback: Signals an interrupt.
---------------------------------------------------------------*/

static void scanline_interrupt_callback(int param)
{
	/* generate the interrupt */
	atarigen_scanline_int_gen();

	/* set a new timer to go off at the same scan line next frame */
	timer_adjust(scanline_interrupt_timer, TIME_IN_HZ(Machine->refresh_rate[0]), 0, 0);
}



/*##########################################################################
    EEPROM HANDLING
##########################################################################*/

/*---------------------------------------------------------------
    atarigen_eeprom_reset: Makes sure that the unlocked state
    is cleared when we reset.
---------------------------------------------------------------*/

void atarigen_eeprom_reset(void)
{
	eeprom_unlocked = 0;
}


/*---------------------------------------------------------------
    atarigen_eeprom_enable_w: Any write to this handler will
    allow one byte to be written to the EEPROM data area the
    next time.
---------------------------------------------------------------*/

WRITE16_HANDLER( atarigen_eeprom_enable_w )
{
	eeprom_unlocked = 1;
}

WRITE32_HANDLER( atarigen_eeprom_enable32_w )
{
	eeprom_unlocked = 1;
}


/*---------------------------------------------------------------
    atarigen_eeprom_w: Writes a "word" to the EEPROM, which is
    almost always accessed via the low byte of the word only.
    If the EEPROM hasn't been unlocked, the write attempt is
    ignored.
---------------------------------------------------------------*/

WRITE16_HANDLER( atarigen_eeprom_w )
{
	if (!eeprom_unlocked)
		return;

	COMBINE_DATA(&atarigen_eeprom[offset]);
	eeprom_unlocked = 0;
}

WRITE32_HANDLER( atarigen_eeprom32_w )
{
	if (!eeprom_unlocked)
		return;

	COMBINE_DATA(&atarigen_eeprom[offset * 2 + 1]);
	data >>= 16;
	mem_mask >>= 16;
	COMBINE_DATA(&atarigen_eeprom[offset * 2]);
	eeprom_unlocked = 0;
}


/*---------------------------------------------------------------
    atarigen_eeprom_r: Reads a "word" from the EEPROM, which is
    almost always accessed via the low byte of the word only.
---------------------------------------------------------------*/

READ16_HANDLER( atarigen_eeprom_r )
{
	return atarigen_eeprom[offset] | 0xff00;
}

READ16_HANDLER( atarigen_eeprom_upper_r )
{
	return atarigen_eeprom[offset] | 0x00ff;
}

READ32_HANDLER( atarigen_eeprom_upper32_r )
{
	return (atarigen_eeprom[offset * 2] << 16) | atarigen_eeprom[offset * 2 + 1] | 0x00ff00ff;
}


/*---------------------------------------------------------------
    nvram_handler_atarigen: Loads the EEPROM data.
---------------------------------------------------------------*/

NVRAM_HANDLER( atarigen )
{
	if (read_or_write)
		mame_fwrite(file, atarigen_eeprom, atarigen_eeprom_size);
	else if (file)
		mame_fread(file, atarigen_eeprom, atarigen_eeprom_size);
	else
	{
		/* all 0xff's work for most games */
		memset(atarigen_eeprom, 0xff, atarigen_eeprom_size);

		/* anything else must be decompressed */
		if (atarigen_eeprom_default)
		{
			if (atarigen_eeprom_default[0] == 0)
				decompress_eeprom_byte(atarigen_eeprom_default + 1);
			else
				decompress_eeprom_word(atarigen_eeprom_default + 1);
		}
	}
}


/*---------------------------------------------------------------
    decompress_eeprom_word: Used for decompressing EEPROM data
    that has every other byte invalid.
---------------------------------------------------------------*/

void decompress_eeprom_word(const UINT16 *data)
{
	UINT16 *dest = (UINT16 *)atarigen_eeprom;
	UINT16 value;

	while ((value = *data++) != 0)
	{
		int count = (value >> 8);
		value = (value << 8) | (value & 0xff);

		while (count--)
			*dest++ = value;
	}
}


/*---------------------------------------------------------------
    decompress_eeprom_byte: Used for decompressing EEPROM data
    that is byte-packed.
---------------------------------------------------------------*/

void decompress_eeprom_byte(const UINT16 *data)
{
	UINT8 *dest = (UINT8 *)atarigen_eeprom;
	UINT16 value;

	while ((value = *data++) != 0)
	{
		int count = (value >> 8);
		value = (value << 8) | (value & 0xff);

		while (count--)
			*dest++ = value;
	}
}



/*##########################################################################
    SLAPSTIC HANDLING
##########################################################################*/

INLINE void update_bank(int bank)
{
	/* if the bank has changed, copy the memory; Pit Fighter needs this */
	if (bank != atarigen_slapstic_bank)
	{
		/* bank 0 comes from the copy we made earlier */
		if (bank == 0)
			memcpy(atarigen_slapstic, atarigen_slapstic_bank0, 0x2000);
		else
			memcpy(atarigen_slapstic, &atarigen_slapstic[bank * 0x1000], 0x2000);

		/* remember the current bank */
		atarigen_slapstic_bank = bank;
	}
}


/*---------------------------------------------------------------
    atarigen_slapstic_init: Installs memory handlers for the
    slapstic and sets the chip number.
---------------------------------------------------------------*/

void atarigen_slapstic_init(int cpunum, int base, int chipnum)
{
	atarigen_slapstic_num = chipnum;
	atarigen_slapstic = NULL;

	/* if we have a chip, install it */
	if (chipnum)
	{
		/* initialize the slapstic */
		slapstic_init(chipnum);

		/* install the memory handlers */
		atarigen_slapstic = memory_install_read16_handler(cpunum, ADDRESS_SPACE_PROGRAM, base, base + 0x7fff, 0, 0, atarigen_slapstic_r);
		atarigen_slapstic = memory_install_write16_handler(cpunum, ADDRESS_SPACE_PROGRAM, base, base + 0x7fff, 0, 0, atarigen_slapstic_w);

		/* allocate memory for a copy of bank 0 */
		atarigen_slapstic_bank0 = auto_malloc(0x2000);
		memcpy(atarigen_slapstic_bank0, atarigen_slapstic, 0x2000);
	}
}


/*---------------------------------------------------------------
    atarigen_slapstic_reset: Makes the selected slapstic number
    active and resets its state.
---------------------------------------------------------------*/

void atarigen_slapstic_reset(void)
{
	if (atarigen_slapstic_num)
	{
		slapstic_reset();
		update_bank(slapstic_bank());
	}
}


/*---------------------------------------------------------------
    atarigen_slapstic_w: Assuming that the slapstic sits in
    ROM memory space, we just simply tweak the slapstic at this
    address and do nothing more.
---------------------------------------------------------------*/

WRITE16_HANDLER( atarigen_slapstic_w )
{
	update_bank(slapstic_tweak(offset));
}


/*---------------------------------------------------------------
    atarigen_slapstic_r: Tweaks the slapstic at the appropriate
    address and then reads a word from the underlying memory.
---------------------------------------------------------------*/

READ16_HANDLER( atarigen_slapstic_r )
{
	/* fetch the result from the current bank first */
	int result = atarigen_slapstic[offset & 0xfff];

	/* then determine the new one */
	update_bank(slapstic_tweak(offset));
	return result;
}



/*##########################################################################
    SOUND I/O
##########################################################################*/

/*---------------------------------------------------------------
    atarigen_sound_io_reset: Resets the state of the sound I/O.
---------------------------------------------------------------*/

void atarigen_sound_io_reset(int cpu_num)
{
	/* remember which CPU is the sound CPU */
	sound_cpu_num = cpu_num;

	/* reset the internal interrupts states */
	timed_int = ym2151_int = 0;

	/* reset the sound I/O states */
	atarigen_cpu_to_sound = atarigen_sound_to_cpu = 0;
	atarigen_cpu_to_sound_ready = atarigen_sound_to_cpu_ready = 0;
}


/*---------------------------------------------------------------
    atarigen_6502_irq_gen: Generates an IRQ signal to the 6502
    sound processor.
---------------------------------------------------------------*/

INTERRUPT_GEN( atarigen_6502_irq_gen )
{
	timed_int = 1;
	update_6502_irq();
}


/*---------------------------------------------------------------
    atarigen_6502_irq_ack_r: Resets the IRQ signal to the 6502
    sound processor. Both reads and writes can be used.
---------------------------------------------------------------*/

READ8_HANDLER( atarigen_6502_irq_ack_r )
{
	timed_int = 0;
	update_6502_irq();
	return 0;
}

WRITE8_HANDLER( atarigen_6502_irq_ack_w )
{
	timed_int = 0;
	update_6502_irq();
}


/*---------------------------------------------------------------
    atarigen_ym2151_irq_gen: Sets the state of the YM2151's
    IRQ line.
---------------------------------------------------------------*/

void atarigen_ym2151_irq_gen(int irq)
{
	ym2151_int = irq;
	update_6502_irq();
}


/*---------------------------------------------------------------
    atarigen_sound_reset_w: Write handler which resets the
    sound CPU in response.
---------------------------------------------------------------*/

WRITE16_HANDLER( atarigen_sound_reset_w )
{
	timer_set(TIME_NOW, 0, delayed_sound_reset);
}


/*---------------------------------------------------------------
    atarigen_sound_reset: Resets the state of the sound CPU
    manually.
---------------------------------------------------------------*/

void atarigen_sound_reset(void)
{
	timer_set(TIME_NOW, 1, delayed_sound_reset);
}


/*---------------------------------------------------------------
    atarigen_sound_w: Handles communication from the main CPU
    to the sound CPU. Two versions are provided, one with the
    data byte in the low 8 bits, and one with the data byte in
    the upper 8 bits.
---------------------------------------------------------------*/

WRITE16_HANDLER( atarigen_sound_w )
{
	if (ACCESSING_LSB)
		timer_set(TIME_NOW, data & 0xff, delayed_sound_w);
}

WRITE16_HANDLER( atarigen_sound_upper_w )
{
	if (ACCESSING_MSB)
		timer_set(TIME_NOW, (data >> 8) & 0xff, delayed_sound_w);
}

WRITE32_HANDLER( atarigen_sound_upper32_w )
{
	if (ACCESSING_MSB32)
		timer_set(TIME_NOW, (data >> 24) & 0xff, delayed_sound_w);
}


/*---------------------------------------------------------------
    atarigen_sound_r: Handles reading data communicated from the
    sound CPU to the main CPU. Two versions are provided, one
    with the data byte in the low 8 bits, and one with the data
    byte in the upper 8 bits.
---------------------------------------------------------------*/

READ16_HANDLER( atarigen_sound_r )
{
	atarigen_sound_to_cpu_ready = 0;
	atarigen_sound_int_ack_w(0, 0, 0);
	return atarigen_sound_to_cpu | 0xff00;
}

READ16_HANDLER( atarigen_sound_upper_r )
{
	atarigen_sound_to_cpu_ready = 0;
	atarigen_sound_int_ack_w(0, 0, 0);
	return (atarigen_sound_to_cpu << 8) | 0x00ff;
}

READ32_HANDLER( atarigen_sound_upper32_r )
{
	atarigen_sound_to_cpu_ready = 0;
	atarigen_sound_int_ack32_w(0, 0, 0);
	return (atarigen_sound_to_cpu << 24) | 0x00ffffff;
}


/*---------------------------------------------------------------
    atarigen_6502_sound_w: Handles communication from the sound
    CPU to the main CPU.
---------------------------------------------------------------*/

WRITE8_HANDLER( atarigen_6502_sound_w )
{
	timer_set(TIME_NOW, data, delayed_6502_sound_w);
}


/*---------------------------------------------------------------
    atarigen_6502_sound_r: Handles reading data communicated
    from the main CPU to the sound CPU.
---------------------------------------------------------------*/

READ8_HANDLER( atarigen_6502_sound_r )
{
	atarigen_cpu_to_sound_ready = 0;
	cpunum_set_input_line(sound_cpu_num, INPUT_LINE_NMI, CLEAR_LINE);
	return atarigen_cpu_to_sound;
}


/*---------------------------------------------------------------
    update_6502_irq: Called whenever the IRQ state changes. An
    interrupt is generated if either atarigen_6502_irq_gen()
    was called, or if the YM2151 generated an interrupt via
    the atarigen_ym2151_irq_gen() callback.
---------------------------------------------------------------*/

void update_6502_irq(void)
{
	if (timed_int || ym2151_int)
		cpunum_set_input_line(sound_cpu_num, M6502_IRQ_LINE, ASSERT_LINE);
	else
		cpunum_set_input_line(sound_cpu_num, M6502_IRQ_LINE, CLEAR_LINE);
}


/*---------------------------------------------------------------
    sound_comm_timer: Set whenever a command is written from
    the main CPU to the sound CPU, in order to temporarily bump
    up the interleave rate. This helps ensure that communications
    between the two CPUs works properly.
---------------------------------------------------------------*/

static void sound_comm_timer(int reps_left)
{
	if (--reps_left)
		timer_set(SOUND_INTERLEAVE_RATE, reps_left, sound_comm_timer);
}


/*---------------------------------------------------------------
    delayed_sound_reset: Synchronizes the sound reset command
    between the two CPUs.
---------------------------------------------------------------*/

static void delayed_sound_reset(int param)
{
	/* unhalt and reset the sound CPU */
	if (param == 0)
	{
		cpunum_set_input_line(sound_cpu_num, INPUT_LINE_HALT, CLEAR_LINE);
		cpunum_set_input_line(sound_cpu_num, INPUT_LINE_RESET, PULSE_LINE);
	}

	/* reset the sound write state */
	atarigen_sound_to_cpu_ready = 0;
	atarigen_sound_int_ack_w(0, 0, 0);
}


/*---------------------------------------------------------------
    delayed_sound_w: Synchronizes a data write from the main
    CPU to the sound CPU.
---------------------------------------------------------------*/

static void delayed_sound_w(int param)
{
	/* warn if we missed something */
	if (atarigen_cpu_to_sound_ready)
		logerror("Missed command from 68010\n");

	/* set up the states and signal an NMI to the sound CPU */
	atarigen_cpu_to_sound = param;
	atarigen_cpu_to_sound_ready = 1;
	cpunum_set_input_line(sound_cpu_num, INPUT_LINE_NMI, ASSERT_LINE);

	/* allocate a high frequency timer until a response is generated */
	/* the main CPU is *very* sensistive to the timing of the response */
	timer_set(SOUND_INTERLEAVE_RATE, SOUND_INTERLEAVE_REPEAT, sound_comm_timer);
}


/*---------------------------------------------------------------
    delayed_6502_sound_w: Synchronizes a data write from the
    sound CPU to the main CPU.
---------------------------------------------------------------*/

static void delayed_6502_sound_w(int param)
{
	/* warn if we missed something */
	if (atarigen_sound_to_cpu_ready)
		logerror("Missed result from 6502\n");

	/* set up the states and signal the sound interrupt to the main CPU */
	atarigen_sound_to_cpu = param;
	atarigen_sound_to_cpu_ready = 1;
	atarigen_sound_int_gen();
}



/*##########################################################################
    SOUND HELPERS
##########################################################################*/

/*---------------------------------------------------------------
    atarigen_set_vol: Scans for a particular sound chip and
    changes the volume on all channels associated with it.
---------------------------------------------------------------*/

void atarigen_set_vol(int volume, int type)
{
	int sndindex = 0;
	int ch;

	for (ch = 0; ch < MAX_SOUND; ch++)
		if (Machine->drv->sound[ch].sound_type == type)
		{
			int output;
			for (output = 0; output < 2; output++)
				sndti_set_output_gain(type, sndindex, output, volume / 100.0);
			sndindex++;
		}
}


/*---------------------------------------------------------------
    atarigen_set_XXXXX_vol: Sets the volume for a given type
    of chip.
---------------------------------------------------------------*/

void atarigen_set_ym2151_vol(int volume)
{
	atarigen_set_vol(volume, SOUND_YM2151);
}

void atarigen_set_ym2413_vol(int volume)
{
	atarigen_set_vol(volume, SOUND_YM2413);
}

void atarigen_set_pokey_vol(int volume)
{
	atarigen_set_vol(volume, SOUND_POKEY);
}

void atarigen_set_tms5220_vol(int volume)
{
	atarigen_set_vol(volume, SOUND_TMS5220);
}

void atarigen_set_oki6295_vol(int volume)
{
	atarigen_set_vol(volume, SOUND_OKIM6295);
}



/*##########################################################################
    SCANLINE TIMING
##########################################################################*/

/*---------------------------------------------------------------
    atarigen_scanline_timer_reset: Sets up the scanline timer.
---------------------------------------------------------------*/

void atarigen_scanline_timer_reset(atarigen_scanline_callback update_graphics, int frequency)
{
	/* set the scanline callback */
	scanline_callback = update_graphics;
	scanline_callback_period = (double)frequency * cpu_getscanlineperiod();
	scanlines_per_callback = frequency;

	/* compute the last scanline */
	last_scanline = (int)(TIME_IN_HZ(Machine->refresh_rate[0]) / cpu_getscanlineperiod());

	/* set a timer to go off on the next VBLANK */
	timer_set(cpu_getscanlinetime(Machine->drv->screen[0].maxheight), 0, vblank_timer);
}


/*---------------------------------------------------------------
    vblank_timer: Called once every VBLANK to prime the scanline
    timers.
---------------------------------------------------------------*/

static void vblank_timer(int param)
{
	/* set a timer to go off at scanline 0 */
	timer_set(Machine->drv->screen[0].vblank_time, 0, scanline_timer);

	/* set a timer to go off on the next VBLANK */
	timer_set(cpu_getscanlinetime(Machine->drv->screen[0].maxheight), 1, vblank_timer);
}


/*---------------------------------------------------------------
    scanline_timer: Called once every n scanlines to generate
    the periodic callback to the main system.
---------------------------------------------------------------*/

static void scanline_timer(int scanline)
{
	/* callback */
	if (scanline_callback)
	{
		(*scanline_callback)(scanline);

		/* generate another? */
		scanline += scanlines_per_callback;
		if (scanline < last_scanline && scanlines_per_callback)
			timer_set(scanline_callback_period, scanline, scanline_timer);
	}
}



/*##########################################################################
    VIDEO CONTROLLER
##########################################################################*/

/*---------------------------------------------------------------
    atarivc_eof_update: Callback that slurps up data and feeds
    it into the video controller registers every refresh.
---------------------------------------------------------------*/

static void atarivc_eof_update(int param)
{
	atarivc_update(atarivc_eof_data);
	timer_set(cpu_getscanlinetime(0), 0, atarivc_eof_update);
}


/*---------------------------------------------------------------
    atarivc_reset: Initializes the video controller.
---------------------------------------------------------------*/

void atarivc_reset(UINT16 *eof_data, int playfields)
{
	/* this allows us to manually reset eof_data to NULL if it's not used */
	atarivc_eof_data = eof_data;
	atarivc_playfields = playfields;

	/* clear the RAM we use */
	memset(atarivc_data, 0, 0x40);
	memset(&atarivc_state, 0, sizeof(atarivc_state));

	/* reset the latches */
	atarivc_state.latch1 = atarivc_state.latch2 = -1;
	actual_vc_latch0 = actual_vc_latch1 = -1;

	/* start a timer to go off a little before scanline 0 */
	if (atarivc_eof_data)
		timer_set(cpu_getscanlinetime(0), 0, atarivc_eof_update);
}


/*---------------------------------------------------------------
    atarivc_update: Copies the data from the specified location
    once/frame into the video controller registers.
---------------------------------------------------------------*/

void atarivc_update(const UINT16 *data)
{
	int i;

	/* echo all the commands to the video controller */
	for (i = 0; i < 0x1c; i++)
		if (data[i])
			atarivc_common_w(i, data[i]);

	/* update the scroll positions */
	atarimo_set_xscroll(0, atarivc_state.mo_xscroll);
	atarimo_set_yscroll(0, atarivc_state.mo_yscroll);

	tilemap_set_scrollx(atarigen_playfield_tilemap, 0, atarivc_state.pf0_xscroll);
	tilemap_set_scrolly(atarigen_playfield_tilemap, 0, atarivc_state.pf0_yscroll);

	if (atarivc_playfields > 1)
	{
		tilemap_set_scrollx(atarigen_playfield2_tilemap, 0, atarivc_state.pf1_xscroll);
		tilemap_set_scrolly(atarigen_playfield2_tilemap, 0, atarivc_state.pf1_yscroll);
	}

	/* use this for debugging the video controller values */
#if 0
	if (code_pressed(KEYCODE_8))
	{
		static FILE *out;
		if (!out) out = fopen("scroll.log", "w");
		if (out)
		{
			for (i = 0; i < 64; i++)
				fprintf(out, "%04X ", data[i]);
			fprintf(out, "\n");
		}
	}
#endif
}


/*---------------------------------------------------------------
    atarivc_w: Handles an I/O write to the video controller.
---------------------------------------------------------------*/

WRITE16_HANDLER( atarivc_w )
{
	int oldword = atarivc_data[offset];
	int newword = oldword;

	COMBINE_DATA(&newword);
	atarivc_common_w(offset, newword);
}



/*---------------------------------------------------------------
    atarivc_common_w: Does the bulk of the word for an I/O
    write.
---------------------------------------------------------------*/

static void atarivc_common_w(offs_t offset, UINT16 newword)
{
	int oldword = atarivc_data[offset];
	atarivc_data[offset] = newword;

	/* switch off the offset */
	switch (offset)
	{
		/*
            additional registers:

                01 = vertical start (for centering)
                04 = horizontal start (for centering)
        */

		/* set the scanline interrupt here */
		case 0x03:
			if (oldword != newword)
				atarigen_scanline_int_set(newword & 0x1ff);
			break;

		/* latch enable */
		case 0x0a:

			/* reset the latches when disabled */
			atarigen_set_playfield_latch((newword & 0x0080) ? actual_vc_latch0 : -1);
			atarigen_set_playfield2_latch((newword & 0x0080) ? actual_vc_latch1 : -1);

			/* check for rowscroll enable */
			atarivc_state.rowscroll_enable = (newword & 0x2000) >> 13;

			/* check for palette banking */
			if (atarivc_state.palette_bank != (((newword & 0x0400) >> 10) ^ 1))
			{
				force_partial_update(0, cpu_getscanline());
				atarivc_state.palette_bank = ((newword & 0x0400) >> 10) ^ 1;
			}
			break;

		/* indexed parameters */
		case 0x10: case 0x11: case 0x12: case 0x13:
		case 0x14: case 0x15: case 0x16: case 0x17:
		case 0x18: case 0x19: case 0x1a: case 0x1b:
			switch (newword & 15)
			{
				case 9:
					atarivc_state.mo_xscroll = (newword >> 7) & 0x1ff;
					break;

				case 10:
					atarivc_state.pf1_xscroll_raw = (newword >> 7) & 0x1ff;
					atarivc_update_pf_xscrolls();
					break;

				case 11:
					atarivc_state.pf0_xscroll_raw = (newword >> 7) & 0x1ff;
					atarivc_update_pf_xscrolls();
					break;

				case 13:
					atarivc_state.mo_yscroll = (newword >> 7) & 0x1ff;
					break;

				case 14:
					atarivc_state.pf1_yscroll = (newword >> 7) & 0x1ff;
					break;

				case 15:
					atarivc_state.pf0_yscroll = (newword >> 7) & 0x1ff;
					break;
			}
			break;

		/* latch 1 value */
		case 0x1c:
			actual_vc_latch0 = -1;
			actual_vc_latch1 = newword;
			atarigen_set_playfield_latch((atarivc_data[0x0a] & 0x80) ? actual_vc_latch0 : -1);
			atarigen_set_playfield2_latch((atarivc_data[0x0a] & 0x80) ? actual_vc_latch1 : -1);
			break;

		/* latch 2 value */
		case 0x1d:
			actual_vc_latch0 = newword;
			actual_vc_latch1 = -1;
			atarigen_set_playfield_latch((atarivc_data[0x0a] & 0x80) ? actual_vc_latch0 : -1);
			atarigen_set_playfield2_latch((atarivc_data[0x0a] & 0x80) ? actual_vc_latch1 : -1);
			break;

		/* scanline IRQ ack here */
		case 0x1e:
			atarigen_scanline_int_ack_w(0, 0, 0);
			break;

		/* log anything else */
		case 0x00:
		default:
			if (oldword != newword)
				logerror("vc_w(%02X, %04X) ** [prev=%04X]\n", offset, newword, oldword);
			break;
	}
}


/*---------------------------------------------------------------
    atarivc_r: Handles an I/O read from the video controller.
---------------------------------------------------------------*/

READ16_HANDLER( atarivc_r )
{
	logerror("vc_r(%02X)\n", offset);

	/* a read from offset 0 returns the current scanline */
	/* also sets bit 0x4000 if we're in VBLANK */
	if (offset == 0)
	{
		int result = cpu_getscanline();

		if (result > 255)
			result = 255;
		if (result > Machine->visible_area[0].max_y)
			result |= 0x4000;

		return result;
	}
	else
		return atarivc_data[offset];
}



/*##########################################################################
    PLAYFIELD/ALPHA MAP HELPERS
##########################################################################*/

/*---------------------------------------------------------------
    atarigen_alpha_w: Generic write handler for alpha RAM.
---------------------------------------------------------------*/

WRITE16_HANDLER( atarigen_alpha_w )
{
	COMBINE_DATA(&atarigen_alpha[offset]);
	tilemap_mark_tile_dirty(atarigen_alpha_tilemap, offset);
}

WRITE32_HANDLER( atarigen_alpha32_w )
{
	COMBINE_DATA(&atarigen_alpha32[offset]);
	if ((mem_mask & 0xffff0000) != 0xffff0000)
		tilemap_mark_tile_dirty(atarigen_alpha_tilemap, offset * 2);
	if ((mem_mask & 0x0000ffff) != 0x0000ffff)
		tilemap_mark_tile_dirty(atarigen_alpha_tilemap, offset * 2 + 1);
}

WRITE16_HANDLER( atarigen_alpha2_w )
{
	COMBINE_DATA(&atarigen_alpha2[offset]);
	tilemap_mark_tile_dirty(atarigen_alpha2_tilemap, offset);
}



/*---------------------------------------------------------------
    atarigen_set_playfield_latch: Sets the latch for the latched
    playfield handlers below.
---------------------------------------------------------------*/

void atarigen_set_playfield_latch(int data)
{
	playfield_latch = data;
}

void atarigen_set_playfield2_latch(int data)
{
	playfield2_latch = data;
}



/*---------------------------------------------------------------
    atarigen_playfield_w: Generic write handler for PF RAM.
---------------------------------------------------------------*/

WRITE16_HANDLER( atarigen_playfield_w )
{
	COMBINE_DATA(&atarigen_playfield[offset]);
	tilemap_mark_tile_dirty(atarigen_playfield_tilemap, offset);
}

WRITE32_HANDLER( atarigen_playfield32_w )
{
	COMBINE_DATA(&atarigen_playfield32[offset]);
	if ((mem_mask & 0xffff0000) != 0xffff0000)
		tilemap_mark_tile_dirty(atarigen_playfield_tilemap, offset * 2);
	if ((mem_mask & 0x0000ffff) != 0x0000ffff)
		tilemap_mark_tile_dirty(atarigen_playfield_tilemap, offset * 2 + 1);
}

WRITE16_HANDLER( atarigen_playfield2_w )
{
	COMBINE_DATA(&atarigen_playfield2[offset]);
	tilemap_mark_tile_dirty(atarigen_playfield2_tilemap, offset);
}



/*---------------------------------------------------------------
    atarigen_playfield_large_w: Generic write handler for
    large (2-word) playfield RAM.
---------------------------------------------------------------*/

WRITE16_HANDLER( atarigen_playfield_large_w )
{
	COMBINE_DATA(&atarigen_playfield[offset]);
	tilemap_mark_tile_dirty(atarigen_playfield_tilemap, offset / 2);
}



/*---------------------------------------------------------------
    atarigen_playfield_upper_w: Generic write handler for
    upper word of split playfield RAM.
---------------------------------------------------------------*/

WRITE16_HANDLER( atarigen_playfield_upper_w )
{
	COMBINE_DATA(&atarigen_playfield_upper[offset]);
	tilemap_mark_tile_dirty(atarigen_playfield_tilemap, offset);
}



/*---------------------------------------------------------------
    atarigen_playfield_dual_upper_w: Generic write handler for
    upper word of split dual playfield RAM.
---------------------------------------------------------------*/

WRITE16_HANDLER( atarigen_playfield_dual_upper_w )
{
	COMBINE_DATA(&atarigen_playfield_upper[offset]);
	tilemap_mark_tile_dirty(atarigen_playfield_tilemap, offset);
	tilemap_mark_tile_dirty(atarigen_playfield2_tilemap, offset);
}



/*---------------------------------------------------------------
    atarigen_playfield_latched_lsb_w: Generic write handler for
    lower word of playfield RAM with a latch in the LSB of the
    upper word.
---------------------------------------------------------------*/

WRITE16_HANDLER( atarigen_playfield_latched_lsb_w )
{
	COMBINE_DATA(&atarigen_playfield[offset]);
	tilemap_mark_tile_dirty(atarigen_playfield_tilemap, offset);

	if (playfield_latch != -1)
		atarigen_playfield_upper[offset] = (atarigen_playfield_upper[offset] & ~0x00ff) | (playfield_latch & 0x00ff);
}



/*---------------------------------------------------------------
    atarigen_playfield_latched_lsb_w: Generic write handler for
    lower word of playfield RAM with a latch in the MSB of the
    upper word.
---------------------------------------------------------------*/

WRITE16_HANDLER( atarigen_playfield_latched_msb_w )
{
	COMBINE_DATA(&atarigen_playfield[offset]);
	tilemap_mark_tile_dirty(atarigen_playfield_tilemap, offset);

	if (playfield_latch != -1)
		atarigen_playfield_upper[offset] = (atarigen_playfield_upper[offset] & ~0xff00) | (playfield_latch & 0xff00);
}



/*---------------------------------------------------------------
    atarigen_playfield_latched_lsb_w: Generic write handler for
    lower word of second playfield RAM with a latch in the MSB
    of the upper word.
---------------------------------------------------------------*/

WRITE16_HANDLER( atarigen_playfield2_latched_msb_w )
{
	COMBINE_DATA(&atarigen_playfield2[offset]);
	tilemap_mark_tile_dirty(atarigen_playfield2_tilemap, offset);

	if (playfield2_latch != -1)
		atarigen_playfield_upper[offset] = (atarigen_playfield_upper[offset] & ~0xff00) | (playfield2_latch & 0xff00);
}




/*##########################################################################
    VIDEO HELPERS
##########################################################################*/

/*---------------------------------------------------------------
    atarigen_get_hblank: Returns a guesstimate about the current
    HBLANK state, based on the assumption that HBLANK represents
    10% of the scanline period.
---------------------------------------------------------------*/

int atarigen_get_hblank(void)
{
	return (cpu_gethorzbeampos() > (Machine->drv->screen[0].maxwidth * 9 / 10));
}


/*---------------------------------------------------------------
    atarigen_halt_until_hblank_0_w: Halts CPU 0 until the
    next HBLANK.
---------------------------------------------------------------*/

WRITE16_HANDLER( atarigen_halt_until_hblank_0_w )
{
	/* halt the CPU until the next HBLANK */
	int hpos = cpu_gethorzbeampos();
	int hblank = Machine->drv->screen[0].maxwidth * 9 / 10;
	double fraction;

	/* if we're in hblank, set up for the next one */
	if (hpos >= hblank)
		hblank += Machine->drv->screen[0].maxwidth;

	/* halt and set a timer to wake up */
	fraction = (double)(hblank - hpos) / (double)Machine->drv->screen[0].maxwidth;
	timer_set(cpu_getscanlineperiod() * fraction, 0, unhalt_cpu);
	cpunum_set_input_line(0, INPUT_LINE_HALT, ASSERT_LINE);
}


/*---------------------------------------------------------------
    atarigen_666_paletteram_w: 6-6-6 RGB palette RAM handler.
---------------------------------------------------------------*/

WRITE16_HANDLER( atarigen_666_paletteram_w )
{
	int newword, r, g, b;

	COMBINE_DATA(&paletteram16[offset]);
	newword = paletteram16[offset];

	r = ((newword >> 9) & 0x3e) | ((newword >> 15) & 1);
	g = ((newword >> 4) & 0x3e) | ((newword >> 15) & 1);
	b = ((newword << 1) & 0x3e) | ((newword >> 15) & 1);

	r = (r << 2) | (r >> 4);
	g = (g << 2) | (g >> 4);
	b = (b << 2) | (b >> 4);

	palette_set_color(offset, r, g, b);
}


/*---------------------------------------------------------------
    atarigen_expanded_666_paletteram_w: 6-6-6 RGB expanded
    palette RAM handler.
---------------------------------------------------------------*/

WRITE16_HANDLER( atarigen_expanded_666_paletteram_w )
{
	COMBINE_DATA(&paletteram16[offset]);

	if (ACCESSING_MSB)
	{
		int palentry = offset / 2;
		int newword = (paletteram16[palentry * 2] & 0xff00) | (paletteram16[palentry * 2 + 1] >> 8);

		int r, g, b;

		r = ((newword >> 9) & 0x3e) | ((newword >> 15) & 1);
		g = ((newword >> 4) & 0x3e) | ((newword >> 15) & 1);
		b = ((newword << 1) & 0x3e) | ((newword >> 15) & 1);

		r = (r << 2) | (r >> 4);
		g = (g << 2) | (g >> 4);
		b = (b << 2) | (b >> 4);

		palette_set_color(palentry & 0x1ff, r, g, b);
	}
}


/*---------------------------------------------------------------
    atarigen_666_paletteram32_w: 6-6-6 RGB palette RAM handler.
---------------------------------------------------------------*/

WRITE32_HANDLER( atarigen_666_paletteram32_w )
{
	int newword, r, g, b;

	COMBINE_DATA(&paletteram32[offset]);

	if (ACCESSING_MSW32)
	{
		newword = paletteram32[offset] >> 16;

		r = ((newword >> 9) & 0x3e) | ((newword >> 15) & 1);
		g = ((newword >> 4) & 0x3e) | ((newword >> 15) & 1);
		b = ((newword << 1) & 0x3e) | ((newword >> 15) & 1);

		r = (r << 2) | (r >> 4);
		g = (g << 2) | (g >> 4);
		b = (b << 2) | (b >> 4);

		palette_set_color(offset * 2, r, g, b);
	}

	if (ACCESSING_LSW32)
	{
		newword = paletteram32[offset] & 0xffff;

		r = ((newword >> 9) & 0x3e) | ((newword >> 15) & 1);
		g = ((newword >> 4) & 0x3e) | ((newword >> 15) & 1);
		b = ((newword << 1) & 0x3e) | ((newword >> 15) & 1);

		r = (r << 2) | (r >> 4);
		g = (g << 2) | (g >> 4);
		b = (b << 2) | (b >> 4);

		palette_set_color(offset * 2 + 1, r, g, b);
	}
}


/*---------------------------------------------------------------
    unhalt_cpu: Timer callback to release the CPU from a halted state.
---------------------------------------------------------------*/

static void unhalt_cpu(int param)
{
	cpunum_set_input_line(param, INPUT_LINE_HALT, CLEAR_LINE);
}



/*##########################################################################
    MISC HELPERS
##########################################################################*/

/*---------------------------------------------------------------
    atarigen_swap_mem: Inverts the bits in a region.
---------------------------------------------------------------*/

void atarigen_swap_mem(void *ptr1, void *ptr2, int bytes)
{
	UINT8 *p1 = ptr1;
	UINT8 *p2 = ptr2;
	while (bytes--)
	{
		int temp = *p1;
		*p1++ = *p2;
		*p2++ = temp;
	}
}


/*---------------------------------------------------------------
    atarigen_blend_gfx: Takes two GFXElements and blends their
    data together to form one. Then frees the second.
---------------------------------------------------------------*/

void atarigen_blend_gfx(int gfx0, int gfx1, int mask0, int mask1)
{
	gfx_element *gx0 = Machine->gfx[gfx0];
	gfx_element *gx1 = Machine->gfx[gfx1];
	int c, x, y;

	/* loop over elements */
	for (c = 0; c < gx0->total_elements; c++)
	{
		UINT8 *c0base = gx0->gfxdata + gx0->char_modulo * c;
		UINT8 *c1base = gx1->gfxdata + gx1->char_modulo * c;
		UINT32 usage = 0;

		/* loop over height */
		for (y = 0; y < gx0->height; y++)
		{
			UINT8 *c0 = c0base, *c1 = c1base;

			for (x = 0; x < gx0->width; x++, c0++, c1++)
			{
				*c0 = (*c0 & mask0) | (*c1 & mask1);
				usage |= 1 << *c0;
			}
			c0base += gx0->line_modulo;
			c1base += gx1->line_modulo;
			if (gx0->pen_usage)
				gx0->pen_usage[c] = usage;
		}
	}

	/* free the second graphics element */
	freegfx(gx1);
	Machine->gfx[gfx1] = NULL;
}


