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

  machine.c

  Functions to emulate general aspects of the machine
  (RAM, ROM, interrupts, I/O ports)

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

#include <stdarg.h>
#include "driver.h"
#include "timer.h"
#include "cpu/z80/z80.h"
#include "video/generic.h"
#include "includes/cgenie.h"
#include "machine/wd17xx.h"
#include "devices/basicdsk.h"
#include "devices/cartslot.h"
#include "sound/ay8910.h"
#include "sound/dac.h"
#include "image.h"

#define AYWriteReg(chip,port,value) \
	AY8910_control_port_0_w(0,port);  \
	AY8910_write_port_0_w(0,value)

#define TAPE_HEADER "Colour Genie - Virtual Tape File"

UINT8 *cgenie_fontram;


int cgenie_tv_mode = -1;
static int cgenie_load_cas = 0;
static int port_ff = 0xff;

#define CGENIE_DRIVE_INFO



/********************************************************
   abbreviations used:
   GPL	Granules Per Lump
   GAT	Granule Allocation Table
   GATL GAT Length
   GATM GAT Mask
   DDGA Disk Directory Granule Allocation
*********************************************************/
typedef struct
{
	UINT8 DDSL; 	 /* Disk Directory Start Lump (lump number of GAT) */
	UINT8 GATL; 	 /* # of bytes used in the Granule Allocation Table sector */
	UINT8 STEPRATE;  /* step rate and somet SD/DD flag ... */
	UINT8 TRK;		 /* number of tracks */
	UINT8 SPT;		 /* sectors per track (both heads counted!) */
	UINT8 GATM; 	 /* number of used bits per byte in the GAT sector (GAT mask) */
	UINT8 P7;		 /* ???? always zero */
	UINT8 FLAGS;	 /* ???? some flags (SS/DS bit 6) */
	UINT8 GPL;		 /* Sectors per granule (always 5 for the Colour Genie) */
	UINT8 DDGA; 	 /* Disk Directory Granule allocation (number of driectory granules) */
}	PDRIVE;

static const PDRIVE pd_list[12] = {
	{0x14, 0x28, 0x07, 0x28, 0x0A, 0x02, 0x00, 0x00, 0x05, 0x02}, /* CMD"<0=A" 40 tracks, SS, SD */
	{0x14, 0x28, 0x07, 0x28, 0x14, 0x04, 0x00, 0x40, 0x05, 0x04}, /* CMD"<0=B" 40 tracks, DS, SD */
	{0x18, 0x30, 0x53, 0x28, 0x12, 0x03, 0x00, 0x03, 0x05, 0x03}, /* CMD"<0=C" 40 tracks, SS, DD */
	{0x18, 0x30, 0x53, 0x28, 0x24, 0x06, 0x00, 0x43, 0x05, 0x06}, /* CMD"<0=D" 40 tracks, DS, DD */
	{0x14, 0x28, 0x07, 0x28, 0x0A, 0x02, 0x00, 0x04, 0x05, 0x02}, /* CMD"<0=E" 40 tracks, SS, SD */
	{0x14, 0x28, 0x07, 0x28, 0x14, 0x04, 0x00, 0x44, 0x05, 0x04}, /* CMD"<0=F" 40 tracks, DS, SD */
	{0x18, 0x30, 0x53, 0x28, 0x12, 0x03, 0x00, 0x07, 0x05, 0x03}, /* CMD"<0=G" 40 tracks, SS, DD */
	{0x18, 0x30, 0x53, 0x28, 0x24, 0x06, 0x00, 0x47, 0x05, 0x06}, /* CMD"<0=H" 40 tracks, DS, DD */
	{0x28, 0x50, 0x07, 0x50, 0x0A, 0x02, 0x00, 0x00, 0x05, 0x02}, /* CMD"<0=I" 80 tracks, SS, SD */
	{0x28, 0x50, 0x07, 0x50, 0x14, 0x04, 0x00, 0x40, 0x05, 0x04}, /* CMD"<0=J" 80 tracks, DS, SD */
	{0x30, 0x60, 0x53, 0x50, 0x12, 0x03, 0x00, 0x03, 0x05, 0x03}, /* CMD"<0=K" 80 tracks, SS, DD */
	{0x30, 0x60, 0x53, 0x50, 0x24, 0x06, 0x00, 0x43, 0x05, 0x06}, /* CMD"<0=L" 80 tracks, DS, DD */
};

#define IRQ_TIMER		0x80
#define IRQ_FDC 		0x40
static UINT8 irq_status;

static UINT8 motor_drive;
static UINT8 head;

/* current tape file handles */
static char tape_name[12+1];
static mame_file *tape_get_file;

/* tape buffer for the first eight bytes at write (to extract a filename) */
static UINT8 tape_buffer[9];

/* file offset within tape file */
static int tape_count;

/* number of sync and data bits that were written */
static int put_bit_count;

/* number of sync and data bits to read */
static int get_bit_count;

/* sync and data bits mask */
static int tape_bits;

/* time in cycles for the next bit at read */
static int tape_time;

/* flag if writing to tape detected the sync header A5 already */
static int in_sync;

/* cycle count at last output port change */
static int put_cycles;

/* cycle count at last input port read */
static int get_cycles;

/* a prototype to be called from cgenie_stop_machine */
static void tape_put_close(running_machine *machine);

/* Buffer for image reads */
static UINT8 *image_buff;

static OPBASE_HANDLER (opbaseoverride)
{
	UINT8 *RAM = memory_region(REGION_CPU1);
	mess_image *img;
	UINT8 *buff, *s, data;
	UINT16 size, entry = 0, block_len, block_ofs = 0;

	/* check if the BASIC prompt is visible on the screen */
	if( cgenie_load_cas && RAM[0x4400+3*40] == 0x3e )
	{
		cgenie_load_cas = 0;

		img = image_from_devtype_and_index(IO_CASSETTE, 0);

		if (image_exists(img))
		{
			buff = image_buff;
			size = image_fread(img, buff, 65536);
			s = buff;
			if( memcmp(s, TAPE_HEADER, sizeof(TAPE_HEADER)-1) == 0 )
			{
				s = (UINT8*)memchr(s, 26, size);
				if( s )
				{
					*s++ = '\n';
					*s++ = '\0';
					logerror("%s",s);
				}
				size -= s - buff;
			}
			if( s[0] == 0x66 && s[1] == 0x55 && s[8] == 0x3c )
			{
				logerror("image name: [%-6.6s]\n",s+1);
				s += 8;
				size -= 8;
				while( size > 3 )
				{
					data = *s++;
					switch( data )
					{
					case 0x01:		   /* CMD file header */
					case 0x07:		   /* another type of CMD file header */
					case 0x3c:		   /* CAS file header */
						block_len = *s++;
						/* on CMD files size zero means size 256 */
						if( block_len == 0 )
							block_len = 256;
						block_ofs = *s++;
						block_ofs += 256 * *s++;
						if( data != 0x3c )
						{
							block_len -= 2;
							if( block_len == 0 )
								block_len = 256;
						}
						size -= 4;
						logerror("cgenie_cmd_load block ($%02X) %d at $%04X\n", data, block_len, block_ofs);
						while( block_len && size )
						{
							program_write_byte(block_ofs, *s);
							s++;
							block_ofs++;
							block_len--;
							size--;
						}
						if( data == 0x3c )
							s++;
						break;
					case 0x02:
						block_len = *s++;
						size -= 1;
					case 0x78:
						block_ofs = *s++;
						block_ofs += 256 * *s++;
						if( !entry )
							entry = block_ofs;
						logerror( "cgenie_cmd_load entry ($%02X) at $%04X\n", data, entry);
						size -= 3;
						if( size <= 3 )
						{
							logerror("starting program at $%04X\n", block_ofs);
						}
						break;
					default:
						size--;
					}
				}
				activecpu_set_reg(REG_PC, entry);
			}
		}
		memory_set_opbase_handler(0,NULL);
	}
	return address;
}

static void cgenie_fdc_callback(wd17xx_state_t event, void *param);

static void cgenie_machine_reset(running_machine *machine)
{
	UINT8 *ROM = memory_region(REGION_CPU1);

	/* reset the AY8910 to be quiet, since the cgenie BIOS doesn't */
	AYWriteReg(0, 0, 0);
	AYWriteReg(0, 1, 0);
	AYWriteReg(0, 2, 0);
	AYWriteReg(0, 3, 0);
	AYWriteReg(0, 4, 0);
	AYWriteReg(0, 5, 0);
	AYWriteReg(0, 6, 0);
	AYWriteReg(0, 7, 0x3f);
	AYWriteReg(0, 8, 0);
	AYWriteReg(0, 9, 0);
	AYWriteReg(0, 10, 0);

	/* wipe out color RAM */
	memset(&ROM[0x0f000], 0x00, 0x0400);

	/* wipe out font RAM */
	memset(&ROM[0x0f400], 0xff, 0x0400);

	if( readinputport(0) & 0x80 )
	{
		logerror("cgenie floppy discs enabled\n");
	}
	else
	{
		logerror("cgenie floppy discs disabled\n");
	}

	/* copy DOS ROM, if enabled or wipe out that memory area */
	if( readinputport(0) & 0x40 )
	{
		if ( readinputport(0) & 0x080 )
		{
			memory_install_read8_handler(0, ADDRESS_SPACE_PROGRAM, 0xc000, 0xdfff, 0, 0, MRA8_ROM);
			memory_install_write8_handler(0, ADDRESS_SPACE_PROGRAM, 0xc000, 0xdfff, 0, 0, MWA8_ROM);
			logerror("cgenie DOS enabled\n");
			memcpy(&ROM[0x0c000],&ROM[0x10000], 0x2000);
		}
		else
		{
			memory_install_read8_handler(0, ADDRESS_SPACE_PROGRAM, 0xc000, 0xdfff, 0, 0, MRA8_NOP);
			memory_install_write8_handler(0, ADDRESS_SPACE_PROGRAM, 0xc000, 0xdfff, 0, 0, MWA8_NOP);
			logerror("cgenie DOS disabled (no floppy image given)\n");
		}
	}
	else
	{
		memory_install_read8_handler(0, ADDRESS_SPACE_PROGRAM, 0xc000, 0xdfff, 0, 0, MRA8_NOP);
		memory_install_write8_handler(0, ADDRESS_SPACE_PROGRAM, 0xc000, 0xdfff, 0, 0, MWA8_NOP);
		logerror("cgenie DOS disabled\n");
		memset(&memory_region(REGION_CPU1)[0x0c000], 0x00, 0x2000);
	}

	/* copy EXT ROM, if enabled or wipe out that memory area */
	if( readinputport(0) & 0x20 )
	{
		memory_install_read8_handler(0, ADDRESS_SPACE_PROGRAM, 0xe000, 0xefff, 0, 0, MRA8_ROM);
		memory_install_write8_handler(0, ADDRESS_SPACE_PROGRAM, 0xe000, 0xefff, 0, 0, MWA8_ROM);
		logerror("cgenie EXT enabled\n");
		memcpy(&memory_region(REGION_CPU1)[0x0e000],
			   &memory_region(REGION_CPU1)[0x12000], 0x1000);
	}
	else
	{
		memory_install_read8_handler(0, ADDRESS_SPACE_PROGRAM, 0xe000, 0xefff, 0, 0, MRA8_NOP);
		memory_install_write8_handler(0, ADDRESS_SPACE_PROGRAM, 0xe000, 0xefff, 0, 0, MWA8_NOP);
		logerror("cgenie EXT disabled\n");
		memset(&memory_region(REGION_CPU1)[0x0e000], 0x00, 0x1000);
	}

	cgenie_load_cas = 1;
	memory_set_opbase_handler(0, opbaseoverride);
}

MACHINE_START( cgenie )
{
	UINT8 *gfx = memory_region(REGION_GFX2);
	int i;

	/* initialize static variables */
	memset(tape_buffer, 0, sizeof(tape_buffer));
	memset(tape_name, 0, sizeof(tape_name));
	tape_get_file = NULL;
	irq_status = 0;
	motor_drive = 0;
	head = 0;
	tape_count = 0;
	put_bit_count = 0;
	get_bit_count = 0;
	tape_bits = 0;
	tape_time = 0;
	in_sync = 0;
	put_cycles = 0;
	get_cycles = 0;
	image_buff = auto_malloc(65536);

	/*
	 * Every fifth cycle is a wait cycle, so I reduced
	 * the overlocking by one fitfth
	 */
	cpunum_set_clockscale(0, 0.80);

	/* Initialize some patterns to be displayed in graphics mode */
	for( i = 0; i < 256; i++ )
		memset(gfx + i * 8, i, 8);

	/* set up RAM */
	memory_install_read8_handler(0,  ADDRESS_SPACE_PROGRAM, 0x4000, 0x4000 + mess_ram_size - 1, 0, 0, MRA8_BANK1);
	memory_install_write8_handler(0, ADDRESS_SPACE_PROGRAM, 0x4000, 0x4000 + mess_ram_size - 1, 0, 0, cgenie_videoram_w);
	videoram = mess_ram;
	memory_set_bankptr(1, mess_ram);

	/* set up FDC */
	wd17xx_init(WD_TYPE_179X, cgenie_fdc_callback, NULL);

	add_reset_callback(machine, cgenie_machine_reset);
	add_exit_callback(machine, tape_put_close);
}


DEVICE_LOAD( cgenie_cassette )
{
	return INIT_PASS;
}

/* basic-dsk is a disk image format which has the tracks and sectors
 * stored in order, no information is stored which details the number
 * of tracks, number of sides, number of sectors etc, so we need to
 * set that up here
 */
DEVICE_LOAD( cgenie_floppy )
{
	int i, j, dir_offset;
	UINT8 buff[16];
	UINT8 tracks = 0;
	UINT8 heads = 0;
	UINT8 spt = 0;
	short dir_sector = 0;
	short dir_length = 0;

	/* A Floppy Isnt manditory, so return if none */
	if (device_load_basicdsk_floppy(image) != INIT_PASS)
		return INIT_FAIL;

	/* determine image geometry */
	image_fseek(image, 0, SEEK_SET);

	/* determine geometry from disk contents */
	for( i = 0; i < 12; i++ )
	{
		image_fseek(image, pd_list[i].SPT * 256, SEEK_SET);
		image_fread(image, buff, 16);
		/* find an entry with matching DDSL */
		if (buff[0] != 0x00 || buff[1] != 0xfe || buff[2] != pd_list[i].DDSL)
			continue;
		logerror("cgenie: checking format #%d\n", i);

		dir_sector = pd_list[i].DDSL * pd_list[i].GATM * pd_list[i].GPL + pd_list[i].SPT;
		dir_length = pd_list[i].DDGA * pd_list[i].GPL;

		/* scan directory for DIR/SYS or NCW1983/JHL files */
		/* look into sector 2 and 3 first entry relative to DDSL */
		for( j = 16; j < 32; j += 8 )
		{
			dir_offset = dir_sector * 256 + j * 32;
			if( image_fseek(image, dir_offset, SEEK_SET) < 0 )
				break;
			if( image_fread(image, buff, 16) != 16 )
				break;
			if( !strncmp((char*)buff + 5, "DIR     SYS", 11) ||
				!strncmp((char*)buff + 5, "NCW1983 JHL", 11) )
			{
				tracks = pd_list[i].TRK;
				heads = (pd_list[i].SPT > 18) ? 2 : 1;
				spt = pd_list[i].SPT / heads;
				dir_sector = pd_list[i].DDSL * pd_list[i].GATM * pd_list[i].GPL + pd_list[i].SPT;
				dir_length = pd_list[i].DDGA * pd_list[i].GPL;
				memcpy(memory_region(REGION_CPU1) + 0x5A71 + image_index_in_device(image) * sizeof(PDRIVE), &pd_list[i], sizeof(PDRIVE));
				break;
			}
		}

		logerror("cgenie: geometry %d tracks, %d heads, %d sec/track\n", tracks, heads, spt);
		/* set geometry so disk image can be read */
		basicdsk_set_geometry(image, tracks, heads, spt, 256, 0, 0, FALSE);

		logerror("cgenie: directory sectors %d - %d (%d sectors)\n", dir_sector, dir_sector + dir_length - 1, dir_length);
		/* mark directory sectors with deleted data address mark */
		/* assumption dir_sector is a sector offset */
		for (j = 0; j < dir_length; j++)
		{
			UINT8 track;
			UINT8 side;
			UINT8 sector_id;
			UINT16 track_offset;
			UINT16 sector_offset;

			/* calc sector offset */
			sector_offset = dir_sector + j;

			/* get track offset */
			track_offset = sector_offset / spt;

			/* calc track */
			track = track_offset / heads;

			/* calc side */
			side = track_offset % heads;

			/* calc sector id - first sector id is 0! */
			sector_id = sector_offset % spt;

			/* set deleted data address mark for sector specified */
			basicdsk_set_ddam(image, track, side, sector_id, 1);
		}

	}
	return INIT_PASS;
}

/*************************************
 *
 *				Tape emulation.
 *
 *************************************/

/*******************************************************************
 * tape_put_byte
 * write next data byte to virtual tape. After collecting the first
 * nine bytes try to extract the kind of data and filename.
 *******************************************************************/
static void tape_put_byte(UINT8 value)
{
	mame_file *file;

	if( tape_count < 9 )
	{
		tape_name[0] = '\0';
		tape_buffer[tape_count++] = value;
		if( tape_count == 9 )
		{
			/* BASIC tape ? */
			if( tape_buffer[1] != 0x55 || tape_buffer[8] != 0x3c )
				sprintf(tape_name, "basic%c.cas", tape_buffer[1]);
			else
			/* SYSTEM tape ? */
			if( tape_buffer[1] == 0x55 && tape_buffer[8] == 0x3c )
				sprintf(tape_name, "%-6.6s.cas", tape_buffer + 2);
			else
				strcpy(tape_name, "unknown.cas");
			mame_fopen(SEARCHPATH_IMAGE, tape_name, OPEN_FLAG_WRITE, &file);
		}
	}
	else
	{
		tape_count++;
	}
}

/*******************************************************************
 * tape_put_close
 * eventuall flush output buffer and close an open
 * virtual tape output file.
 *******************************************************************/
static void tape_put_close(running_machine *machine)
{
	tape_count = 0;
}

/*******************************************************************
 * tape_put_bit
 * port FF tape status bit changed. Figure out what to do with it ;-)
 *******************************************************************/
static void tape_put_bit(void)
{
	int now_cycles = activecpu_gettotalcycles();
	int diff = now_cycles - put_cycles;
	int limit = 12 * (memory_region(REGION_CPU1)[0x4310] + memory_region(REGION_CPU1)[0x4311]);
	UINT8 value;

	/* overrun since last write ? */
	if( diff > 4000 )
	{
		/* reset tape output */
		tape_put_close(Machine);
		put_bit_count = tape_bits = in_sync = 0;
	}
	else
	{
		/* change within time for a 1 bit ? */
		if( diff < limit )
		{
			tape_bits = (tape_bits << 1) | 1;
			switch( in_sync )
			{
				case 0: 	   /* look for sync AA */
					if( (tape_bits & 0xffff) == 0xcccc )
					{
						popmessage("Tape sync1 written");
						in_sync = 1;
					}
					break;
				case 1: 	   /* look for sync 66 */
					if( (tape_bits & 0xffff) == 0x3c3c )
					{
						in_sync = 2;
						popmessage("Tape sync2 written");
						put_bit_count = 16;
					}
					break;
				case 2: 	   /* count 1 bit */
					put_bit_count += 1;
			}
		}
		/* no change within time indicates a 0 bit */
		else
		{
			tape_bits <<= 2;
			switch (in_sync)
			{
				case 0: 	   /* look for sync AA */
					if( (tape_bits & 0xffff) == 0xcccc )
					{
						popmessage("Tape sync1 written");
						in_sync = 1;
					}
					break;
				case 1: 	   /* look for sync 66 */
					if( (tape_bits & 0xffff) == 0x3c3c )
					{
						popmessage("Tape sync2 written");
						in_sync = 2;
						put_bit_count = 16;
					}
					break;
				case 2: 	   /* count 2 bits */
					put_bit_count += 2;
			}
		}

		logerror("%4d %4d %d bits %04X\n", diff, limit, in_sync, tape_bits & 0xffff);

		/* collected 8 sync plus 8 data bits ? */
		if( put_bit_count >= 16 )
		{
			/* extract data bits to value */
			value = 0;
			if( tape_bits & 0x8000 )
				value |= 0x80;
			if( tape_bits & 0x2000 )
				value |= 0x40;
			if( tape_bits & 0x0800 )
				value |= 0x20;
			if( tape_bits & 0x0200 )
				value |= 0x10;
			if( tape_bits & 0x0080 )
				value |= 0x08;
			if( tape_bits & 0x0020 )
				value |= 0x04;
			if( tape_bits & 0x0008 )
				value |= 0x02;
			if( tape_bits & 0x0002 )
				value |= 0x01;
			put_bit_count -= 16;
			tape_bits = 0;
			tape_put_byte(value);
		}
	}
/* remember the cycle count of this write */
	put_cycles = now_cycles;
}

/*******************************************************************
 * tape_get_byte
 * read next byte from input tape image file.
 * the first 32 bytes are faked to be sync header AA.
 *******************************************************************/
static void tape_get_byte(void)
{
	UINT8 value;

	if( tape_get_file )
	{
		if( tape_count < 32 )
		{
			popmessage("Tape load sync1");
			value = 0xaa;
		}
		else
		{
			popmessage("Tape load '%s' $%04X bytes", tape_name, tape_count);
			mame_fread(tape_get_file, &value, 1);
		}
		tape_bits |= 0xaaaa;
		if( value & 0x80 )
			tape_bits ^= 0x4000;
		if( value & 0x40 )
			tape_bits ^= 0x1000;
		if( value & 0x20 )
			tape_bits ^= 0x0400;
		if( value & 0x10 )
			tape_bits ^= 0x0100;
		if( value & 0x08 )
			tape_bits ^= 0x0040;
		if( value & 0x04 )
			tape_bits ^= 0x0010;
		if( value & 0x02 )
			tape_bits ^= 0x0004;
		if( value & 0x01 )
			tape_bits ^= 0x0001;
		get_bit_count = 16;
		tape_count++;
	}
}

/*******************************************************************
 * tape_get_open
 * Open a virtual tape image file for input. Look for a special
 * header and skip leading description, if present.
 * The filename is taken from BASIC input buffer at 41E8 ff.
 *******************************************************************/
static void tape_get_open(void)
{
	if( !tape_get_file )
	{
		char buffer[sizeof(TAPE_HEADER)];
		UINT8 *ram = memory_region(REGION_CPU1);
		char *p;

		sprintf(tape_name, "%-6.6s", ram + 0x41e8);
		p = strchr(tape_name, ' ');
		if( p ) *p = '\0';
		strcat(tape_name, ".cas");
		if (tape_name[0] != ' ')
		{
			logerror("tape_get_open '%s'\n", tape_name);
			mame_fopen(SEARCHPATH_IMAGE, tape_name, OPEN_FLAG_READ, &tape_get_file);
		}
		if( tape_get_file )
		{
			popmessage("Tape load '%s'", tape_name);
			mame_fread(tape_get_file, buffer, sizeof(TAPE_HEADER));
			if( strncmp(buffer, TAPE_HEADER, sizeof(TAPE_HEADER) - 1) == 0 )
			{
				UINT8 data;
				/* skip data until zero byte */
				do
				{
					mame_fread(tape_get_file, &data, 1);
				} while( data );
			}
			else
			{
				/* seek back to start of tape */
				mame_fseek(tape_get_file, 0, SEEK_SET);
			}
		}
		tape_count = 0;
	}
}

static void tape_get_bit(void)
{
	int now_cycles = activecpu_gettotalcycles();
	int limit = 10 * memory_region(REGION_CPU1)[0x4312];
	int diff = now_cycles - get_cycles;

	/* overrun since last read ? */
	if( diff >= 4000 )
	{
		if( tape_get_file )
		{
			mame_fclose(tape_get_file);
			tape_get_file = NULL;
			popmessage("Tape file closed");
		}
		get_bit_count = tape_bits = tape_time = 0;
	}
	else
	/* check what he will get for input */
	{
		/* count down cycles */
		tape_time -= diff;

		/* time for the next sync or data bit ? */
		if( tape_time <= 0 )
		{
			/* approx time for a bit */
			tape_time += limit;
			/* need to read get new data ? */
			if( --get_bit_count <= 0 )
			{
				tape_get_open();
				tape_get_byte();
			}
			/* shift next sync or data bit to bit 16 */
			tape_bits <<= 1;
			if( tape_bits & 0x10000 )
				port_ff ^= 1;
		}
	}
	/* remember the cycle count of this read */
	get_cycles = now_cycles;
}

/*************************************
 *
 *				Port handlers.
 *
 *************************************/

/* used bits on port FF */
#define FF_CAS	0x01		   /* tape output signal */
#define FF_BGD0 0x04		   /* background color enable */
#define FF_CHR1 0x08		   /* charset 0xc0 - 0xff 1:fixed 0:defined */
#define FF_CHR0 0x10		   /* charset 0x80 - 0xbf 1:fixed 0:defined */
#define FF_CHR	(FF_CHR0 | FF_CHR1)
#define FF_FGR	0x20		   /* 1: "hi" resolution graphics, 0: text mode */
#define FF_BGD1 0x40		   /* background color select 1 */
#define FF_BGD2 0x80		   /* background color select 2 */
#define FF_BGD	(FF_BGD0 | FF_BGD1 | FF_BGD2)

WRITE8_HANDLER( cgenie_port_ff_w )
{
	int port_ff_changed = port_ff ^ data;

	if( port_ff_changed & FF_CAS )	/* casette port changed ? */
	{
		/* virtual tape ? */
		if( readinputport(0) & 0x08 )
			tape_put_bit();
		else
			DAC_data_w(0,(data & FF_CAS) ? 127:0 );
	}

	/* background bits changed ? */
	if( port_ff_changed & FF_BGD )
	{
		unsigned char r, g, b;

		if( data & FF_BGD0 )
		{
			r = 112;
			g = 0;
			b = 112;
		}
		else
		{
			if( cgenie_tv_mode == 0 )
			{
				switch( data & (FF_BGD1 + FF_BGD2) )
				{
				case FF_BGD1:
					r = 112;
					g = 40;
					b = 32;
					break;
				case FF_BGD2:
					r = 40;
					g = 112;
					b = 32;
					break;
				case FF_BGD1 + FF_BGD2:
					r = 72;
					g = 72;
					b = 72;
					break;
				default:
					r = 0;
					g = 0;
					b = 0;
					break;
				}
			}
			else
			{
				r = 15;
				g = 15;
				b = 15;
			}
		}
		palette_set_color_rgb(Machine, 0, r, g, b);
	}

	/* character mode changed ? */
	if( port_ff_changed & FF_CHR )
	{
		cgenie_font_offset[2] = (data & FF_CHR0) ? 0x00 : 0x80;
		cgenie_font_offset[3] = (data & FF_CHR1) ? 0x00 : 0x80;
		if( (port_ff_changed & FF_CHR) == FF_CHR )
			cgenie_invalidate_range(0x80, 0xff);
		else
		if( (port_ff_changed & FF_CHR) == FF_CHR0 )
			cgenie_invalidate_range(0x80, 0xbf);
		else
			cgenie_invalidate_range(0xc0, 0xff);
	}

	/* graphics mode changed ? */
	if( port_ff_changed & FF_FGR )
	{
		cgenie_invalidate_range(0x00, 0xff);
		cgenie_mode_select(data & FF_FGR);
	}

	port_ff = data;
}

 READ8_HANDLER( cgenie_port_ff_r )
{
	/* virtual tape ? */

	if( readinputport(0) & 0x08 )
		tape_get_bit();

	return port_ff;
}

int cgenie_port_xx_r( int offset )
{
	return 0xff;
}

/*************************************
 *									 *
 *		Memory handlers 			 *
 *									 *
 *************************************/

static UINT8 psg_a_out = 0x00;
static UINT8 psg_b_out = 0x00;
static UINT8 psg_a_inp = 0x00;
static UINT8 psg_b_inp = 0x00;

 READ8_HANDLER( cgenie_psg_port_a_r )
{
	return psg_a_inp;
}

UINT8 cgenie_psg_port_b_r(offs_t port)
{
	if( psg_a_out < 0xd0 )
	{
		/* comparator value */
		psg_b_inp = 0x00;
		if( input_port_9_r(0) > psg_a_out )
			psg_b_inp |= 0x80;
		if( input_port_10_r(0) > psg_a_out )
			psg_b_inp |= 0x40;
		if( input_port_11_r(0) > psg_a_out )
			psg_b_inp |= 0x20;
		if( input_port_12_r(0) > psg_a_out )
			psg_b_inp |= 0x10;
	}
	else
	{
		/* read keypad matrix */
		psg_b_inp = 0xFF;
		if( !(psg_a_out & 0x01) )
			psg_b_inp &= ~(input_port_13_r(0) & 15);
		if( !(psg_a_out & 0x02) )
			psg_b_inp &= ~(input_port_13_r(0) / 16);
		if( !(psg_a_out & 0x04) )
			psg_b_inp &= ~(input_port_14_r(0) & 15);
		if( !(psg_a_out & 0x08) )
			psg_b_inp &= ~(input_port_14_r(0) / 16);
		if( !(psg_a_out & 0x10) )
			psg_b_inp &= ~(input_port_15_r(0) & 15);
		if( !(psg_a_out & 0x20) )
			psg_b_inp &= ~(input_port_15_r(0) / 16);
	}
	return psg_b_inp;
}

WRITE8_HANDLER( cgenie_psg_port_a_w )
{
	psg_a_out = data;
}

WRITE8_HANDLER( cgenie_psg_port_b_w )
{
	psg_b_out = data;
}

 READ8_HANDLER( cgenie_status_r )
{
	/* If the floppy isn't emulated, return 0 */
	if( (readinputport(0) & 0x80) == 0 )
		return 0;
	return wd17xx_status_r(offset);
}

 READ8_HANDLER( cgenie_track_r )
{
	/* If the floppy isn't emulated, return 0xff */
	if( (readinputport(0) & 0x80) == 0 )
		return 0xff;
	return wd17xx_track_r(offset);
}

 READ8_HANDLER( cgenie_sector_r )
{
	/* If the floppy isn't emulated, return 0xff */
	if( (readinputport(0) & 0x80) == 0 )
		return 0xff;
	return wd17xx_sector_r(offset);
}

 READ8_HANDLER(cgenie_data_r )
{
	/* If the floppy isn't emulated, return 0xff */
	if( (readinputport(0) & 0x80) == 0 )
		return 0xff;
	return wd17xx_data_r(offset);
}

WRITE8_HANDLER( cgenie_command_w )
{
	/* If the floppy isn't emulated, return immediately */
	if( (readinputport(0) & 0x80) == 0 )
		return;
	wd17xx_command_w(offset, data);
}

WRITE8_HANDLER( cgenie_track_w )
{
	/* If the floppy isn't emulated, ignore the write */
	if( (readinputport(0) & 0x80) == 0 )
		return;
	wd17xx_track_w(offset, data);
}

WRITE8_HANDLER( cgenie_sector_w )
{
	/* If the floppy isn't emulated, ignore the write */
	if( (readinputport(0) & 0x80) == 0 )
		return;
	wd17xx_sector_w(offset, data);
}

WRITE8_HANDLER( cgenie_data_w )
{
	/* If the floppy isn't emulated, ignore the write */
	if( (readinputport(0) & 0x80) == 0 )
		return;
	wd17xx_data_w(offset, data);
}

 READ8_HANDLER( cgenie_irq_status_r )
{
int result = irq_status;

	irq_status &= ~(IRQ_TIMER | IRQ_FDC);
	return result;
}

INTERRUPT_GEN( cgenie_timer_interrupt )
{
	if( (irq_status & IRQ_TIMER) == 0 )
	{
		irq_status |= IRQ_TIMER;
		cpunum_set_input_line(0, 0, HOLD_LINE);
	}
}

static INTERRUPT_GEN( cgenie_fdc_interrupt )
{
	if( (irq_status & IRQ_FDC) == 0 )
	{
		irq_status |= IRQ_FDC;
		cpunum_set_input_line(0, 0, HOLD_LINE);
	}
}

void cgenie_fdc_callback(wd17xx_state_t event, void *param)
{
	/* if disc hardware is not enabled, do not cause an int */
	if (!( readinputport(0) & 0x80 ))
		return;

	switch( event )
	{
		case WD17XX_IRQ_CLR:
			irq_status &= ~IRQ_FDC;
			break;
		case WD17XX_IRQ_SET:
			cgenie_fdc_interrupt();
			break;
		case WD17XX_DRQ_CLR:
		case WD17XX_DRQ_SET:
			/* do nothing */
			break;
	}
}

WRITE8_HANDLER( cgenie_motor_w )
{
	UINT8 drive = 255;

	logerror("cgenie motor_w $%02X\n", data);

	if( data & 1 )
		drive = 0;
	if( data & 2 )
		drive = 1;
	if( data & 4 )
		drive = 2;
	if( data & 8 )
		drive = 3;

	if( drive > 3 )
		return;

	/* mask head select bit */
		head = (data >> 4) & 1;

	/* currently selected drive */
	motor_drive = drive;

	wd17xx_set_drive(drive);
	wd17xx_set_side(head);
}

/*************************************
 *		Keyboard					 *
 *************************************/
 READ8_HANDLER( cgenie_keyboard_r )
{
	int result = 0;

	if( offset & 0x01 )
		result |= input_port_1_r(0);
	if( offset & 0x02 )
		result |= input_port_2_r(0);
	if( offset & 0x04 )
		result |= input_port_3_r(0);
	if( offset & 0x08 )
		result |= input_port_4_r(0);
	if( offset & 0x10 )
		result |= input_port_5_r(0);
	if( offset & 0x20 )
		result |= input_port_6_r(0);
	if( offset & 0x40 )
		result |= input_port_7_r(0);
	if( offset & 0x80 )
		result |= input_port_8_r(0);

	return result;
}

/*************************************
 *		Video RAM					 *
 *************************************/

int cgenie_videoram_r( int offset )
{
	return videoram[offset];
}

WRITE8_HANDLER( cgenie_videoram_w )
{
	/* write to video RAM */
	if( data == videoram[offset] )
		return; 			   /* no change */
	videoram[offset] = data;
	dirtybuffer[offset] = 1;
}

 READ8_HANDLER( cgenie_colorram_r )
{
	return colorram[offset] | 0xf0;
}

WRITE8_HANDLER( cgenie_colorram_w )
{
	int a;

	/* only bits 0 to 3 */
	data &= 15;
	/* nothing changed ? */
	if( data == colorram[offset] )
		return;

/* set new value */
	colorram[offset] = data;
/* make offset relative to video frame buffer offset */
	offset = (offset + (cgenie_get_register(12) << 8) + cgenie_get_register(13)) & 0x3ff;
/* mark every 1k of the frame buffer dirty */
	for( a = offset; a < 0x4000; a += 0x400 )
		dirtybuffer[a] = 1;
}

 READ8_HANDLER( cgenie_fontram_r )
{
	return cgenie_fontram[offset];
}

WRITE8_HANDLER( cgenie_fontram_w )
{
	UINT8 *dp;
	int code;

	if( data == cgenie_fontram[offset] )
		return; 			   /* no change */

	/* store data */
	cgenie_fontram[offset] = data;

	/* convert eight pixels */
	dp = &Machine->gfx[0]->gfxdata[(256 * 8 + offset) * Machine->gfx[0]->width];
	dp[0] = (data & 0x80) ? 1 : 0;
	dp[1] = (data & 0x40) ? 1 : 0;
	dp[2] = (data & 0x20) ? 1 : 0;
	dp[3] = (data & 0x10) ? 1 : 0;
	dp[4] = (data & 0x08) ? 1 : 0;
	dp[5] = (data & 0x04) ? 1 : 0;
	dp[6] = (data & 0x02) ? 1 : 0;
	dp[7] = (data & 0x01) ? 1 : 0;

	/* invalidate related character */
	code = 0x80 + offset / 8;
	cgenie_invalidate_range(code, code);
}

/*************************************
 *
 *		Interrupt handlers.
 *
 *************************************/

INTERRUPT_GEN( cgenie_frame_interrupt )
{
	if( cgenie_tv_mode != (readinputport(0) & 0x10) )
	{
		cgenie_tv_mode = input_port_0_r(0) & 0x10;
		memset(dirtybuffer, 1, videoram_size);
		/* force setting of background color */
		port_ff ^= FF_BGD0;
		cgenie_port_ff_w(0, port_ff ^ FF_BGD0);
	}
}

