/* @(#)cdtext.c	1.9 03/05/09 Copyright 1999-2002 J. Schilling */
#ifndef lint
static	char sccsid[] =
	"@(#)cdtext.c	1.9 03/05/09 Copyright 1999-2002 J. Schilling";
#endif
/*
 *	Generic CD-Text support functions
 *
 *	Copyright (c) 1999-2002 J. Schilling
 */
/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; see the file COPYING.  If not, write to the Free Software
 * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <mconfig.h>
#include <stdio.h>
#include <stdxlib.h>
#include <unixstd.h>	/* Include sys/types.h to make off_t available */
#include <standard.h>
#include <utypes.h>
#include <strdefs.h>
#include <schily.h>

#include <scg/scsitransp.h>	/* For write_leadin() */

#include "cdtext.h"
#include "cdrecord.h"
#include "crc16.h"

#define	PTI_TITLE	0x80	/* Album name and Track titles */
#define	PTI_PERFORMER	0x81	/* Singer/player/conductor/orchestra */
#define	PTI_SONGWRITER	0x82	/* Name of the songwriter */
#define	PTI_COMPOSER	0x83	/* Name of the composer */
#define	PTI_ARRANGER	0x84	/* Name of the arranger */
#define	PTI_MESSAGE	0x85	/* Message from content provider or artist */
#define	PTI_DISK_ID	0x86	/* Disk identification information */
#define	PTI_GENRE	0x87	/* Genre identification / information */
#define	PTI_TOC		0x88	/* TOC information */
#define	PTI_TOC2	0x89	/* Second TOC */
#define	PTI_RES_8A	0x8A	/* Reserved 8A */
#define	PTI_RES_8B	0x8B	/* Reserved 8B */
#define	PTI_RES_8C	0x8C	/* Reserved 8C */
#define	PTI_CLOSED_INFO	0x8D	/* For internal use by content provider */
#define	PTI_ISRC	0x8E	/* UPC/EAN code of album and ISRC for tracks */
#define	PTI_SIZE	0x8F	/* Size information of the block */

extern	int	xdebug;

typedef struct textpack {
	Uchar	pack_type;	/* Pack Type indicator	*/
	char	track_no;	/* Track Number (0..99)	*/
	char	seq_number;	/* Sequence Number	*/
	char	block_number;	/* Block # / Char pos	*/
	char	text[12];	/* CD-Text Data field	*/
	char	crc[2];		/* CRC 16		*/
} txtpack_t;

#define	EXT_DATA 0x80		/* Extended data indicator in track_no */
#define	DBCC	 0x80		/* Double byte char indicator in block */

/*
 *	CD-Text size example:
 *
 *	0  1  2  3  00 01 02 03 04 05 06 07 08 09 10 11 CRC16
 *
 *	8F 00 2B 00 01 01 0D 03 0C 0C 00 00 00 00 01 00 7B 3D 
 *	8F 01 2C 00 00 00 00 00 00 00 12 03 2D 00 00 00 DA B7
 *	8F 02 2D 00 00 00 00 00 09 00 00 00 00 00 00 00 6A 24
 *
 *	charcode 1
 *	first tr 1
 *	last tr  13
 *	Copyr	 3
 *	Pack Count 80= 12, 81 = 12, 86 = 1, 8e = 18, 8f = 3
 *	last seq   0 = 2d
 *	languages  0 = 9
 */

typedef struct textsizes {
	char	charcode;
	char	first_track;
	char	last_track;
	char	copyr_flags;
	char	pack_count[16];
	char	last_seqnum[8];
	char	language_codes[8];
} txtsize_t;

typedef struct textargs {
	txtpack_t	*tp;
	char		*p;
	txtsize_t	*tsize;
	int		seqno;
} txtarg_t;


EXPORT	Uchar	*textsub;
EXPORT	int	textlen;

EXPORT	BOOL	checktextfile	__PR((char *fname));
LOCAL	void	setuptextdata	__PR((Uchar *bp, int len));
LOCAL	BOOL	cdtext_crc_ok	__PR((struct textpack *p));
EXPORT	void	packtext	__PR((int tracks, track_t *trackp));
LOCAL	BOOL	anytext		__PR((int pack_type, int tracks, track_t *trackp));
LOCAL	void	fillup_pack	__PR((txtarg_t *ap));
LOCAL	void	fillpacks	__PR((txtarg_t *ap, char *from, int len, int track_no, int pack_type));
EXPORT	int	write_cdtext	__PR((SCSI *scgp, cdr_t *dp, long startsec));
LOCAL	void	eight2six	__PR((Uchar *in, Uchar *out));
LOCAL	void	six2eight	__PR((Uchar *in, Uchar *out));


EXPORT BOOL
checktextfile(fname)
	char	*fname;
{
	FILE	*f;
	Uchar	hbuf[4];
	Uchar	*bp;
	struct textpack *tp;
	int	len;
	int	crc;
	int	n;
	int	j;
	off_t	fs;

	if ((f = fileopen(fname, "rb")) == NULL) {
		errmsg("Cannot open '%s'.\n", fname);
		return (FALSE);
	}
	fs = filesize(f);
	j = fs % sizeof(struct textpack);
	if (j == 4) {
		n = fileread(f, hbuf, 4);
		if (n != 4) {
			if (n < 0)
				errmsg("Cannot read '%s'.\n", fname);
			else
				errmsgno(EX_BAD, "File '%s' is too small for CD-Text.\n", fname);
			return (FALSE);
		}
		len = hbuf[0] * 256 + hbuf[1];
		len -= 2;
		n = fs - 4;
		if (n != len) {
			errmsgno(EX_BAD, "Inconsistent CD-Text file '%s' length should be %d but is %lld\n",
				fname, len+4, (Llong)fs);
			return (FALSE);
		}
	} else if (j != 0) {
		errmsgno(EX_BAD, "Inconsistent CD-Text file '%s' not a multiple of pack length\n",
			fname);
		return (FALSE);
	} else {
		len = fs;
	}
	printf("Text len: %d\n", len);
	bp = malloc(len);
	if (bp == NULL) {
		errmsg("Cannot malloc CD-Text read buffer.\n");
		return (FALSE);
	}
	n = fileread(f, bp, len);

	tp = (struct textpack*)bp;
	for (n=0; n < len; n += sizeof(struct textpack), tp++) {
		if (tp->pack_type < 0x80 || tp->pack_type > 0x8F) {
			errmsgno(EX_BAD, "Illegal pack type 0x%02X pack #%ld in CD-Text file '%s'.\n",
				tp->pack_type, (long)(n/sizeof(struct textpack)), fname);
			return (FALSE);
		}
		crc = (tp->crc[0] & 0xFF) << 8 | (tp->crc[1] & 0xFF);
		crc ^= 0xFFFF;
		if (crc != calcCRC((Uchar *)tp, sizeof(*tp)-2)) {
			if (cdtext_crc_ok(tp)) {
				errmsgno(EX_BAD,
				"Corrected CRC ERROR in pack #%ld (offset %d-%ld) in CD-Text file '%s'.\n",
				(long)(n/sizeof(struct textpack)),
				n+j, (long)(n+j+sizeof(struct textpack)),
				fname);
			} else {
			errmsgno(EX_BAD, "CRC ERROR in pack #%ld (offset %d-%ld) in CD-Text file '%s'.\n",
				(long)(n/sizeof(struct textpack)),
				n+j, (long)(n+j+sizeof(struct textpack)),
				fname);
			return (FALSE);
			}
		}
	}
	setuptextdata(bp, len);
	free(bp);

	return (TRUE);
}

LOCAL void
setuptextdata(bp, len)
	Uchar	*bp;
	int	len;
{
	int	n;
	int	i;
	int	j;
	Uchar	*p;

	if (xdebug) {
		printf("%ld packs %% 4 = %ld\n",
			(long)(len/sizeof(struct textpack)),
			(long)(len/sizeof(struct textpack)) % 4);
	}
	i = (len/sizeof(struct textpack)) % 4;
	if (i == 0) {
		n = len;
	} else if (i == 2) {
		n = 2 * len;
	} else {
		n = 4 * len;
	}
	n = (n * 4) / 3;
	p = malloc(n);
	if (p == NULL) {
		errmsg("Cannot malloc CD-Text write buffer.\n");
	}
	for (i=0, j=0; j < n;) {
		eight2six(&bp[i%len], &p[j]);
		i += 3;
		j += 4;
	}
	textsub = p;
	textlen = n;

#ifdef	DEBUG
	{
	Uchar	sbuf[10000];
	struct textpack *tp;
	FILE		*f;
	int		crc;

	tp = (struct textpack*)bp;
	p = sbuf;
	for (n=0; n < len; n += sizeof(struct textpack), tp++) {
		crc = (tp->crc[0] & 0xFF) << 8 | (tp->crc[1] & 0xFF);
		crc ^= 0xFFFF;

		printf("Pack:%3d ", n/ sizeof(struct textpack));
		printf("Pack type: %02X ", tp->pack_type & 0xFF);
		printf("Track #: %2d ", tp->track_no & 0xFF);
		printf("Sequence #:%3d ", tp->seq_number & 0xFF);
		printf("Block #:%3d ", tp->block_number & 0xFF);
		printf("CRC: %04X (%04X) ", crc, calcCRC((Uchar *)tp, sizeof(*tp)-2));
		printf("Text: '%.12s'\n", tp->text);
		movebytes(tp->text, p, 12);
		p += 12;
	}
	printf("len total: %d\n", n);
	f = fileopen("cdtext.out", "wctb");
	if (f) {
		filewrite(f, sbuf, p - sbuf);
		fflush(f);
		fclose(f);
	}
	}
#endif
}

LOCAL BOOL
cdtext_crc_ok(p)
	struct textpack *p;
{
	int		crc;
	struct textpack	new;

	movebytes(p, &new, sizeof(struct textpack));
	new.crc[0] ^= 0xFF;
	new.crc[1] ^= 0xFF;
	crc = calcCRC((Uchar *)&new, sizeof(struct textpack));
	crc = flip_crc_error_corr((Uchar *)&new, sizeof(struct textpack), crc);
	new.crc[0] ^= 0xFF;
	new.crc[1] ^= 0xFF;
	if (crc == 0)
		movebytes(&new, p, 18);

	return (crc == 0);
}


EXPORT void
packtext(tracks, trackp)
	int	tracks;
	track_t	*trackp;
{
	int	type;
	int	i;
	struct textpack *tp;
	struct textsizes tsize;
	txtarg_t targ;
	char	sbuf[256*18];

	fillbytes(sbuf, sizeof(sbuf), 0);
	fillbytes(&tsize, sizeof(tsize), 0);

	tsize.charcode		= CC_8859_1;		/* ISO-8859-1	     */
	tsize.first_track	= trackp[1].trackno;
	tsize.last_track	= trackp[1].trackno + tracks - 1;
#ifdef	__FOUND_ON_COMMERCIAL_CD__
	tsize.copyr_flags	= 3;			/* for titles/names */
#else
	tsize.copyr_flags	= 0;			/* no Copyr. limitat.*/
#endif
	tsize.pack_count[0x0F]	= 3;			/* 3 size packs	     */
	tsize.last_seqnum[0]	= 0;			/* Start value only  */
	tsize.language_codes[0]	= LANG_ENGLISH;		/* English	     */

	tp = (struct textpack *)sbuf;

	targ.tp = tp;
	targ.p = NULL;
	targ.tsize = &tsize;
	targ.seqno = 0;

	for (type=0; type <= 0x0E; type++) {
		register int	maxtrk;
		register char	*s;

		if (!anytext(type, tracks, trackp))
			continue;
		maxtrk = tsize.last_track;
		if (type == 6) {
			maxtrk = 0;
		}
		for (i=0; i <= maxtrk; i++) {
			s = trackp[i].text;
			if (s)
				s = ((textptr_t *)s)->textcodes[type];
			if (s)
				fillpacks(&targ, s, strlen(s)+1, i, 0x80| type);
			else
				fillpacks(&targ, "", 1, i, 0x80| type);

		}
		fillup_pack(&targ);
	}

	/*
	 * targ.seqno overshoots by one and we add 3 size packs...
	 */
	tsize.last_seqnum[0] = targ.seqno + 2;

	for (i=0; i < 3; i++) {
		fillpacks(&targ, &((char *)(&tsize))[i*12], 12, i, 0x8f);
	}

	setuptextdata((Uchar *)sbuf, targ.seqno*18);

#ifdef	DEBUG
	{	FILE	*f;

	f = fileopen("cdtext.new", "wctb");
	if (f) {
		filewrite(f, sbuf, targ.seqno*18);
		fflush(f);
		fclose(f);
	}
	}
#endif
}

LOCAL BOOL
anytext(pack_type, tracks, trackp)
	int		pack_type;
	int	tracks;
	track_t	*trackp;
{
	register int	i;
	register char	*p;

	for (i=0; i <= tracks; i++) {
		if (trackp[i].text == NULL)
			continue;
		p = ((textptr_t *)(trackp[i].text))->textcodes[pack_type];
		if (p != NULL && *p != '\0')
			return (TRUE);
	}
	return (FALSE);
}

LOCAL void
fillup_pack(ap)
	register txtarg_t *ap;
{
	if (ap->p) {
		fillbytes(ap->p, &ap->tp->text[12] - ap->p, '\0');
		fillcrc((Uchar *)ap->tp, sizeof(*ap->tp));
		ap->p  = 0;
		ap->tp++;
	}
}

LOCAL void
fillpacks(ap, from, len, track_no, pack_type)
	register txtarg_t	*ap;
	register char		*from;
	register int		len;
	register int		track_no;
	register int		pack_type;
{
	register int		charpos;
	register char		*p;
	register txtpack_t	*tp;

	tp = ap->tp;
	p  = ap->p;
	charpos = 0;
	do {
		if (p == 0) {
			p = tp->text;
			tp->pack_type = pack_type;
			if (pack_type != 0x8f)
				ap->tsize->pack_count[pack_type & 0x0F]++;
			tp->track_no = track_no;
			tp->seq_number = ap->seqno++;
			if (charpos < 15)
				tp->block_number = charpos;
			else
				tp->block_number = 15;
		}
		for (; --len >= 0 && p < &tp->text[12]; charpos++) {
			*p++ = *from++;
		}
		len++;	/* Overshoot compensation */

		if (p >= &tp->text[12]) {
			fillcrc((Uchar *)tp, sizeof(*tp));
			p = 0;
			tp++;
		}
	} while (len > 0);

	ap->tp = tp;
	ap->p = p;
}

EXPORT int
write_cdtext(scgp, dp, startsec)
	SCSI	*scgp;
	cdr_t	*dp;
	long	startsec;
{
	char	*bp = (char *)textsub;
	int	buflen = textlen;
	long	amount;
	long	bytes = 0;
	long	end = -150;
	int	secspt = textlen / 96;
	int	bytespt = textlen;
	long	maxdma = scgp->maxbuf;
	int	idx;
	int	secs;
	int	nbytes;

/*maxdma = 4320;*/
	if (maxdma >= (2*textlen)) {
		/*
		 * Try to make each CD-Text transfer use as much data
		 * as possible.
		 */
		bp = scgp->bufptr;
		for (idx = 0; (idx + textlen) <= maxdma; idx += textlen)
			movebytes(textsub, &bp[idx], textlen);
		buflen = idx;
		secspt = buflen / 96;
		bytespt = buflen;
/*printf("textlen: %d buflen: %d secspt: %d\n", textlen, buflen, secspt);*/
	} else if (maxdma < buflen) {
		/*
		 * We have more CD-Text data than we may transfer at once.
		 */
		secspt = maxdma / 96;
		bytespt = secspt * 96;
	}
	while (startsec < end) {
		if ((end - startsec) < secspt) {
			secspt = end - startsec;
			bytespt = secspt * 96;
		}
		idx = 0;
		secs = secspt;
		nbytes = bytespt;
		do {			/* loop over CD-Text data buffer */

			if ((idx + nbytes) > buflen) {
				nbytes = buflen - idx;
				secs = nbytes / 96;
			}
/*printf("idx: %d nbytes: %d secs: %d startsec: %ld\n",*/
/*idx, nbytes, secs, startsec);*/
			amount = write_secs(scgp, dp,
				(char *)&bp[idx], startsec, nbytes, secs, FALSE);
			if (amount < 0) {
				printf("write CD-Text data: error after %ld bytes\n",
						bytes);
				return (-1);
			}
			bytes += amount;
			idx += amount;
			startsec += secs;
		} while (idx < buflen && startsec < end);
	}
	return (0);
}


/*
 * 3 input bytes (8 bit based) are converted into 4 output bytes (6 bit based).
 */
LOCAL void
eight2six(in, out)
	register Uchar	*in;
	register Uchar	*out;
{
	register int	c;

	c = in[0];
	out[0]  = (c >> 2) & 0x3F;
	out[1]  = (c & 0x03) << 4;

	c = in[1];
	out[1] |= (c & 0xF0) >> 4;
	out[2]  = (c & 0x0F) << 2;

	c = in[2];
	out[2] |= (c & 0xC0) >> 6;
	out[3]  = c & 0x3F;
}

/*
 * 4 input bytes (6 bit based) are converted into 3 output bytes (8 bit based).
 */
LOCAL void
six2eight(in, out)
	register Uchar	*in;
	register Uchar	*out;
{
	register int	c;

	c = in[0] & 0x3F;
	out[0]  = c << 2;

	c = in[1] & 0x3F;
	out[0] |= c >> 4;
	out[1]  = c << 4;

	c = in[2] & 0x3F;
	out[1] |= c >> 2;
	out[2]  = c << 6;

	c = in[3] & 0x3F;
	out[2] |= c;
}
