#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <scsi.h>
#include <Directory.h>
#include <Entry.h>
#include <Path.h>

#include "quakedef.h"

static int	cd_fd = -1;
static char cd_devname[200];
static byte	remap[100];
static bool initialized = false;
static bool enabled = true;
static bool cdValid = false;
static bool playing = false;
static bool wasPlaying = false;
static bool playLooping = false;
static float	cdvolume;
static byte	maxTrack;
static byte playTrack;


static void CDAudio_Eject(void)
{
	status_t res;
	if( cd_fd < 0 ) return;

	res = ioctl(cd_fd, B_SCSI_EJECT);
	if (res != B_OK)
		Con_DPrintf("ioctl cdromeject failed\n");
}

static int CDAudio_GetAudioDiskInfo(void)
{
	scsi_toc toc;
	status_t media_status	= B_DEV_NO_MEDIA;
	scsi_position pos;

	status_t result;
	
	result = ioctl(cd_fd, B_SCSI_GET_TOC, &toc);

	if (result != B_OK)
		return -1;

	ioctl(cd_fd, B_GET_MEDIA_STATUS, &media_status, sizeof(media_status));
	if (media_status != B_OK)
		return -1;

	result = ioctl(cd_fd, B_SCSI_GET_POSITION, &pos);

	if (result != B_NO_ERROR)
		return -1;
	else if ((!pos.position[1]) || (pos.position[1] >= 0x13) ||
	   ((pos.position[1] == 0x12) && (!pos.position[6])))
	{
		playing = false;
		wasPlaying = false;
	}
	else if (pos.position[1] == 0x11)
	{
		playing = true;
	}
	else
	{
		playing = false;
		wasPlaying = true;
	}

	cdValid = true;
	maxTrack = toc.toc_data[3];
	return 0;
}

void CDAudio_Play(byte track, qboolean looping)
{
	scsi_play_track	sc_track;
	status_t		res;

	if(cd_fd < 0 || !enabled)
		return;

	if(!cdValid)
	{
		CDAudio_GetAudioDiskInfo();
		if(!cdValid)
			return;
	}

	track = remap[track];

	if( (track < 1) || (track > maxTrack) )
	{
		Con_DPrintf("CDAudio: Bad track number %u.\n", track);
		return;
	}

	if(playing)
	{
		if( playTrack == track )
			return;
		CDAudio_Stop();
	}

	sc_track.start_track = track;
	sc_track.start_index = 1;
	sc_track.end_track = maxTrack;
	sc_track.end_index = 1;

	res = ioctl(cd_fd, B_SCSI_PLAY_TRACK, &sc_track);
	if (res != B_OK)
	{
		Con_DPrintf("CDAudio: error playing track\n");
		return;
	}
	playLooping = looping;
	playTrack = track;
	playing = true;
	if(cdvolume == 0.0)
		CDAudio_Pause();
}


void CDAudio_Stop(void)
{
	status_t res;

	if( (cd_fd < 0) || !enabled)
		return;

	if( !playing )
		return;

	res = ioctl(cd_fd, B_SCSI_STOP_AUDIO);
	if( res != B_OK )
		Con_DPrintf("ioctl SCSI_STOP_AUDIO failed\n");

	wasPlaying = false;
	playing = false;
}

void CDAudio_Pause(void)
{
	if(!enabled || !playing)
		return;

	status_t result = ioctl(cd_fd, B_SCSI_PAUSE_AUDIO);
	if (result != B_OK)
	{
		Con_DPrintf("CDAudio: ioctl SCSI_PAUSE_AUDIO failed\n");
		return;
	}
	wasPlaying = true;
	playing = false;
}

void CDAudio_Resume(void)
{
	if(!enabled || !cdValid || !wasPlaying)
		return;

	status_t result = ioctl(cd_fd, B_SCSI_RESUME_AUDIO);
	if (result != B_OK) {
		Con_DPrintf("CDAudio: ioctl SCSI_RESUME_AUDIO failed\n");
		return;
	}
	playing = true;
}

static void CD_f (void)
{
	char	*command;
	int		ret;
	int		n;

	if (Cmd_Argc() < 2)
		return;

	command = Cmd_Argv (1);

	if (Q_strcasecmp(command, "on") == 0)
	{
		enabled = true;
		return;
	}

	if (Q_strcasecmp(command, "off") == 0)
	{
		if (playing)
			CDAudio_Stop();
		enabled = false;
		return;
	}

	if (Q_strcasecmp(command, "reset") == 0)
	{
		enabled = true;
		if (playing)
			CDAudio_Stop();
		for (n = 0; n < 100; n++)
			remap[n] = n;
		CDAudio_GetAudioDiskInfo();
		return;
	}

	if (Q_strcasecmp(command, "remap") == 0)
	{
		ret = Cmd_Argc() - 2;
		if (ret <= 0)
		{
			for (n = 1; n < 100; n++)
				if (remap[n] != n)
					Con_Printf("  %u -> %u\n", n, remap[n]);
			return;
		}
		for (n = 1; n <= ret; n++)
			remap[n] = Q_atoi(Cmd_Argv (n+1));
		return;
	}

	if (Q_strcasecmp(command, "close") == 0)
	{
//		close cd tray();	
		return;
	}

	if (!cdValid)
	{
		CDAudio_GetAudioDiskInfo();
		if (!cdValid)
		{
			Con_Printf("No CD in player.\n");
			return;
		}
	}

	if (Q_strcasecmp(command, "play") == 0)
	{
		CDAudio_Play((byte)Q_atoi(Cmd_Argv (2)), qfalse);
		return;
	}

	if (Q_strcasecmp(command, "loop") == 0)
	{
		CDAudio_Play((byte)Q_atoi(Cmd_Argv (2)), qtrue);
		return;
	}

	if (Q_strcasecmp(command, "stop") == 0)
	{
		CDAudio_Stop();
		return;
	}

	if (Q_strcasecmp(command, "pause") == 0)
	{
		CDAudio_Pause();
		return;
	}

	if (Q_strcasecmp(command, "resume") == 0)
	{
		CDAudio_Resume();
		return;
	}

	if (Q_strcasecmp(command, "eject") == 0)
	{
		if (playing)
			CDAudio_Stop();
		CDAudio_Eject();
		cdValid = false;
		return;
	}

	if (Q_strcasecmp(command, "info") == 0)
	{
		Con_Printf("%u tracks\n", maxTrack);
		if (playing)
			Con_Printf("Currently %s track %u\n", playLooping ? "looping" : "playing", playTrack);
		else if (wasPlaying)
			Con_Printf("Paused %s track %u\n", playLooping ? "looping" : "playing", playTrack);
		Con_Printf("Volume is %f\n", cdvolume);
		return;
	}
}

void CDAudio_Update(void)
{
	static time_t lastchk;

	if(!enabled)
		return;

	if(bgmvolume.value != cdvolume)
	{
		if (cdvolume)
		{
			Cvar_SetValue ("bgmvolume", 0.0);
			cdvolume = bgmvolume.value;
			CDAudio_Pause ();
		}
		else
		{
			Cvar_SetValue ("bgmvolume", 1.0);
			cdvolume = bgmvolume.value;
			CDAudio_Resume ();
		}
	}

	if (playing && lastchk < time(NULL))
	{
		lastchk = time(NULL) + 2; //two seconds between chks

//		subchnl.cdsc_format = CDROM_MSF;
//		if (ioctl(cdfile, CDROMSUBCHNL, &subchnl) == -1 ) {
//			Con_DPrintf("ioctl cdromsubchnl failed\n");
//			playing = false;
//			return;
//		}
//		if (subchnl.cdsc_audiostatus != CDROM_AUDIO_PLAY &&
//			subchnl.cdsc_audiostatus != CDROM_AUDIO_PAUSED) {
//			playing = false;
//			if (playLooping)
//				CDAudio_Play(playTrack, true);
//		}
	}


}

bool is_cdrom(int dev_fd)
{
	device_geometry	g;
	if(ioctl(dev_fd, B_GET_GEOMETRY, &g, sizeof(g)) >= 0)
	{
		if(B_CD == g.device_type)
			return true;
	}
	return false;
}

int find_cdrom(const char *directory)
{ 
	BDirectory	dir; 
	BEntry		ent; 

	dir.SetTo(directory); 
	if( dir.InitCheck() != B_OK )
		return false; 

	dir.Rewind(); 
	while( dir.GetNextEntry(&ent) >= 0 )
	{ 
		BPath		path; 
		const char *name; 
		entry_ref	ref; 
		int			dev_fd;
		
		if( ent.GetPath(&path) != B_OK ) continue;
		name = path.Path();
		if( ent.GetRef(&ref) != B_OK ) continue; 
		if( ent.IsDirectory() )
		{ 
			if(!strcmp(ref.name, "floppy")) continue;
			dev_fd = find_cdrom(name);
			if( dev_fd >= 0 )
				return dev_fd;
		}
		else
		{ 
			if(strcmp(ref.name, "raw") != 0) continue;
			dev_fd = open(name, O_RDONLY); 
			if(dev_fd < 0) continue; 
			if(is_cdrom(dev_fd))
			{
				return dev_fd;
			}
			close(dev_fd);
		} 
	}
	return -1;
}

int CDAudio_Init(void)
{
	int i;
	if( COM_CheckParm("-nocdaudio") ) return -1;

	for(i=0;i<100;i++)
		remap[i] = i;

	if( (i = COM_CheckParm("-cddev"))!=0 && i < com_argc-1)
	{
		strncpy(cd_devname, com_argv[i+1], sizeof(cd_devname));
		cd_devname[sizeof(cd_devname)-1]=0;
		cd_fd = open(cd_devname, O_RDONLY);
		if(cd_fd < 0)
			return -1;
		if(!is_cdrom(cd_fd))
		{
			close(cd_fd);
			cd_fd = -1;
			return -1;
		}
	}else{
		cd_fd = find_cdrom("/dev/disk");
		if(cd_fd < 0)
			return -1;
	}

	initialized = true;
	enabled = true;

	if(CDAudio_GetAudioDiskInfo())
	{
		Con_Printf("CDAudio_Init: No CD in player.\n");
		cdValid = false;
	}

	Cmd_AddCommand("cd", CD_f);
	Con_Printf("CD Audio Initialised\n");
	return 0;
}

void CDAudio_Shutdown(void)
{
	if(!initialized)
		return;
	CDAudio_Stop();
	close(cd_fd);
	cd_fd = -1;
}
