/* @(#)cue.c	1.34 08/06/14 Copyright 2001-2008 J. Schilling */
#ifndef lint
static	char sccsid[] =
	"@(#)cue.c	1.34 08/06/14 Copyright 2001-2008 J. Schilling";
#endif
/*
 *	Cue sheet parser
 *
 *	Copyright (c) 2001-2008 J. Schilling
 */
/*
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * See the file CDDL.Schily.txt in this distribution for details.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file CDDL.Schily.txt from this distribution.
 */

#include <schily/mconfig.h>
#include <stdio.h>
#include <schily/stdlib.h>
#include <schily/unistd.h>
#include <schily/standard.h>
#include <schily/fcntl.h>
#include <schily/stat.h>
#include <schily/varargs.h>
#include <schily/schily.h>
#include <schily/string.h>
#include <schily/utypes.h>
#include <ctype.h>
#include <schily/errno.h>

#include "xio.h"
#include "cdtext.h"
#include "cdrecord.h"
#include "auheader.h"
#include "schily/libport.h"

typedef struct state {
	char	*filename;
	void	*xfp;
	Llong	trackoff;
	Llong	filesize;
	int	filetype;
	int	tracktype;
	int	sectype;
	int	dbtype;
	int	secsize;
	int	dataoff;
	int	state;
	int	track;
	int	index;
	long	index0;
	long	index1;		/* Current index 1 value	*/
	long	secoff;		/* Old index 1 value		*/
	long	pregapsize;
	long	postgapsize;
	int	flags;
} state_t;

#define	STATE_NONE	0
#define	STATE_POSTGAP	1
#define	STATE_FILE	2
#define	STATE_TRACK	3
#define	STATE_FLAGS	4
#define	STATE_INDEX0	5
#define	STATE_INDEX1	6

typedef struct keyw {
	char	*k_name;
	int	k_type;
} keyw_t;

/*
 *	Keywords (first word on line):
 *		CATALOG		- global	CATALOG		<MCN>
 *		CDTEXTFILE	- global	CDTEXTFILE	<fname>
 *		FILE		- track local	FILE		<fame> <type>
 *		FLAGS		- track local	FLAGS		<flag> ...
 *		INDEX		- track local	INDEX		<#> <mm:ss:ff>
 *		ISRC		- track local	ISRC		<ISRC>
 *		PERFORMER	- global/local	PERFORMER	<string>
 *		POSTGAP		- track locak	POSTGAP		<mm:ss:ff>
 *		PREGAP		- track local	PREGAP		<mm:ss:ff>
 *		REM		- anywhere	REM		<comment>
 *		SONGWRITER	- global/local	SONGWRITER	<string>
 *		TITLE		- global/local	TITLE		<string>
 *		TRACK		- track local	TRACK		<#> <datatype>
 *
 *	Order of keywords:
 *		CATALOG
 *		CDTEXTFILE
 *		PERFORMER | SONGWRITER | TITLE		Doc says past FILE...
 *		FILE					Must be past CATALOG
 *		------- Repeat the following:		mehrere FILE Commands?
 *		TRACK
 *		FLAGS | ISRC | PERFORMER | PREGAP | SONGWRITER | TITLE
 *		INDEX
 *		POSTGAP
 */

#define	K_G		0x10000		/* Global			*/
#define	K_T		0x20000		/* Track local			*/
#define	K_A		(K_T | K_G)	/* Global & Track local		*/

#define	K_MCN		(0 | K_G)	/* Media catalog number 	*/
#define	K_TEXTFILE	(1 | K_G)	/* CD-Text binary file		*/
#define	K_FILE		(2 | K_T)	/* Input data file		*/
#define	K_FLAGS		(3 | K_T)	/* Flags for ctrl nibble	*/
#define	K_INDEX		(4 | K_T)	/* Index marker for track	*/
#define	K_ISRC		(5 | K_T)	/* ISRC string for track	*/
#define	K_PERFORMER	(6 | K_A)	/* CD-Text Performer		*/
#define	K_POSTGAP	(7 | K_T)	/* Post gap for track (autogen)	*/
#define	K_PREGAP	(8 | K_T)	/* Pre gap for track (autogen)	*/
#define	K_REM		(9 | K_A)	/* Remark (Comment)		*/
#define	K_SONGWRITER	(10| K_A)	/* CD-Text Songwriter		*/
#define	K_TITLE		(11| K_A)	/* CD-Text Title		*/
#define	K_TRACK		(12| K_T)	/* Track marker			*/


LOCAL keyw_t	keywords[] = {
	{ "CATALOG",	K_MCN },
	{ "CDTEXTFILE",	K_TEXTFILE },
	{ "FILE",	K_FILE },
	{ "FLAGS",	K_FLAGS },
	{ "INDEX",	K_INDEX },
	{ "ISRC",	K_ISRC },
	{ "PERFORMER",	K_PERFORMER },
	{ "POSTGAP",	K_POSTGAP },
	{ "PREGAP",	K_PREGAP },
	{ "REM",	K_REM },
	{ "SONGWRITER",	K_SONGWRITER },
	{ "TITLE",	K_TITLE },
	{ "TRACK",	K_TRACK },
	{ NULL,		0 },
};


/*
 *	Filetypes - argument to FILE Keyword (one only):
 *
 *		BINARY		- Intel binary file (least significant byte first)
 *		MOTOTOLA	- Motorola binary file (most significant byte first)
 *		AIFF		- Audio AIFF file
 *		AU		- Sun Audio file
 *		WAVE		- Audio WAVE file
 *		MP3		- Audio MP3 file
 */
#define	K_BINARY	100
#define	K_MOTOROLA	101
#define	K_AIFF		102
#define	K_AU		103
#define	K_WAVE		104
#define	K_MP3		105
#define	K_OGG		106

LOCAL keyw_t	filetypes[] = {
	{ "BINARY",	K_BINARY },
	{ "MOTOROLA",	K_MOTOROLA },
	{ "AIFF",	K_AIFF },
	{ "AU",		K_AU },
	{ "WAVE",	K_WAVE },
	{ "MP3",	K_MP3 },
	{ "OGG",	K_OGG },
	{ NULL,		0 },
};

/*
 *	Flags - argument to FLAGS Keyword (more than one allowed):
 *		DCP		- Digital copy permitted
 *		4CH		- Four channel audio
 *		PRE		- Pre-emphasis enabled (audio tracks only)
 *		SCMS		- Serial copy management system (not supported by all recorders)
 */
#define	K_DCP		1000
#define	K_4CH		1001
#define	K_PRE		1002
#define	K_SCMS		1003

LOCAL keyw_t	flags[] = {
	{ "DCP",	K_DCP },
	{ "4CH",	K_4CH },
	{ "PRE",	K_PRE },
	{ "SCMS",	K_SCMS },
	{ NULL,		0 },
};

/*
 *	Datatypes - argument to TRACK Keyword (one only):
 *		AUDIO		- Audio/Music (2352)
 *		CDG		- Karaoke CD+G (2448)
 *		MODE1/2048	- CDROM Mode1 Data (cooked)
 *		MODE1/2352	- CDROM Mode1 Data (raw)
 *		MODE2/2336	- CDROM-XA Mode2 Data
 *		MODE2/2352	- CDROM-XA Mode2 Data
 *		CDI/2336	- CDI Mode2 Data
 *		CDI/2352	- CDI Mode2 Data
 */
#define	K_AUDIO		10000
#define	K_CDG		10001
#define	K_MODE1		10002
#define	K_MODE2		10003
#define	K_CDI		10004

LOCAL keyw_t	dtypes[] = {
	{ "AUDIO",	K_AUDIO },
	{ "CDG",	K_CDG },
	{ "MODE1",	K_MODE1 },
	{ "MODE2",	K_MODE2 },
	{ "CDI",	K_CDI },
	{ NULL,		0 },
};


#ifdef	CUE_MAIN
EXPORT	int	main		__PR((int ac, char **av));
#endif
EXPORT	int	parsecue	__PR((char *cuefname, track_t trackp[]));
EXPORT	void	fparsecue	__PR((FILE *f, track_t trackp[]));
LOCAL	void	parse_mcn	__PR((track_t trackp[], state_t *sp));
LOCAL	void	parse_textfile	__PR((track_t trackp[], state_t *sp));
LOCAL	void	parse_file	__PR((track_t trackp[], state_t *sp));
LOCAL	void	parse_flags	__PR((track_t trackp[], state_t *sp));
LOCAL	void	parse_index	__PR((track_t trackp[], state_t *sp));
LOCAL	void	parse_isrc	__PR((track_t trackp[], state_t *sp));
LOCAL	void	parse_performer	__PR((track_t trackp[], state_t *sp));
LOCAL	void	parse_postgap	__PR((track_t trackp[], state_t *sp));
LOCAL	void	parse_pregap	__PR((track_t trackp[], state_t *sp));
LOCAL	void	parse_songwriter __PR((track_t trackp[], state_t *sp));
LOCAL	void	parse_title	__PR((track_t trackp[], state_t *sp));
LOCAL	void	parse_track	__PR((track_t trackp[], state_t *sp));
LOCAL	void	parse_offset	__PR((long *lp));
LOCAL	void	newtrack	__PR((track_t trackp[], state_t *sp));

LOCAL	keyw_t	*lookup		__PR((char *word, keyw_t table[]));
#ifdef	DEBUG
LOCAL	void	wdebug		__PR((void));
#endif
LOCAL	FILE	*cueopen	__PR((char *name));
LOCAL	char	*cuename	__PR((void));
LOCAL	char	*nextline	__PR((FILE *f));
LOCAL	void	ungetline	__PR((void));
LOCAL	char	*skipwhite	__PR((const char *s));
LOCAL	char	*peekword	__PR((void));
LOCAL	char	*lineend	__PR((void));
LOCAL	char	*markword	__PR((char *delim));
LOCAL	char	getworddelim	__PR((void));
LOCAL	char	*getnextitem	__PR((char *delim));
LOCAL	char	*neednextitem	__PR((char *delim));
LOCAL	char	*nextword	__PR((void));
LOCAL	char	*needword	__PR((void));
LOCAL	char	*curword	__PR((void));
LOCAL	char	*nextitem	__PR((void));
LOCAL	char	*needitem	__PR((void));
LOCAL	void	checkextra	__PR((void));
LOCAL	void	cueabort	__PR((const char *fmt, ...));

#ifdef	CUE_MAIN
int	debug;
int	xdebug = 1;

int write_secs	__PR((void));
int write_secs() { return (-1); }

EXPORT int
main(ac, av)
	int	ac;
	char	*av[];
{
	int	i;
	track_t	track[MAX_TRACK+2];	/* Max tracks + track 0 + track AA */

	save_args(ac, av);

	fillbytes(track, sizeof (track), '\0');
	for (i = 0; i < MAX_TRACK+2; i++)
		track[i].track = track[i].trackno = i;
	track[0].tracktype = TOC_MASK;


	parsecue(av[1], track);
	return (0);
}
#else
extern	int	xdebug;
#endif

EXPORT int
parsecue(cuefname, trackp)
	char	*cuefname;
	track_t	trackp[];
{
	FILE	*f = cueopen(cuefname);

	fparsecue(f, trackp);
	return (0);
}

EXPORT void
fparsecue(f, trackp)
	FILE	*f;
	track_t	trackp[];
{
	char	*word;
	struct keyw *kp;
	BOOL	isglobal = TRUE;
	state_t	state;

	state.filename	= NULL;
	state.xfp	= NULL;
	state.trackoff	= 0;
	state.filesize	= 0;
	state.filetype	= 0;
	state.tracktype	= 0;
	state.sectype	= 0;
	state.dbtype	= 0;
	state.secsize	= 0;
	state.dataoff	= 0;
	state.state	= STATE_NONE;
	state.track	= 0;
	state.index	= -1;
	state.index0	= -1;
	state.index1	= -1;
	state.secoff	= 0;
	state.pregapsize = -1;
	state.postgapsize = -1;
	state.flags	= 0;

	if (xdebug > 1)
		printf("---> Entering CUE Parser...\n");
	do {
		if (nextline(f) == NULL) {
			/*
			 * EOF on CUE File
			 * Do post processing here
			 */
			if (state.state < STATE_INDEX1 && state.state != STATE_POSTGAP)
				cueabort("Incomplete CUE file");
			if (state.xfp) {
				xclose(state.xfp);
				state.xfp = NULL;
			}
			if (xdebug > 1) {
				printf("---> CUE Parser got EOF, found %d tracks.\n",
								state.track);
			}
			return;
		}
		word = nextitem();
		if (*word == '\0')	/* empty line */
			continue;

		if (xdebug > 1)
			printf("\nKEY: '%s'     %s\n", word, peekword());
		kp = lookup(word, keywords);
		if (kp == NULL)
			cueabort("Unknown CUE keyword '%s'", word);

		if ((kp->k_type & K_G) == 0) {
			if (isglobal)
				isglobal = FALSE;
		}
		if ((kp->k_type & K_T) == 0) {
			if (!isglobal)
				cueabort("Badly placed CUE keyword '%s'", word);
		}
#ifdef	DEBUG
		printf("%s-", isglobal ? "G" : "T");
		wdebug();
#endif

		switch (kp->k_type) {

		case K_MCN:	   parse_mcn(trackp, &state);		break;
		case K_TEXTFILE:   parse_textfile(trackp, &state);	break;
		case K_FILE:	   parse_file(trackp, &state);		break;
		case K_FLAGS:	   parse_flags(trackp, &state);		break;
		case K_INDEX:	   parse_index(trackp, &state);		break;
		case K_ISRC:	   parse_isrc(trackp, &state);		break;
		case K_PERFORMER:  parse_performer(trackp, &state);	break;
		case K_POSTGAP:	   parse_postgap(trackp, &state);	break;
		case K_PREGAP:	   parse_pregap(trackp, &state);	break;
		case K_REM:						break;
		case K_SONGWRITER: parse_songwriter(trackp, &state);	break;
		case K_TITLE:	   parse_title(trackp, &state);		break;
		case K_TRACK:	   parse_track(trackp, &state);		break;

		default:
			cueabort("Panic: unknown CUE command '%s'", word);
		}
	} while (1);
}

LOCAL void
parse_mcn(trackp, sp)
	track_t	trackp[];
	state_t	*sp;
{
	char	*word;
	textptr_t *txp;

	if (sp->track != 0)
		cueabort("CATALOG keyword must be before first TRACK");

	word = needitem();
	setmcn(word, &trackp[0]);
	txp = gettextptr(0, trackp); /* MCN is isrc for trk 0 */
	txp->tc_isrc = strdup(word);

	checkextra();
}

LOCAL void
parse_textfile(trackp, sp)
	track_t	trackp[];
	state_t	*sp;
{
	char	*word;

	if (sp->track != 0)
		cueabort("CDTEXTFILE keyword must be before first TRACK");

	word = needitem();

	if (trackp[MAX_TRACK+1].flags & TI_TEXT) {
		if (!checktextfile(word)) {
			comerrno(EX_BAD,
				"Cannot use '%s' as CD-Text file.\n",
				word);
		}
		trackp[0].flags |= TI_TEXT;
	} else {
		errmsgno(EX_BAD, "Ignoring CDTEXTFILE '%s'.\n", word);
		errmsgno(EX_BAD, "If you like to write CD-Text, call cdrecord -text.\n");
	}

	checkextra();
}

LOCAL void
parse_file(trackp, sp)
	track_t	trackp[];
	state_t	*sp;
{
	char	cname[1024];
	char	newname[1024];
	struct keyw *kp;
	char	*word;
	char	*filetype;
	struct stat	st;
#ifdef	hint
	Llong		lsize;
#endif

	if ((sp->state <= STATE_TRACK && sp->state > STATE_POSTGAP) ||
	    (sp->state >= STATE_TRACK && sp->state < STATE_INDEX1))
		cueabort("Badly placed FILE keyword");
	sp->state = STATE_FILE;

	word = needitem();
	if (sp->xfp) {
		xclose(sp->xfp);
		sp->xfp = NULL;
	}
	sp->xfp = xopen(word, O_RDONLY|O_BINARY, 0, X_NOREWIND);
	if (sp->xfp == NULL && geterrno() == ENOENT) {
		char	*p;

		if (strchr(word, '/') == 0 &&
		    strchr(cuename(), '/') != 0) {
			js_snprintf(cname, sizeof (cname),
				"%s", cuename());
			p = strrchr(cname, '/');
			if (p)
				*p = '\0';
			js_snprintf(newname, sizeof (newname),
				"%s/%s", cname, word);
			word = newname;
			sp->xfp = xopen(word, O_RDONLY|O_BINARY, 0, X_NOREWIND);
		}
	}
	if (sp->xfp == NULL)
		comerr("Cannot open FILE '%s'.\n", word);

	sp->filename	 = strdup(word);
	sp->trackoff	 = 0;
	sp->filesize	 = 0;
	sp->flags	&= ~TI_SWAB;	/* Reset what we might set for FILE */

	filetype = needitem();
	kp = lookup(filetype, filetypes);
	if (kp == NULL)
		cueabort("Unknown filetype '%s'", filetype);

	switch (kp->k_type) {

	case K_BINARY:
	case K_MOTOROLA:
			if (fstat(xfileno(sp->xfp), &st) >= 0 &&
			    S_ISREG(st.st_mode)) {
				sp->filesize = st.st_size;
			} else {
				cueabort("Unknown file size for FILE '%s'",
								sp->filename);
			}
			break;
	case K_AIFF:
			cueabort("Unsupported filetype '%s'", kp->k_name);
			break;
	case K_AU:
			sp->filesize = ausize(xfileno(sp->xfp));
			break;
	case K_WAVE:
			sp->filesize = wavsize(xfileno(sp->xfp));
			sp->flags |= TI_SWAB;
			break;
	case K_MP3:
	case K_OGG:
			cueabort("Unsupported filetype '%s'", kp->k_name);
			break;

	default:	cueabort("Panic: unknown filetype '%s'", filetype);
	}

	if (sp->filesize == AU_BAD_CODING) {
		cueabort("Inappropriate audio coding in '%s'",
							sp->filename);
	}
	if (xdebug > 0)
		printf("Track %d File '%s' Filesize %lld\n",
			sp->track, sp->filename, sp->filesize);

	sp->filetype = kp->k_type;

	checkextra();


#ifdef	hint
		trackp->itracksize = lsize;
		if (trackp->itracksize != lsize)
			comerrno(EX_BAD, "This OS cannot handle large audio images.\n");
#endif
}

LOCAL void
parse_flags(trackp, sp)
	track_t	trackp[];
	state_t	*sp;
{
	struct keyw *kp;
	char	*word;

	if ((sp->state < STATE_TRACK) ||
	    (sp->state >= STATE_INDEX0))
		cueabort("Badly placed FLAGS keyword");
	sp->state = STATE_FLAGS;

	do {
		word = needitem();
		kp = lookup(word, flags);
		if (kp == NULL)
			cueabort("Unknown flag '%s'", word);

		switch (kp->k_type) {

		case K_DCP:	sp->flags |= TI_COPY;	break;
		case K_4CH:	sp->flags |= TI_QUADRO;	break;
		case K_PRE:	sp->flags |= TI_PREEMP;	break;
		case K_SCMS:	sp->flags |= TI_SCMS;	break;
		default:	cueabort("Panic: unknown FLAG '%s'", word);
		}

	} while (peekword() < lineend());

	if (xdebug > 0)
		printf("Track %d flags 0x%08X\n", sp->track, sp->flags);
}

LOCAL void
parse_index(trackp, sp)
	track_t	trackp[];
	state_t	*sp;
{
	char	*word;
	long	l;
	int	track = sp->track;

	if (sp->state < STATE_TRACK)
		cueabort("Badly placed INDEX keyword");

	word = needitem();
	if (*astolb(word, &l, 10) != '\0')
		cueabort("Not a number '%s'", word);
	if (l < 0 || l > 99)
		cueabort("Illegal index '%s'", word);

	if ((sp->index < l) &&
	    (((sp->index + 1) == l) || l == 1))
		sp->index = l;
	else
		cueabort("Badly placed INDEX %ld number", l);

	if (l > 0)
		sp->state = STATE_INDEX1;
	else
		sp->state = STATE_INDEX0;

	parse_offset(&l);

	if (xdebug > 1)
		printf("Track %d Index %d %ld\n", sp->track, sp->index, l);

	if (sp->index == 0)
		sp->index0 = l;
	if (sp->index == 1) {
		sp->index1 = l;
		trackp[track].nindex = 1;
		newtrack(trackp, sp);

		if (xdebug > 1) {
			printf("Track %d pregapsize %ld\n",
				sp->track, trackp[track].pregapsize);
		}
	}
	if (sp->index == 2) {
		trackp[track].tindex = malloc(100*sizeof (long));
		trackp[track].tindex[1] = 0;
		trackp[track].tindex[2] = l - sp->index1;
		trackp[track].nindex = 2;
	}
	if (sp->index > 2) {
		trackp[track].tindex[sp->index] = l - sp->index1;
		trackp[track].nindex = sp->index;
	}

	checkextra();
}

LOCAL void
parse_isrc(trackp, sp)
	track_t	trackp[];
	state_t	*sp;
{
	char	*word;
	textptr_t *txp;
	int	track = sp->track;

	if (track == 0)
		cueabort("ISRC keyword must be past first TRACK");

	if ((sp->state < STATE_TRACK) ||
	    (sp->state >= STATE_INDEX0))
		cueabort("Badly placed ISRC keyword");
	sp->state = STATE_FLAGS;

	word = needitem();
	setisrc(word, &trackp[track]);
	txp = gettextptr(track, trackp);
	txp->tc_isrc = strdup(word);

	checkextra();
}

LOCAL void
parse_performer(trackp, sp)
	track_t	trackp[];
	state_t	*sp;
{
	char	*word;
	textptr_t *txp;

	if (sp->track > 0 && sp->state > STATE_INDEX0)
		cueabort("Badly placed PERFORMER keyword");

	word = needitem();
	txp = gettextptr(sp->track, trackp);
	txp->tc_performer = strdup(word);

	checkextra();
}

LOCAL void
parse_postgap(trackp, sp)
	track_t	trackp[];
	state_t	*sp;
{
	long	l;

	if (sp->state < STATE_INDEX1)
		cueabort("Badly placed POSTGAP keyword");
	sp->state = STATE_POSTGAP;

	parse_offset(&l);
	sp->postgapsize = l;
	trackp[sp->track].padsecs = l;
	/*
	 * Add to size of track.
	 * In non-CUE mode, this is done in opentracks().
	 */
	if (l > 0)
		trackp[sp->track].tracksecs += l;

	checkextra();
}

LOCAL void
parse_pregap(trackp, sp)
	track_t	trackp[];
	state_t	*sp;
{
	long	l;

	if ((sp->state < STATE_TRACK) ||
	    (sp->state >= STATE_INDEX0))
		cueabort("Badly placed PREGAP keyword");
	sp->state = STATE_FLAGS;

	parse_offset(&l);
	sp->pregapsize = l;

	checkextra();
}

LOCAL void
parse_songwriter(trackp, sp)
	track_t	trackp[];
	state_t	*sp;
{
	char	*word;
	textptr_t *txp;

	if (sp->track > 0 && sp->state > STATE_INDEX0)
		cueabort("Badly placed SONGWRITER keyword");

	word = needitem();
	txp = gettextptr(sp->track, trackp);
	txp->tc_songwriter = strdup(word);

	checkextra();
}

LOCAL void
parse_title(trackp, sp)
	track_t	trackp[];
	state_t	*sp;
{
	char	*word;
	textptr_t *txp;

	if (sp->track > 0 && sp->state > STATE_INDEX0)
		cueabort("Badly placed TITLE keyword");

	word = needitem();
	txp = gettextptr(sp->track, trackp);
	txp->tc_title = strdup(word);

	checkextra();
}

LOCAL void
parse_track(trackp, sp)
	track_t	trackp[];
	state_t	*sp;
{
	struct keyw *kp;
	char	*word;
	long	l;
	long	secsize = -1;

	if ((sp->state >= STATE_TRACK) &&
	    (sp->state < STATE_INDEX1))
		cueabort("Badly placed TRACK keyword");
	sp->state = STATE_TRACK;
	sp->index0 = -1;
	sp->index = -1;
	sp->pregapsize = -1;
	sp->postgapsize = -1;

	word = needitem();
	if (*astolb(word, &l, 10) != '\0')
		cueabort("Not a number '%s'", word);
	if (l <= 0 || l > 99)
		cueabort("Illegal TRACK number '%s'", word);

	if ((sp->track < l) &&
	    (((sp->track + 1) == l) || sp->track == 0))
		sp->track = l;
	else
		cueabort("Badly placed TRACK %ld number", l);

	word = needword();
	kp = lookup(word, dtypes);
	if (kp == NULL)
		cueabort("Unknown filetype '%s'", word);

	if (getworddelim() == '/') {
		word = needitem();
		if (*astol(++word, &secsize) != '\0')
			cueabort("Not a number '%s'", word);
	}

	/*
	 * Reset all flags that may be set in TRACK & FLAGS lines
	 */
	sp->flags &= ~(TI_AUDIO|TI_COPY|TI_QUADRO|TI_PREEMP|TI_SCMS);

	if (kp->k_type == K_AUDIO)
		sp->flags |= TI_AUDIO;

	switch (kp->k_type) {

	case K_CDG:
		if (secsize < 0)
			secsize = 2448;
	case K_AUDIO:
		if (secsize < 0)
			secsize = 2352;

		sp->tracktype = TOC_DA;
		sp->sectype = SECT_AUDIO;
		sp->dbtype = DB_RAW;
		sp->secsize = secsize;
		sp->dataoff = 0;
		if (secsize != 2352)
			cueabort("Unsupported sector size %ld for audio", secsize);
		break;

	case K_MODE1:
		if (secsize < 0)
			secsize = 2048;

		sp->tracktype = TOC_ROM;
		sp->sectype = SECT_ROM;
		sp->dbtype = DB_ROM_MODE1;
		sp->secsize = secsize;
		sp->dataoff = 16;
		/*
		 * XXX Sector Size == 2352 ???
		 * XXX It seems that there exist bin/cue pairs with this value
		 */
		if (secsize != 2048)
			cueabort("Unsupported sector size %ld for data", secsize);
		break;

	case K_MODE2:
	case K_CDI:
		sp->tracktype = TOC_ROM;
		sp->sectype = SECT_MODE_2;
		sp->dbtype = DB_ROM_MODE2;
		sp->secsize = secsize;
		sp->dataoff = 16;
		if (secsize == 2352) {
			sp->tracktype = TOC_XA2;
			sp->sectype = SECT_MODE_2_MIX;
			sp->sectype |= ST_MODE_RAW;
			sp->dbtype = DB_RAW;
			sp->dataoff = 0;
		} else if (secsize != 2336)
			cueabort("Unsupported sector size %ld for mode2", secsize);
		if (kp->k_type == K_CDI)
			sp->tracktype = TOC_CDI;
		break;

	default:	cueabort("Panic: unknown datatype '%s'", word);
	}

	if (sp->flags & TI_PREEMP)
		sp->sectype |= ST_PREEMPMASK;
	sp->secsize = secsize;

	if (xdebug > 1) {
		printf("Track %d Tracktype %s/%d\n",
			sp->track, kp->k_name, sp->secsize);
	}

	checkextra();
}

LOCAL void
parse_offset(lp)
	long	*lp;
{
	char	*word;
	char	*p;
	long	m = -1;
	long	s = -1;
	long	f = -1;

	word = needitem();

	if (strchr(word, ':') == NULL) {
		if (*astol(word, lp) != '\0')
			cueabort("Not a number '%s'", word);
		return;
	}
	if (*(p = astolb(word, &m, 10)) != ':')
		cueabort("Not a number '%s'", word);
	if (m < 0 || m >= 160)
		cueabort("Illegal minute value in '%s'", word);
	p++;
	if (*(p = astolb(p, &s, 10)) != ':')
		cueabort("Not a number '%s'", p);
	if (s < 0 || s >= 60)
		cueabort("Illegal second value in '%s'", word);
	p++;
	if (*(p = astolb(p, &f, 10)) != '\0')
		cueabort("Not a number '%s'", p);
	if (f < 0 || f >= 75)
		cueabort("Illegal frame value in '%s'", word);

	m = m * 60 + s;
	m = m * 75 + f;
	*lp = m;
}

/*--------------------------------------------------------------------------*/
LOCAL void
newtrack(trackp, sp)
	track_t	trackp[];
	state_t	*sp;
{
	register int	i;
	register int	track = sp->track;
		Llong	tracksize;

	if (xdebug > 1)
		printf("-->Newtrack %d\n", track);
	if (track > 1 && streql(sp->filename, trackp[track-1].filename)) {
		tracksize = (sp->index1 - sp->secoff) * trackp[track-1].isecsize;

		if (xdebug > 1)
			printf("    trackoff %lld filesize %lld index1 %ld size %ld/%lld secsize/isecsize %d/%d\n",
				sp->trackoff, sp->filesize, sp->index1,
				sp->index1 - sp->secoff,
				tracksize,
				trackp[track-1].secsize,
				trackp[track-1].isecsize);

		trackp[track-1].itracksize = tracksize;
		trackp[track-1].tracksize = tracksize;
		if (trackp[track-1].secsize != trackp[track-1].isecsize) {
			/*
			 * In RAW mode, we need to recompute the track size.
			 */
			trackp[track-1].tracksize =
						(trackp[track-1].itracksize /
						trackp[track-1].isecsize) *
						trackp[track-1].secsize
						+ trackp[track-1].itracksize %
						trackp[track-1].isecsize;
		}
		trackp[track-1].tracksecs = sp->index1 - sp->secoff;
		/*
		 * Add to size of track.
		 * In non-CUE mode, this is done in opentracks().
		 */
		if (trackp[track-1].padsecs > 0)
			trackp[track-1].tracksecs += trackp[track-1].padsecs;

		sp->trackoff += tracksize;
		sp->secoff = sp->index1;
	}
	/*
	 * Make 'tracks' immediately usable in track structure.
	 */
	for (i = 0; i < MAX_TRACK+2; i++)
		trackp[i].tracks = track;

	trackp[track].filename = sp->filename;
	trackp[track].xfp = xopen(sp->filename, O_RDONLY|O_BINARY, 0, X_NOREWIND);
	trackp[track].trackstart = 0L;
/*
 * SEtzen wenn tracksecs bekannt sind
 * d.h. mit Index0 oder Index 1 vom nächsten track
 *
 *	trackp[track].itracksize = tracksize;
 *	trackp[track].tracksize = tracksize;
 *	trackp[track].tracksecs = -1L;
 */
	tracksize = sp->filesize - sp->trackoff;

	trackp[track].itracksize = tracksize;
	trackp[track].tracksize = tracksize;
	trackp[track].tracksecs = (tracksize + sp->secsize - 1) / sp->secsize;

	if (xdebug > 1)
		printf("    Remaining Filesize %lld (%lld secs)\n",
			(sp->filesize-sp->trackoff),
			(sp->filesize-sp->trackoff +sp->secsize - 1) / sp->secsize);

	if (sp->pregapsize >= 0) {
/*		trackp[track].flags &= ~TI_PREGAP;*/
		sp->flags &= ~TI_PREGAP;
		trackp[track].pregapsize = sp->pregapsize;
	} else {
/*		trackp[track].flags |= TI_PREGAP;*/
		if (track > 1)
			sp->flags |= TI_PREGAP;
		if (track == 1)
			trackp[track].pregapsize = sp->index1 + 150;
		else if (sp->index0 < 0)
			trackp[track].pregapsize = -1;
		else
			trackp[track].pregapsize = sp->index1 - sp->index0;
	}

	trackp[track].isecsize = sp->secsize;
	trackp[track].secsize = sp->secsize;
	trackp[track].flags = sp->flags | trackp[0].flags;
	if (trackp[0].flags & TI_RAW) {
		if (is_raw16(&trackp[track]))
			trackp[track].secsize = RAW16_SEC_SIZE;
		else
			trackp[track].secsize = RAW96_SEC_SIZE;
#ifndef	HAVE_LIB_EDC_ECC
		if ((sp->sectype & ST_MODE_MASK) != ST_MODE_AUDIO) {
			errmsgno(EX_BAD,
				"EDC/ECC library not compiled in.\n");
			comerrno(EX_BAD,
				"Data sectors are not supported in RAW mode.\n");
		}
#endif
	}
	/*
	 * In RAW mode, we need to recompute the track size.
	 */
	trackp[track].tracksize =
			(trackp[track].itracksize / trackp[track].isecsize) *
			trackp[track].secsize
			+ trackp[track].itracksize % trackp[track].isecsize;

	trackp[track].secspt = 0;	/* transfer size is set up in set_trsizes() */
/*	trackp[track].pktsize = pktsize; */
	trackp[track].pktsize = 0;
	trackp[track].trackno = sp->track;
	trackp[track].sectype = sp->sectype;

	trackp[track].dataoff = sp->dataoff;
	trackp[track].tracktype = sp->tracktype;
	trackp[track].dbtype = sp->dbtype;

	if (track == 1) {
		trackp[0].tracktype &= ~TOC_MASK;
		trackp[0].tracktype |= sp->tracktype;

		if (xdebug > 1) {
			printf("Track %d Tracktype %X\n",
					0, trackp[0].tracktype);
		}
	}
	if (xdebug > 1) {
		printf("Track %d Tracktype %X\n",
				track, trackp[track].tracktype);
	}
	trackp[track].nindex = 1;
	trackp[track].tindex = 0;

	if (xdebug > 1) {
		printf("Track %d flags 0x%08X\n", 0, trackp[0].flags);
		printf("Track %d flags 0x%08X\n", track, trackp[track].flags);
	}
}

/*--------------------------------------------------------------------------*/
LOCAL keyw_t *
lookup(word, table)
	char	*word;
	keyw_t	table[];
{
	register keyw_t	*kp = table;

	while (kp->k_name) {
		if (streql(kp->k_name, word))
			return (kp);
		kp++;
	}
	return (NULL);
}

/*--------------------------------------------------------------------------*/
/*
 * Parser low level functions start here...
 */

LOCAL	char	linebuf[4096];
LOCAL	char	*fname;
LOCAL	char	*linep;
LOCAL	char	*wordendp;
LOCAL	char	wordendc;
LOCAL	int	olinelen;
LOCAL	int	linelen;
LOCAL	int	lineno;

LOCAL	char	worddelim[] = "=:,/";
LOCAL	char	nulldelim[] = "";

#ifdef	DEBUG
LOCAL void
wdebug()
{
		printf("WORD: '%s' rest '%s'\n", linep, peekword());
		printf("linep %lX peekword %lX end %lX\n",
			(long)linep, (long)peekword(), (long)&linebuf[linelen]);
}
#endif

LOCAL FILE *
cueopen(name)
	char	*name;
{
	FILE	*f;

	f = fileopen(name, "r");
	if (f == NULL)
		comerr("Cannot open '%s'.\n", name);

	fname = name;
	return (f);
}

LOCAL char *
cuename()
{
	return (fname);
}

LOCAL char *
nextline(f)
	FILE	*f;
{
	register int	len;

	do {
		fillbytes(linebuf, sizeof (linebuf), '\0');
		len = fgetline(f, linebuf, sizeof (linebuf));
		if (len < 0)
			return (NULL);
		if (len > 0 && linebuf[len-1] == '\r') {
			linebuf[len-1] = '\0';
			len--;
		}
		linelen = len;
		lineno++;
	} while (linebuf[0] == '#');

	olinelen = linelen;
	linep = linebuf;
	wordendp = linep;
	wordendc = *linep;

	return (linep);
}

LOCAL void
ungetline()
{
	linelen = olinelen;
	linep = linebuf;
	*wordendp = wordendc;
	wordendp = linep;
	wordendc = *linep;
}

LOCAL char *
skipwhite(s)
	const char	*s;
{
	register const Uchar	*p = (const Uchar *)s;

	while (*p) {
		if (!isspace(*p))
			break;
		p++;
	}
	return ((char *)p);
}

LOCAL char *
peekword()
{
	return (&wordendp[1]);
}

LOCAL char *
lineend()
{
	return (&linebuf[linelen]);
}

LOCAL char *
markword(delim)
	char	*delim;
{
	register	BOOL	quoted = FALSE;
	register	Uchar	c;
	register	Uchar	*s;
	register	Uchar	*from;
	register	Uchar	*to;

	for (s = (Uchar *)linep; (c = *s) != '\0'; s++) {
		if (c == '"') {
			quoted = !quoted;
/*			strcpy((char *)s, (char *)&s[1]);*/
			for (to = s, from = &s[1]; *from; ) {
				c = *from++;
				if (c == '\\' && quoted && (*from == '\\' || *from == '"'))
					c = *from++;
				*to++ = c;
			}
			*to = '\0';
			c = *s;
linelen--;
		}
		if (!quoted && isspace(c))
			break;
		if (!quoted && strchr(delim, c) && s > (Uchar *)linep)
			break;
	}
	wordendp = (char *)s;
	wordendc = (char)*s;
	*s = '\0';

	return (linep);
}

LOCAL char
getworddelim()
{
	return (wordendc);
}

LOCAL char *
getnextitem(delim)
	char	*delim;
{
	*wordendp = wordendc;

	linep = skipwhite(wordendp);
	return (markword(delim));
}

LOCAL char *
neednextitem(delim)
	char	*delim;
{
	char	*olinep = linep;
	char	*nlinep;

	nlinep = getnextitem(delim);

	if ((olinep == nlinep) || (*nlinep == '\0'))
		cueabort("Missing text");

	return (nlinep);
}

LOCAL char *
nextword()
{
	return (getnextitem(worddelim));
}

LOCAL char *
needword()
{
	return (neednextitem(worddelim));
}

LOCAL char *
curword()
{
	return (linep);
}

LOCAL char *
nextitem()
{
	return (getnextitem(nulldelim));
}

LOCAL char *
needitem()
{
	return (neednextitem(nulldelim));
}

LOCAL void
checkextra()
{
	if (peekword() < lineend())
		cueabort("Extra text '%s'", peekword());
}

/* VARARGS1 */
#ifdef	PROTOTYPES
LOCAL void
cueabort(const char *fmt, ...)
#else
LOCAL void
cueabort(fmt, va_alist)
	char	*fmt;
	va_dcl
#endif
{
	va_list	args;

#ifdef	PROTOTYPES
	va_start(args, fmt);
#else
	va_start(args);
#endif
	comerrno(EX_BAD, "%r on line %d in '%s'.\n",
		fmt, args, lineno, fname);
	va_end(args);
}
