#include "emu.h"
#include "includes/psx.h"
#include "psxcd.h"
#include "machine/devhelpr.h"
#include "sound/spu.h"
#include "debugger.h"

//
//
//

//#define debug_cdrom
//#define debug_cdrom_registers
//#define skip_reads
//#define dump_subheader
//#define disable_xa_prefetch
//#define disable_cdda_prefetch

//
//
//

enum cdrom_events
{
	event_cmd_complete=0,
	event_preread_sector,
	event_read_sector,
	event_play_sector
};

//
//
//

enum intr_status
{
	intr_nointr=0,
	intr_dataready,
	intr_acknowledge,
	intr_complete,
	intr_dataend,
	intr_diskerror
};

enum mode_flags
{
	mode_double_speed=0x80,
	mode_adpcm=0x40,
	mode_size=0x20,
	mode_size2=0x10,
	mode_size_shift=4,
	mode_size_mask=(3<<mode_size_shift),
	mode_channel=0x08,
	mode_report=0x04,
	mode_autopause=0x02,
	mode_cdda=0x01
};

enum status_f
{
	status_playing=0x80,
	status_seeking=0x40,
	status_reading=0x20,
	status_shellopen=0x10,
	status_seekerror=0x04,
	status_standby=0x02,
	status_error=0x01
};

struct subheader
{
	unsigned char file,
								channel,
								submode,
								coding;
};

enum submode_flags
{
	submode_eof=0x80,
	submode_realtime=0x40,
	submode_form=0x20,
	submode_trigger=0x10,
	submode_data=0x08,
	submode_audio=0x04,
	submode_video=0x02,
	submode_eor=0x01
};

//
//
//

static const unsigned int max_xa_prefetch_distance=32,
													max_cdda_prefetch_distance=32;

//**************************************************************************
//  DEVICE DEFINITIONS
//**************************************************************************

const device_type PSXCD = &device_creator<psxcd_device>;

void psxcd_device::static_set_devname(device_t &device, const char *devname)
{
	psxcd_device &psxcd = downcast<psxcd_device &>(device);
	psxcd.m_devname = devname;
}

psxcd_device::psxcd_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
	: device_t(mconfig, PSXCD, "PSXCD", tag, owner, clock)
{
}

void psxcd_device::device_start()
{
	unsigned int sysclk=machine().device<cpu_device>("maincpu")->clock()/2;
	start_read_delay=(sysclk/60);
	read_sector_cycles=(sysclk/75);
	preread_delay=(read_sector_cycles>>2)-500;

	m_sysclock = sysclk;

	secleft = 0;
	secsize = 2048;
	res_queue = NULL;
	cur_res = NULL;
	open = false;
	streaming = false;
	sechead = 0;
	sectail = 0;
	secin = 0;
	secskip = 0;
	next_read_event = NULL;
	cbp = cmdbuf;
	first_open = true;

	status=status_standby;
	sr=8|1;
	res=0;
	ir=0;
	mode=0;

	driver = NULL;

	for (int i = 0; i < MAX_PSXCD_TIMERS; i++)
	{
		m_timers[i] = timer_alloc(i, NULL);
		m_timerinuse[i] = false;
	}
}

//
//
//

void psxcd_device::device_reset()
{
	stop_read();
	open=false;

	for (int i = 0; i < MAX_PSXCD_TIMERS; i++)
	{
		m_timerinuse[i] = false;
	}

	m_cddevice = machine().device<cdrom_image_device>(m_devname);
	if (m_cddevice)
	{
		m_cd = m_cddevice->get_cdrom_file();

		if (m_cd)
		{
//          printf("psxcd: found disc!\n");
			driver = open_mess_drv();
			driver->set_machine(machine());
			driver->set_cdrom_file(m_cd);
		}
		else
		{
			driver = NULL;
//          printf("psxcd: Found device, but no disc\n");
		}
	}
	else
	{
		driver = NULL;
//      printf("psxcd: Device [%s] not found!\n", m_devname);
	}

}

//
//
//

unsigned char psxcd_device::read_byte(const unsigned int addr)
{
	unsigned char ret = 0;

	switch (addr&3)
	{
		case 0: ret=sr; break;
		case 1:
			ret=res;
			if ((cur_res) && (rdp<cur_res->sz))
			{
				res=cur_res->data[rdp++];
				sr|=(1<<5);
			} else
			{
				if ((cur_res) && (cur_res->res&0x10))
				{
					global_free(cur_res);
					cur_res=NULL;
				}

				sr&=~(1<<5);
			}
			break;
		case 2: ret=0; break;
		case 3: ret=ir; break;
	}

	#ifdef debug_cdrom_registers
		printf("cdrom: read byte %08x = %02x (PC=%x)\n",addr,ret,cpu_get_pc(machine().device("maincpu")));
	#endif

	return ret;
}

//
//
//

void psxcd_device::write_byte(const unsigned int addr, const unsigned char byte)
{
	#ifdef debug_cdrom_registers
		printf("cdrom: write byte %08x = %02x (PC=%x)\n",addr,byte,cpu_get_pc(machine().device("maincpu")));
	#endif

	switch (addr&3)
	{
		case 0:
			cmdmode=byte&1;
			if (cmdmode==0)
			{
				cbp=cmdbuf;
			} else
			{
				if (! cur_res)
				{
					if (cur_res) global_free(cur_res);

					if (res_queue)
					{
						#ifdef debug_cdrom_registers
							printf("cdrom: nextres\n");
						#endif

						cur_res=res_queue;
						res_queue=res_queue->next;
						ir=cur_res->res&0xf;
						rdp=0;

						if (cur_res->sz)
						{
							res=cur_res->data[rdp++];
							sr|=(1<<5);
						} else
						{
							sr&=~(1<<5);
						}
					} else
					{
						//ir=0;
						cur_res=NULL;
					}
				}
				/*else
                {
                    if (rdp>=cur_res->sz)
                    {
                        sr&=~(1<<5);
                    } else
                    {
                        sr|=~(1<<5);
                        res=cur_res->data[rdp++];
                    }
                }
                */
			}
			break;

		case 1:
			if (cmdmode==0)
			{
				write_command(byte);
			}
			break;

		case 2:
			if (cmdmode==0)
			{
				*cbp++=byte;
			} else
			{
				// ?flush buffer?
			}
			break;

		case 3:
			if (byte==0x07)
			{
				if (cur_res)
				{
					global_free(cur_res);
					cur_res=NULL;
					sr&=~(1<<5);
				}
				ir=0;
			}
			break;
	}
}

psxcd_device::command_info psxcd_device::cmd_table[num_commands]=
{
	{ &psxcd_device::cdcmd_sync,				"sync"			},	// 00
	{ &psxcd_device::cdcmd_nop,				"nop"				},	// 01
	{ &psxcd_device::cdcmd_setloc,			"setloc"		},	// 02
	{ &psxcd_device::cdcmd_play,				"play"			},	// 03
	{ &psxcd_device::cdcmd_forward,		"forward"		},	// 04
	{ &psxcd_device::cdcmd_backward,		"backward"	},	// 05
	{ &psxcd_device::cdcmd_readn,			"readn"			},	// 06
	{ &psxcd_device::cdcmd_standby,		"standby"		},	// 07
	{ &psxcd_device::cdcmd_stop,				"stop"			},	// 08
	{ &psxcd_device::cdcmd_pause,			"pause"			},	// 09
	{ &psxcd_device::cdcmd_init,				"init"			},	// 0a
	{ &psxcd_device::cdcmd_mute,				"mute"			},	// 0b
	{ &psxcd_device::cdcmd_demute,			"demute"		},	// 0c
	{ &psxcd_device::cdcmd_setfilter,	"setfilter"	},	// 0d
	{ &psxcd_device::cdcmd_setmode,		"setmode"		},	// 0e
	{ &psxcd_device::cdcmd_getparam,		"getparam"	},	// 0f
	{ &psxcd_device::cdcmd_getlocl,		"getlocl"		},	// 10
	{ &psxcd_device::cdcmd_getlocp,		"getlocp"		},	// 11
	{ &psxcd_device::cdcmd_illegal,		"illegal"		},	// 12
	{ &psxcd_device::cdcmd_gettn,			"gettn"			},	// 13
	{ &psxcd_device::cdcmd_gettd,			"gettd"			},	// 14
	{ &psxcd_device::cdcmd_seekl,			"seekl"			},	// 15
	{ &psxcd_device::cdcmd_seekp,			"seekp"			},	// 16
	{ &psxcd_device::cdcmd_illegal,		"illegal"		},	// 17
	{ &psxcd_device::cdcmd_illegal,		"illegal"		},	// 18
	{ &psxcd_device::cdcmd_test,				"test"			},	// 19
	{ &psxcd_device::cdcmd_id,					"id"				},	// 1a
	{ &psxcd_device::cdcmd_reads,			"reads"			},	// 1b
	{ &psxcd_device::cdcmd_reset,			"reset"			},	// 1c
	{ &psxcd_device::cdcmd_illegal,		"illegal"		},	// 1d
	{ &psxcd_device::cdcmd_readtoc,		"readtoc"		},	// 1e
};

//
//
//

void psxcd_device::write_command(const unsigned char byte)
{
	assert(byte<num_commands);
	(this->*cmd_table[byte].func)();
}

//
//
//

void psxcd_device::cdcmd_sync()
{
	#ifdef debug_cdrom
		printf("cdrom: sync\n");
	#endif

	stop_read();
	send_result(intr_acknowledge);
}

static int open_nops=0;

void psxcd_device::cdcmd_nop()
{
	#ifdef debug_cdrom
		printf("cdrom: nop\n");
	#endif

	//stop_read();

	if ((! open) && (driver))
	{
		if (open_nops==0)
		{
			open=driver->read_toc();
		} else
		{
			open_nops--;
		}
	}

	send_result(intr_complete);
}

void psxcd_device::cdcmd_setloc()
{
	#ifdef debug_cdrom
		printf("cdrom: setloc %08x:%08x:%08x\n",
				cmdbuf[0],
				cmdbuf[1],
				cmdbuf[2]);
	#endif

	stop_read();

	unsigned char l[3];
	l[0]=bcd_to_decimal(cmdbuf[0]);
	l[1]=bcd_to_decimal(cmdbuf[1]);
	l[2]=bcd_to_decimal(cmdbuf[2]);

	if ((l[0]>0) || (l[1]>=2))
	{
		loc[0]=l[0];
		loc[1]=l[1];
		loc[2]=l[2];
	} else
	{
		printf("setloc out of range: %02d:%02d:%02d\n",
					 l[0],l[1],l[2]);
	}

	send_result(intr_complete);
}

void psxcd_device::cdcmd_play()
{
	#ifdef debug_cdrom
		printf("cdrom: play %02x %02x %02x => %d\n", loc[0], loc[1], loc[2], msf_to_sector(loc));
	#endif

	curpos[0]=loc[0];
	curpos[1]=loc[1];
	curpos[2]=loc[2];

	if ((curpos[0]==0) &&
			(curpos[1]==0) &&
			(curpos[2]==0))
	{
		send_result(intr_acknowledge);
	} else
	{
		stop_read();
		start_play();
		send_result(intr_acknowledge);
	}
}

void psxcd_device::cdcmd_forward()
{
	#ifdef debug_cdrom
		printf("cdrom: forward\n");
	#endif
}

void psxcd_device::cdcmd_backward()
{
	#ifdef debug_cdrom
		printf("cdrom: backward\n");
	#endif
}

void psxcd_device::cdcmd_readn()
{
	if (driver)
	{
		#ifdef debug_cdrom
			printf("cdrom: readn\n");
		#endif

		curpos[0]=loc[0];
		curpos[1]=loc[1];
		curpos[2]=loc[2];

		stop_read();
		start_read();
		send_result(intr_complete);
	} else
	{
		printf("read without driver\n");
		send_result(intr_diskerror);
	}
}

void psxcd_device::cdcmd_standby()
{
	#ifdef debug_cdrom
		printf("cdrom: standby\n");
	#endif

	stop_read();
	send_result(intr_acknowledge);
}

void psxcd_device::cdcmd_stop()
{
	#ifdef debug_cdrom
		printf("cdrom: stop\n");
	#endif

	stop_read();
	send_result(intr_acknowledge);
}

void psxcd_device::cdcmd_pause()
{
	#ifdef debug_cdrom
		printf("cdrom: pause\n");
	#endif

	stop_read();

	send_result(intr_acknowledge);
}

void psxcd_device::cdcmd_init()
{
	#ifdef debug_cdrom
		printf("cdrom: init\n");
	#endif

	stop_read();
	mode=0;
	sr|=0x10;

	send_result(intr_acknowledge);
}

void psxcd_device::cdcmd_mute()
{
	#ifdef debug_cdrom
		printf("cdrom: mute\n");
	#endif

	send_result(intr_acknowledge);
}

void psxcd_device::cdcmd_demute()
{
	#ifdef debug_cdrom
		printf("cdrom: demute\n");
	#endif

	send_result(intr_acknowledge);
}

void psxcd_device::cdcmd_setfilter()
{
	#ifdef debug_cdrom
		printf("cdrom: setfilter %08x,%08x\n",cmdbuf[0],cmdbuf[1]);
	#endif

	filter_file=cmdbuf[0];
	filter_channel=cmdbuf[1];

	send_result(intr_acknowledge);
}

void psxcd_device::cdcmd_setmode()
{
	#ifdef debug_cdrom
		printf("cdrom: setmode %08x\n",cmdbuf[0]);
	#endif

	mode=cmdbuf[0];

	switch ((mode&mode_size_mask)>>mode_size_shift)
	{
		case 1:
			secsize=2328;
			secskip=24;
			break;

		case 2:
			secsize=2340;
			secskip=12;
			break;

		default:
			secsize=2048;
			secskip=24;
			break;
	}

	send_result(intr_complete);
}

void psxcd_device::cdcmd_getparam()
{
	unsigned char data[6]=
	{
		status,
		mode,
		filter_file,
		filter_channel,
		0,
		0
	};

	#ifdef debug_cdrom
		printf("cdrom: getparam [%02x %02x %02x %02x %02x %02x]\n",
									data[0],
									data[1],
									data[2],
									data[3],
									data[4],
									data[5]);
	#endif

	send_result(intr_complete,data,6);
}

#if 0
static void add_loc(unsigned char *dst, const unsigned char *src1, const unsigned char *src2)
{
	int f=src1[2]+src2[2],
			s=src1[1]+src2[1],
			m=src1[0]+src2[0];
	while (f>=75) { s++; f-=75; }
	while (s>=60) { m++; s-=60; }

	dst[0]=m;
	dst[1]=s;
	dst[2]=f;
}
#endif
static void sub_loc(unsigned char *dst, const unsigned char *src1, const unsigned char *src2)
{
	int f=src1[2]-src2[2],
			s=src1[1]-src2[1],
			m=src1[0]-src2[0];
	while (f<0) { s--; f+=75; }
	while (s<0) { m--; s+=60; }

	if (m<0)
		m=s=f=0;

	dst[0]=m;
	dst[1]=s;
	dst[2]=f;
}

void psxcd_device::cdcmd_getlocl()
{
	#ifdef debug_cdrom
		printf("cdrom: getlocl\n");
	#endif

	#ifdef debug_cdrom
		printf("cdrom: getlocl [%02x %02x %02x %02x %02x %02x %02x %02x]\n",
									lastsechdr[0],
									lastsechdr[1],
									lastsechdr[2],
									lastsechdr[3],
									lastsechdr[4],
									lastsechdr[5],
									lastsechdr[6],
									lastsechdr[7]);
	#endif

	send_result(intr_complete,lastsechdr,8);
}

void psxcd_device::cdcmd_getlocp()
{
	unsigned char tloc[3],
								twosec[3]={ 0,2,0 };
	sub_loc(tloc,loc,twosec);

	unsigned char data[8]=
	{
		0x01,							// track
		0x01,							// index
		decimal_to_bcd(tloc[0]),	// min
		decimal_to_bcd(tloc[1]),	// sec
		decimal_to_bcd(tloc[2]),	// frame
		decimal_to_bcd(loc[0]),	// amin
		decimal_to_bcd(loc[1]),	// asec
		decimal_to_bcd(loc[2])	// aframe
	};

	//unsigned char data[8]={ 2,1,0,0xff,0xff,0xff,0xff,0xff };

	#ifdef debug_cdrom
		printf("cdrom: getlocp [%02x %02x %02x %02x %02x %02x %02x %02x]\n",
									data[0],
									data[1],
									data[2],
									data[3],
									data[4],
									data[5],
									data[6],
									data[7]);
	#endif

	send_result(intr_complete,data,8);
}

void psxcd_device::cdcmd_illegal()
{
	assert(0);
}

void psxcd_device::cdcmd_gettn()
{
	#ifdef debug_cdrom
		printf("cdrom: gettn\n");
	#endif

	assert(driver);

	unsigned char data[3]=
	{
		status,
		decimal_to_bcd(driver->get_first_track()),
		decimal_to_bcd(driver->get_num_tracks())
	};

	//stop_read();
	send_result(intr_acknowledge,data,3);
}

void psxcd_device::cdcmd_gettd()
{
	unsigned char addr[3];
	driver->get_track_address(bcd_to_decimal(cmdbuf[0]), addr);

	unsigned char data[3]=
	{
		status,
		decimal_to_bcd(addr[0]),
		decimal_to_bcd(addr[1])
	};

	#ifdef debug_cdrom
		printf("cdrom: gettd %02x [%02x %02x %02x]\n",
												 cmdbuf[0],
												 data[0],
												 data[1],
												 data[2]);
	#endif

	//stop_read();
	send_result(intr_acknowledge,data,3);
}

void psxcd_device::cdcmd_seekl()
{
	#ifdef debug_cdrom
		printf("cdrom: seekl [%02d:%02d:%02d]\n",loc[0],loc[1],loc[2]);
	#endif

	curpos[0]=loc[0];
	curpos[1]=loc[1];
	curpos[2]=loc[2];

	send_result(intr_acknowledge);
}

void psxcd_device::cdcmd_seekp()
{
	#ifdef debug_cdrom
		printf("cdrom: seekp\n");
	#endif

	curpos[0]=loc[0];
	curpos[1]=loc[1];
	curpos[2]=loc[2];

	send_result(intr_acknowledge);
}

void psxcd_device::cdcmd_test()
{
	#ifdef debug_cdrom
		printf("cdrom: test %08x\n",cmdbuf[0]);
	#endif

	static unsigned char data[4]=
	{
		0x95,
		0x07,
		0x06,
		0xff
	};

	send_result(intr_complete,data,4);
}

void psxcd_device::cdcmd_id()
{
	#ifdef debug_cdrom
		printf("cdrom: id\n");
	#endif

	if (! open)
	{
		if (driver)
			open=driver->read_toc();
	}

	if (open)
	{
		static unsigned char gamedata[8] = { 0x08, 0x00, 0x00, 0x00, 'S', 'C', 'E', 'A' };
		static unsigned char audiodata[8] = { 0x08, 0x90, 0x00, 0x00, 'S', 'C', 'E', 'A' };	// drops into the audio CD player.  08 80 goes to the menu.

		if (cdrom_get_track_type(m_cd, 0) == CD_TRACK_AUDIO)
		{
			send_result(intr_acknowledge,audiodata,8);
		}
		else
		{
			send_result(intr_acknowledge,gamedata,8);
		}
	} else
	{
		status=status_error|status_shellopen;
		send_result(intr_diskerror);
	}
}

void psxcd_device::cdcmd_reads()
{
	curpos[0]=loc[0];
	curpos[1]=loc[1];
	curpos[2]=loc[2];

	#ifdef skip_reads
		#ifdef debug_cdrom
			log("cdrom: reads [SKIPPING - RETURN COMPLETE]\n");
		#endif

		send_result(intr_complete);
	#else
		#ifdef debug_cdrom
			printf("cdrom: reads\n");
		#endif

		stop_read();
		start_streaming();
	#endif
}

void psxcd_device::cdcmd_reset()
{
	#ifdef debug_cdrom
		printf("cdrom: reset\n");
	#endif
}

void psxcd_device::cdcmd_readtoc()
{
	#ifdef debug_cdrom
		printf("cdrom: readtoc\n");
	#endif

	static unsigned char data[4]=
	{
		0x00,
		0x00,
		0xff,
		0xfe
	};

	send_result(intr_complete|0x10,data,2);
}

//
//
//

void psxcd_device::cmd_complete(command_result *res)
{
	bool doint=((res->res&0x10)==0);
	#ifdef debug_cdrom
	command_result *rf;
	#endif

	if (doint)
	{
		psx_irq_set(machine(), 0x0004);
	}

	add_result(res);

	#ifdef debug_cdrom
		if (doint)
		{
			printf("cdrom: irq [");
			for (rf=res_queue; ((rf) && (rf->next)); rf=rf->next);
				printf("%d ",rf->res);
			printf("]\n");
		}
	#endif
}

//
//
//

void psxcd_device::add_result(command_result *res)
{
	command_result *rf;

	if (res_queue)
	{
		for (rf=res_queue; rf->next; rf=rf->next);
		rf->next=res;
	} else
	{
		res_queue=res;
	}

	res->next=NULL;
}

//
//
//

event *psxcd_device::send_result(const unsigned int res,
														const unsigned char *data,
														const unsigned int sz,
															const unsigned int delay)
{
	// Update shell open status

	if (! open)
	{
		status=status_error|status_shellopen;
	}
	else
	{
		if (status&status_shellopen)
			status=status_standby;
	}

	// Prepare event

	event *ev=new event;
	ev->t=delay;
	ev->type=event_cmd_complete;

	command_result *cr=(command_result *)ev->data;

	cr->res=res;
	if (sz)
	{
		assert(sz<sizeof(cr->data));
		memcpy(cr->data,data,sz);
		cr->sz=sz;
	} else
	{
		cr->data[0]=status;
		cr->sz=1;
	}

	// Avoid returning results after sector read results -
	// delay the sector read slightly if necessary

	UINT64 systime = machine().device<cpu_device>("maincpu")->total_cycles();
	if ((next_read_event) && ((systime+ev->t)>(next_sector_t)))
	{
		UINT32 hz = m_sysclock / (delay + 2000);
		next_read_event->timer->adjust(attotime::from_hz(hz), 0, attotime::never);

		for (int i = 0; i < MAX_PSXCD_TIMERS; i++)
		{
			if (m_eventfortimer[i] == next_read_event)
			{
				printf("Adjusting timer %d to %d hz]n", i, hz);
			}
		}
	}

	add_system_event(ev);
	return ev;
}

//
//
//

void psxcd_device::start_dma(UINT8 *mainram, UINT32 size)
{
	if ((int)size>secleft)
	{
		printf("cdrom: dma past end of sector (secleft=%d sz=%d)\n",secleft,size);
	}

//  printf("cdrom: start dma %d bytes, %d remaining, secptr %p\n", size, secleft, secptr);
	#ifdef debug_cdrom
	if (size==12)
	{
		char poo[1024],*pp=poo;
		for (unsigned int i=0; i<12; i++)
		{
			sprintf(pp,"%02x ",secptr[i]);
			pp+=3;
		}
		printf("cdrom: data=%s\n",poo);
	}
	#endif

	memcpy(mainram, secptr, size);
	memset(secptr, 0xff, size);

	secptr += size;
	secleft -= size;

	if (secleft<0) secleft=0;
	if (secleft==0) sr&=~0x40;
}

//
//
//

void psxcd_device::prefetch_next_sector()
{
	unsigned int pos=msf_to_sector(curpos);
	assert(driver);
	driver->prefetch_sector(pos);
}

//
//
//

bool psxcd_device::read_next_sector(const bool block)
{
	unsigned int pos=msf_to_sector(curpos);
	unsigned char *buf=&secbuf[sechead*raw_sector_size];
	assert(driver);
	assert(secin<sector_buffer_size);

//  printf("read_next_sector: sec %d, sechead %d, raw_sector_size %d\n", pos, sechead, raw_sector_size);
	if (driver->read_sector(pos, buf, block))
	{
//      printf("buf contents = %02x %02x | %02x %02x\n", buf[0], buf[1], buf[0x20], buf[0x21]);

		sechead=(sechead+1)&(sector_buffer_size-1);
		secin++;

		memcpy(lastsechdr,&secbuf[raw_sector_size*sectail]+12,8);

		return true;
	} else
	{
		return false;
	}
}

//
//
//

void psxcd_device::read_sector()
{
	next_read_event=NULL;

	if (status & status_reading)
	{
		bool isend=false;

		if (read_next_sector(false))
		{
			unsigned char *rawsec;

			if ((mode&mode_adpcm) && (streaming))
			{
				rawsec=&secbuf[raw_sector_size*sectail];
				secptr=rawsec+24;
				secleft=2048;
			}
			else
			{
				rawsec=&secbuf[raw_sector_size*sectail];
				secptr=rawsec+secskip;
				secleft=secsize;
			}

			secin--;
			sectail=(sectail+1)&(sector_buffer_size-1);

			//

			bool isxa=false;

			subheader *sub=(subheader *)(rawsec+16);

			#ifdef dump_subheader
				printf("cdrom: subheader file=%02x chan=%02x submode=%02x coding=%02x [%02x%02x%02x%02x]\n",
						sub->file,
						sub->channel,
						sub->submode,
						sub->coding,
						rawsec[0xc],
						rawsec[0xd],
						rawsec[0xe],
						rawsec[0xf]);
			#endif

			status&=~status_playing;
			isxa=((mode&mode_adpcm) && (sub->submode&submode_audio));

			if (((mode&mode_channel)==0) ||
					((sub->file==filter_file) && (sub->channel==filter_channel)))
			{
				if (isxa)
				{
					if (sub->submode&submode_eof)
					{
						isend=true;
						//printf("end of file\n");
					}

					#ifndef disable_xa_prefetch
						unsigned int cursec=msf_to_sector(curpos);

						if (xa_prefetch_sector==-1)
						{
							xa_prefetch_sector=cursec;
							machine().device<spu_device>("spu")->flush_xa();
						}

						unsigned char *xaptr;
						unsigned int xasecsz;

						while (1)
						{
							int dist=xa_prefetch_sector-cursec;
							if (dist>max_xa_prefetch_distance) break;

							xaptr=driver->get_prefetch_sector(xa_prefetch_sector,&xasecsz);
							if (! xaptr) break;

							switch (xasecsz)
							{
								case 2336:
									break;

								default:
									xaptr+=16;
									break;
							}

							subheader *xasub=(subheader *)xaptr;

							if ((xasub->submode&submode_audio) &&
									(((mode&mode_channel)==0) ||
									 ((xasub->file==sub->file) &&
										(xasub->channel==sub->channel))))
							{
								if (! machine().device<spu_device>("spu")->play_xa(xa_prefetch_sector,xaptr))
									break;
							}
							xa_prefetch_sector++;
						}
					#else
						machine().device<spu_device>("spu")->play_xa(0,rawsec+16);
					#endif

					status|=status_playing;
				}
			}

			if ((mode&mode_autopause)==0)
				isend=false;

			//

			curpos[2]++;
			if (curpos[2]==75)
			{
				curpos[2]=0;
				curpos[1]++;
				if (curpos[1]==60)
				{
					curpos[1]=0;
					curpos[0]++;
				}
			}

			loc[0]=curpos[0];
			loc[1]=curpos[1];
			loc[2]=curpos[2];

			//

			command_result *res=new command_result;
			res->res=isend?intr_dataend:intr_dataready;
			res->data[0]=status;
			res->sz=1;

			sr|=0x40;

			if ((streaming) && (isxa))
			{
				global_free(res);
				res=NULL;
			}

			if (res)
			{
				#ifdef debug_cdrom
					printf("cdrom:: data ready\n");
				#endif

				cmd_complete(res);
			}
		}

		if (! isend)
		{
			unsigned int cyc=read_sector_cycles;
			if (mode&mode_double_speed) cyc>>=1;

			next_sector_t+=cyc;

			event *ev=new event;
			ev->t=cyc;
			ev->type=event_read_sector;
			next_read_event=ev;
			add_system_event(ev);

			//read_next_sector();
		} else
		{
			printf("autopause xa\n");
			stop_read();
		}
	}
}

//
//
//

bool psxcd_device::play_cdda_sector(const unsigned int sector,
																 unsigned char *rawsec)
{
	bool isdata=true;

	if (rawsec[0]!=0)
	{
		isdata=false;
	} else
	{
		for (int i=0; i<10; i++)
			if (rawsec[i+1]!=0xff)
			{
				isdata=false;
				break;
			}
	}

	if (! isdata)
	{
		return machine().device<spu_device>("spu")->play_cdda(sector,rawsec);
	} else
	{
		return true;
	}
}

//
//
//

void psxcd_device::play_sector()
{
	next_read_event=NULL;

	if (status&status_playing)
	{
		#ifdef disable_cdda_prefetch
		unsigned char *rawsec=&secbuf[raw_sector_size*sectail];
		#endif
		secin--;
		sectail=(sectail+1)&(sector_buffer_size-1);

		//

		#ifndef disable_cdda_prefetch
			unsigned int cursec=msf_to_sector(curpos);

			if (cdda_prefetch_sector==-1)
			{
				cdda_prefetch_sector=cursec;
				machine().device<spu_device>("spu")->flush_cdda();
			}

			unsigned char *cddaptr;
//          bool full=false;
			unsigned int cddasecsz;

			while (1)
			{
				int dist=cdda_prefetch_sector-cursec;
				if (dist>max_cdda_prefetch_distance) break;

				cddaptr=driver->get_prefetch_sector(cdda_prefetch_sector,&cddasecsz);
				if (! cddaptr) break;

				if (! play_cdda_sector(cdda_prefetch_sector,cddaptr))
					break;

				cdda_prefetch_sector++;
			}
		#else
			play_cdda_sector(0,rawsec);
		#endif

		//

		curpos[2]++;
		if (curpos[2]==75)
		{
			curpos[2]=0;
			curpos[1]++;
			if (curpos[1]==60)
			{
				curpos[1]=0;
				curpos[0]++;
			}
		}

		loc[0]=curpos[0];
		loc[1]=curpos[1];
		loc[2]=curpos[2];

		//

		if (mode&mode_autopause)
		{
			if (msf_to_sector(loc)>=autopause_sector)
			{
				printf("autopause cdda\n");

				command_result *res=new command_result;
				res->res=intr_dataend;
				res->data[0]=status_standby;
				res->sz=1;
				cmd_complete(res);
				stop_read();
				return;
			}
		}

		if (mode&mode_report)
		{
			command_result *res=new command_result;
			res->res=intr_complete;	//dataready;

			res->data[0]=status_playing|status_standby;
			res->data[1]=0x01;
			res->data[2]=0x80;
			res->data[3]=decimal_to_bcd(loc[0]);
			res->data[4]=decimal_to_bcd(loc[1])|0x80;
			res->data[5]=decimal_to_bcd(loc[2]);

			res->sz=8;

			cmd_complete(res);
		}

		unsigned int cyc=read_sector_cycles;

		event *ev=new event;
		ev->t=next_sector_t - machine().device<cpu_device>("maincpu")->total_cycles();
		ev->type=event_play_sector;

		next_sector_t+=cyc;

		next_read_event=ev;
		add_system_event(ev);

		read_next_sector();
	}
}


//
//
//

void psxcd_device::preread_sector()
{
	next_read_event=NULL;

	//

	event *ev=new event;

	unsigned int pos=msf_to_sector(curpos);
	unsigned char *buf=&secbuf[sechead*raw_sector_size];
	if (! driver->read_sector(pos,buf,false))
	{
		ev->t=(m_sysclock/60);
		ev->type=event_preread_sector;

		unsigned int cyc=read_sector_cycles;
		if (mode&mode_double_speed) cyc>>=1;
		next_sector_t=ev->t+(cyc-preread_delay)+machine().device<cpu_device>("maincpu")->total_cycles();
	} else
	{
		memcpy(lastsechdr,buf+12,8);

		//

		command_result *res=new command_result;
		res->res=intr_complete;
		res->data[0]=status;
		res->sz=1;

		#ifdef debug_cdrom
			printf("cdrom: read acknowledge\n");
		#endif

		cmd_complete(res);

		//

		ev->t=next_sector_t - machine().device<cpu_device>("maincpu")->total_cycles();
		ev->type=event_read_sector;

		//read_next_sector();
	}

	next_read_event=ev;
	add_system_event(ev);
}

//
//
//

void psxcd_device::start_read()
{
	#ifdef debug_cdrom
		printf("cdrom: start read\n");
	#endif

	assert((status&(status_reading|status_playing))==0);

	status|=status_reading;

	secin=sechead=sectail=0;
	xa_prefetch_sector=-1;

	unsigned int cyc=read_sector_cycles;
	if (mode&mode_double_speed) cyc>>=1;

	INT64 systime=machine().device<cpu_device>("maincpu")->total_cycles();

	systime+=start_read_delay;

	event *ev=new event;
	ev->t=start_read_delay+preread_delay;
	ev->type=event_preread_sector;

	next_sector_t=systime+cyc;
	next_read_event=ev;
	add_system_event(ev);

	prefetch_next_sector();
}

//
//
//

void psxcd_device::start_streaming()
{
	assert(! streaming);

	streaming=true;
	start_read();
}

//
//
//

void psxcd_device::start_play()
{
	#ifdef debug_cdrom
		printf("cdrom: start play\n");
	#endif

	status|=status_playing;

	secin=sechead=sectail=0;
	cdda_prefetch_sector=-1;

	if (mode&mode_autopause)
	{
		unsigned int pos=msf_to_sector(curpos);
		driver->find_track(pos+150,NULL,&autopause_sector);
//      printf("pos=%d auto=%d\n",pos,autopause_sector);
	}

	unsigned int cyc=read_sector_cycles;

	next_sector_t=machine().device<cpu_device>("maincpu")->total_cycles()+cyc;

	event *ev=new event;
	ev->t=next_sector_t - machine().device<cpu_device>("maincpu")->total_cycles();
	ev->type=event_play_sector;

	next_sector_t+=cyc;

	next_read_event=ev;
	add_system_event(ev);

	read_next_sector();
}

//
//
//

void psxcd_device::stop_read()
{
	#ifdef debug_cdrom
		if (status&status_reading)
			printf("cdrom: stop read\n");
	#endif
	status&=~(status_reading|status_playing);
	streaming=false;

	if (next_read_event)
	{
		next_read_event->timer->adjust(attotime::never, 0, attotime::never);
		for (int i = 0; i < MAX_PSXCD_TIMERS; i++)
		{
			if (m_timers[i] == next_read_event->timer)
			{
				m_timerinuse[i] = false;
				m_eventfortimer[i] = false;
				break;
			}
		}
		global_free(next_read_event);
		next_read_event=NULL;
	}

	unsigned int sector=msf_to_sector(curpos);
	machine().device<spu_device>("spu")->flush_xa(sector);
	machine().device<spu_device>("spu")->flush_cdda(sector);
}

//
//
//

void psxcd_device::set_driver(cdrom_driver *d)
{
	char err[1024];

	if (d)
	{
		if (d->is_usable(err,1024))
		{
			driver=d;
			open=false;

			if (! first_open)
			{
				command_result *res=new command_result;
				res->res=intr_acknowledge;
				res->data[0]=status_standby;
				res->sz=1;

				cmd_complete(res);

				open_nops=10;
			}

			first_open=false;
		}
	} else
	{
		driver=NULL;
		open=false;

		command_result *res=new command_result;
		res->res=intr_acknowledge;
		res->data[0]=status_shellopen;
		res->sz=1;

		cmd_complete(res);
	}
}

//
//
//
void psxcd_device::device_timer(emu_timer &timer, device_timer_id tid, int param, void *ptr)
{
	event *ev = m_eventfortimer[tid];

//  printf("timer %d fired, performing event type %d\n", tid, ev->type);

	switch (ev->type)
	{
		case event_cmd_complete:
		{
			#ifdef debug_cdrom
				printf("cdrom:: event cmd complete\n");
			#endif

			command_result *res=new command_result;
			memcpy(res,ev->data,sizeof(command_result));
			cmd_complete(res);
			break;
		}

		case event_preread_sector:
			preread_sector();
			break;

		case event_read_sector:
			read_sector();
			break;

		case event_play_sector:
			play_sector();
			break;
	}

	// free the timer
//  printf("Freeing timer %d\n", tid);
	m_timers[tid]->adjust(attotime::never, 0, attotime::never);
	m_timerinuse[tid] = false;
	m_eventfortimer[tid] = NULL;

	global_free(ev);
}

void psxcd_device::add_system_event(event *ev)
{
	emu_timer *timer = NULL;
	int tnum = -1;

	// allocate a timer for this event
	for (int i = 0; i < MAX_PSXCD_TIMERS; i++)
	{
		if (!m_timerinuse[i])
		{
			tnum = i;
			timer = m_timers[i];
			m_timerinuse[i] = true;
			m_eventfortimer[i] = ev;
			break;
		}
	}

	if (tnum == -1)
	{
		fatalerror("PSXCD: ran out of timers!\n");
	}

	// ev->t is in maincpu clock cycles
	UINT32 hz = m_sysclock / ev->t;
//  printf("add_system_event: event type %d for %d hz (using timer %d)\n", ev->type, hz, tnum);
	timer->adjust(attotime::from_hz(hz), tnum, attotime::never);

	// back-reference the timer from the event
	ev->timer = timer;
}

