/*
    2004.02.01
        first released source code for IOMP
*/
#define load() /* nothing */

void strmcpy (char **t, char *s);

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/ustat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <string.h>
#ifdef linux
#include <linux/cdrom.h>
#else
#include <sundev/srreg.h>
#endif /* linux */
#include "struct.h"
#include <sys/times.h>


//void *malloc();
//char *strchr();

extern struct play *playlist;
extern struct cdinfo thiscd, *cd;

static int start_play_time_tick = 0; /* 1/100 second */
static int start_playing_pos = 0; /* 1/75 second */
static int pause_status = 0; /* not pause */
static int pause_time_tick = 0;

int	cd_fd = -1;

/*
 * The minimum volume setting for the Sun and DEC CD-ROMs is 128 but for other
 * machines this might not be the case.
 */
int	min_volume = 128;
int	max_volume = 255;

char    *cd_device;

int cur_track, cur_index, cur_lasttrack, cur_firsttrack, cur_pos_abs,	
	cur_frame, cur_pos_rel, cur_tracklen, cur_cdlen, cur_ntracks,	
	cur_nsections, cur_cdmode, cur_listno, cur_stopmode, exit_on_eject,
	cur_balance;
 char *cur_artist, *cur_cdname, *cur_trackname;
char	cur_contd, cur_avoid;
/*
 * read_toc()
 *
 * Read the table of contents from the CD.  Return a pointer to a cdinfo
 * struct containing the relevant information (minus artist/cdname/etc.)
 * This is a static struct.  Returns NULL if there was an error.
 *
 * XXX allocates one trackinfo too many.
 */

struct cdinfo *
read_toc()
{
	struct playlist		*l;
	struct cdrom_tochdr	hdr;
	struct cdrom_tocentry	entry;
	int			i, pos;

	if (cd_fd < 0)
		return(NULL);

	if (ioctl(cd_fd, CDROMREADTOCHDR, &hdr))
	{
		perror("readtochdr");
		return (NULL);
	}

	thiscd.artist[0] = thiscd.cdname[0] = '\0';
	thiscd.whichdb = thiscd.otherrc = thiscd.otherdb = NULL;
	thiscd.length = 0;
	thiscd.autoplay = thiscd.playmode = thiscd.volume = 0;
	thiscd.ntracks = hdr.cdth_trk1;

	if (thiscd.lists != NULL)
	{
		for (l = thiscd.lists; l->name != NULL; l++)
		{
			free(l->name);
			free(l->list);
		}
		free(thiscd.lists);
		thiscd.lists = NULL;
	}

	if (thiscd.trk != NULL)
		free(thiscd.trk);

	thiscd.trk = malloc((thiscd.ntracks + 1) * sizeof(struct trackinfo));
	if (thiscd.trk == NULL)
	{
		perror("malloc");
		return (NULL);
	}
	for (i = 0; i <= thiscd.ntracks; i++)
	{
		if (i == thiscd.ntracks)
			entry.cdte_track = CDROM_LEADOUT;
		else
			entry.cdte_track = i + 1;
		entry.cdte_format = CDROM_MSF;
		if (ioctl(cd_fd, CDROMREADTOCENTRY, &entry))
		{
			perror("tocentry read");
			return (NULL);
		}

		thiscd.trk[i].data =
		thiscd.trk[i].avoid = entry.cdte_ctrl & CDROM_DATA_TRACK ?
			1 : 0;
		thiscd.trk[i].length = entry.cdte_addr.msf.minute * 60 +
				entry.cdte_addr.msf.second;
		thiscd.trk[i].start = thiscd.trk[i].length * 75 +
				entry.cdte_addr.msf.frame;
		thiscd.trk[i].songname = thiscd.trk[i].otherrc =
		thiscd.trk[i].otherdb = NULL;
		thiscd.trk[i].contd = 0;
		thiscd.trk[i].volume = 0;
		thiscd.trk[i].track = i + 1;
		thiscd.trk[i].section = 0;
	}

/* Now compute actual track lengths. */
	pos = thiscd.trk[0].length;

	for (i = 0; i < thiscd.ntracks; i++)
	{
		thiscd.trk[i].length = thiscd.trk[i+1].length - pos;
		pos = thiscd.trk[i+1].length;
		if (thiscd.trk[i].data)
			thiscd.trk[i].length = (thiscd.trk[i + 1].start -
				thiscd.trk[i].start) * 2;
		if (thiscd.trk[i].avoid)
			strmcpy(&thiscd.trk[i].songname, "DATA TRACK");
	}

	thiscd.length = thiscd.trk[thiscd.ntracks].length;

	return (&thiscd);
}

/*
 * cd_status()
 *
 * Return values:
 *
 *	0	No CD in drive.
 *	1	CD in drive.
 *	2	CD has just been inserted (TOC has been read)
 *	3	CD has just been removed
 *
 * Updates cur_track, cur_pos_rel, cur_pos_abs and other variables.
 */
int
cd_status()
{
	char				realname[MAXPATHLEN];
	static int			warned = 0;
	struct cdrom_subchnl		sc;

	int				ret = 1;

	if (cd_fd < 0)
	{

		if ((cd_fd = open(cd_device, 0)) < 0)
		{

			if (errno == EACCES)
			{
				if (!warned)
				{
					strcpy(realname, cd_device);

					fprintf(stderr,
		"As root, please run\n\nchmod 666 %s\n\n%s\n", realname,
		"to give yourself permission to access the CD-ROM device.");
					warned++;
				}
			}
			else if (errno != ENXIO)
			{
				perror(cd_device);
				if (thiscd.trk != NULL)
					free(thiscd.trk);
				exit(1);
			}
			return (0);
		}
		cur_cdmode = 5;
	}

	sc.cdsc_format = CDROM_MSF;

	if (ioctl(cd_fd, CDROMSUBCHNL, &sc))
	{
		cur_cdmode = 5;
		cur_track = -1;
		cur_cdlen = cur_tracklen = 1;
		cur_pos_abs = cur_pos_rel = cur_frame = 0;

		if (exit_on_eject)
			exit(0);

		return (0);
	}

	if (cur_cdmode == 5)	/* CD has been ejected */
	{
		cur_pos_rel = cur_pos_abs = cur_frame = 0;

		if ((cd = read_toc()) == NULL) 
		{
			close(cd_fd);
			cd_fd = -1;
			if (exit_on_eject)
				exit(0);
			else
				return (0);
		}
		cur_nsections = 0;
		cur_ntracks = cd->ntracks;
		cur_cdlen = cd->length;
		load();
		cur_artist = cd->artist;
		cur_cdname = cd->cdname;
		cur_cdmode = 4;
		ret = 2;
	}

	switch (sc.cdsc_audiostatus) {
	case CDROM_AUDIO_PLAY:
		cur_cdmode = 1;
dopos:
		cur_pos_abs = sc.cdsc_absaddr.msf.minute * 60 +
			sc.cdsc_absaddr.msf.second;
		cur_frame = cur_pos_abs * 75 + sc.cdsc_absaddr.msf.frame;

		/* Only look up the current track number if necessary. */
		if (cur_track < 1 || cur_frame < cd->trk[cur_track-1].start ||
				cur_frame >= (cur_track >= cur_ntracks ?
				(cur_cdlen + 1) * 75 :
				cd->trk[cur_track].start))
		{
			cur_track = 0;
			while (cur_track < cur_ntracks && cur_frame >=
					cd->trk[cur_track].start)
				cur_track++;
		}
		if (cur_track >= 1 && sc.cdsc_trk > cd->trk[cur_track-1].track)
			cur_track++;

		cur_index = sc.cdsc_ind;
doall:
		if (cur_track >= 1 && cur_track <= cur_ntracks)
		{
			cur_trackname = cd->trk[cur_track-1].songname;
			cur_avoid = cd->trk[cur_track-1].avoid;
			cur_contd = cd->trk[cur_track-1].contd;
			cur_pos_rel = (cur_frame -
				cd->trk[cur_track-1].start) / 75;
			if (cur_pos_rel < 0)
				cur_pos_rel = -cur_pos_rel;
		}

		/* note: workbone requires playlist == NULL always! */		
		if (playlist != NULL && playlist[0].start)
		{
			cur_pos_abs -= cd->trk[playlist[cur_listno-1].
				start - 1].start / 75;
			cur_pos_abs += playlist[cur_listno-1].starttime;
		}
		if (cur_pos_abs < 0)
			cur_pos_abs = cur_frame = 0;

		if (cur_track < 1)
			cur_tracklen = cd->length;
		else
			cur_tracklen = cd->trk[cur_track-1].length;
		break;

	case CDROM_AUDIO_PAUSED:
		if (cur_cdmode == 1 || cur_cdmode == 3)
		{
			cur_cdmode = 3;
			goto dopos;
		}
		else
			cur_cdmode = 4;
		goto doall;

	case CDROM_AUDIO_COMPLETED:
		cur_cdmode = 0;		/* waiting for next track. */
		break;

	case CDROM_AUDIO_NO_STATUS:
		cur_cdmode = 4;
		cur_lasttrack = cur_firsttrack = -1;
		goto doall;
	}
	return (ret);
}

int cd_check(void)
{
struct cdrom_subchnl cs;

if(ioctl(cd_fd,CDROMSUBCHNL,&cs)){return(-1);}
return cs.cdsc_audiostatus;
}


/*
 * pause_cd()
 *
 * Pause the CD, if it's in play mode.  If it's already paused, go back to
 * play mode.
 */
void
pause_cd()
{
	if (cd_fd < 0)	/* do nothing if there's no CD! */
		return;

	ioctl(cd_fd, CDROMPAUSE);

	if (pause_status==0) {
		pause_status = 1;
		pause_time_tick = times(NULL);
	}

}

void resume_cd()
{
	if (cd_fd < 0)  /* do nothing if there's no CD! */
		return;

	ioctl(cd_fd, CDROMRESUME);

	if (pause_status==1) {
		start_play_time_tick += (times(NULL) - pause_time_tick);
		pause_status = 0;
		pause_time_tick = 0;
	}
}



/*
 * stop_cd()
 *
 * Stop the CD if it's not already stopped.
 */
void
stop_cd()
{
	if (cd_fd < 0)
		return;

	cur_lasttrack = cur_firsttrack = -1;
	cur_cdmode = 4;
	ioctl(cd_fd, CDROMSTOP);
	cur_track = 1;
	
	if(ioctl(cd_fd, CDROM_LOCKDOOR, 0) < 0)
    	perror("cdrom tray unlock");
    	
	close(cd_fd);
	cd_fd = -1;

#if 0
	pause_status = 1;
	start_play_time_tick = 0;
	start_playing_pos = 0;
	pause_time_tick = 0;
#endif
}

/*
 * play_chunk(start, end)
 *
 * Play the CD from one position to another (both in frames.)
 */
void
play_chunk(start, end)
	int start, end;
{
	struct cdrom_msf		msf;
	struct cdrom_volctrl	vol;

	if (cd == NULL || cd_fd < 0)
		return;

	end--;
	if (start >= end)
		start = end-1;

	msf.cdmsf_min0 = start / (60*75);
	msf.cdmsf_sec0 = (start % (60*75)) / 75;
	msf.cdmsf_frame0 = start % 75;
	msf.cdmsf_min1 = end / (60*75);
	msf.cdmsf_sec1 = (end % (60*75)) / 75;
	msf.cdmsf_frame1 = end % 75;

	vol.channel0 = 180;
	vol.channel1 = 180;
	vol.channel2 = 180;
	vol.channel3 = 180;

	if (ioctl(cd_fd, CDROMVOLCTRL, &vol)) {
		perror("CDROMVOLCTRL");
		return;
	}

	if (ioctl(cd_fd, CDROMSTART))
	{
		perror("CDROMSTART");
		return;
	}

	if (ioctl(cd_fd, CDROMPLAYMSF, &msf))
	{
		printf("play(%d,%d)\n",start,end);
		printf("msf = %d:%d:%d %d:%d:%d\n",
			msf.cdmsf_min0, msf.cdmsf_sec0, msf.cdmsf_frame0,
			msf.cdmsf_min1, msf.cdmsf_sec1, msf.cdmsf_frame1);
		perror("CDROMPLAYMSF");
		return;
	}
}

/*
 * play_cd(starttrack, pos, endtrack)
 *
 * Start playing the CD or jump to a new position.  "pos" is in seconds,
 * relative to start of track.
 */

void
play_cd(start, pos, end)
int start, pos, end;
{
	cd_status();

	if (cd == NULL || cd_fd < 0)
		return;

	cur_firsttrack = start;
	start--;
	end--;
	cur_lasttrack = end;

	play_chunk(cd->trk[start].start + pos * 75, end >= cur_ntracks ?
		cur_cdlen * 75 : cd->trk[end].start - 1);

	start_play_time_tick = times(NULL);
	start_playing_pos = cd->trk[start].start + pos * 75;
	pause_status = 0;
	pause_time_tick = 0;

}

static int 
get_current_playing_tick()
{ 
	if (cur_cdmode == 1||cur_cdmode==3) { /* 1==>playing 3==>pause */
		if (pause_status==1) {
			return pause_time_tick-start_play_time_tick;
		} else {
			return times(NULL)-start_play_time_tick;
		} 
	}

	return 0;	
}

static int 
get_current_playing_track()
{
	int ticks = get_current_playing_tick();
	int cd_pos = ticks * 3 / 4 + start_playing_pos;
	int i;

	for (i=1; i<= cur_ntracks; i++) {
		if (cd_pos<=cd->trk[i].start) {
			return i;
		}
	}

	return cur_ntracks;
}

static int
update_track_info()
{
	cur_track = get_current_playing_track();
	//printf("check current track=%d\n", cur_track);
	return 0;
}

void 
play_to_next()
{
	if (cur_cdmode == 1) {
		update_track_info();
		if (cur_track >= cur_ntracks) 
			return;
		cur_track++;
		play_cd(cur_track, 0, 9999);//cur_ntracks);
	}
}

#define SEEK_OFFSET	40	/* 10 seconds */

/* for seek */
void
play_to_prev_seek()
{
	int cd_pos, ticks;

	if (cur_cdmode == 1 ) {
		ticks = get_current_playing_tick();
		cd_pos = ticks * 3 / 4 + start_playing_pos - SEEK_OFFSET*75;
		update_track_info();
		if (cur_track<=1) {
			play_cd(cur_track, 0, 9999);
			return;
		}

		cur_track--;
		play_cd(cur_track, (cd_pos - cd->trk[cur_track-1].start)/75, 9999);
	}
}

void
seek_backward()
{
	int ticks, cd_pos;

	update_track_info();

	ticks = get_current_playing_tick();
	cd_pos = ticks * 3 / 4 + start_playing_pos - SEEK_OFFSET*75;

	if (cd_pos < cd->trk[cur_track-1].start) {
		//play_cd(cur_track, 0, 9999);
		play_to_prev_seek();
	} else {
		play_cd(cur_track, (cd_pos - cd->trk[cur_track-1].start)/75, 9999);
	}
}


void 
seek_forward()
{
	int ticks, cd_pos;

	update_track_info();

	ticks = get_current_playing_tick();
	cd_pos = ticks * 3 / 4 + start_playing_pos + SEEK_OFFSET*75;

	if (cur_track>=cur_ntracks) {
		if (cd_pos > cd->trk[cur_ntracks].start) {
			stop_cd();
			exit(0);
			return;
		}

		play_cd(cur_ntracks, (cd_pos - cd->trk[cur_ntracks-1].start)/75, 9999);
	} else {
		if (cd_pos > cd->trk[cur_track].start) {
			if (cur_cdmode == 1) {
				update_track_info();
				if (cur_track >= cur_ntracks) {
					stop_cd();
					exit(0); /* terminate */
				}
				cur_track++;
				play_cd(cur_track, 0, 9999);//cur_ntracks);
			}

			return; /* seek to the tail */
		}

		play_cd(cur_track, (cd_pos - cd->trk[cur_track-1].start)/75, 9999);
	}
}


void 
play_to_prev()
{
	if (cur_cdmode == 1) {
		update_track_info();
		if (cur_track <= 1)
			return;

		cur_track--;
		play_cd(cur_track, 0, 9999);//cur_ntracks);
	}
}




/*
 * Eject the current CD, if there is one, and set the mode to 5.
 *
 * Returns 0 on success, 1 if the CD couldn't be ejected, or 2 if the
 * CD contains a mounted filesystem.
 */
int 
eject_cd()
{
	struct stat	stbuf;
	struct ustat	ust;

	if (cur_cdmode == 5)		/* Already ejected! */
		return (0);

	if (fstat(cd_fd, &stbuf) != 0)
	{
		perror("fstat");
		return (1);
	}

	/* Is this a mounted filesystem? */
	if (ustat(stbuf.st_rdev, &ust) == 0)
		return (2);

	if (ioctl(cd_fd, CDROMEJECT))
	{
		perror("CDEJECT");
		return (1);
	}

	if (exit_on_eject)
		exit(0);

	cur_track = -1;
	cur_cdlen = cur_tracklen = 1;
	cur_pos_abs = cur_pos_rel = cur_frame = 0;
	cur_cdmode = 5;

	return (0);
}

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

typedef struct _info_msg_t {
    long msgtype;
    unsigned short code;
    char data[1024];
} info_msg_t;

int 
read_info()
{ 
    static int msg_id = -1;
	info_msg_t msg_t;
	int pos = 0;
    
    if (msg_id == -1) {
        msg_id = msgget(45160, O_WRONLY);
    }
    
    if (msg_id == -1) 
        return -1;
        
	//printf("pause=%d\n", pause_status);
	memset(&msg_t,sizeof(msg_t),0);

	if (cur_cdmode == 1||cur_cdmode==3) { /* 1==>playing 3==>pause */
	    
		if (pause_status==1) {		    
		    pos = pause_time_tick-start_play_time_tick;
		} else {
		    pos = times(NULL)-start_play_time_tick;
			update_track_info();
			//printf("cur_track = %d\n", cur_track);
		} 
		//printf("time=%d = %d sec\n", pos,pos/75);
				
		sprintf(msg_t.data,"pause=%d cur_track=%d time=%d",
				pause_status,cur_track,pos/75);
	}
	msg_t.msgtype = 100;
	msg_t.code = 1;
	msgsnd(msg_id,&msg_t,sizeof(msg_t) - sizeof(long),0);	
	return 0;
}

