/* @(#)subchan.c	1.6 01/06/20 Copyright 2000 J. Schilling */
#ifndef lint
static	char sccsid[] =
	"@(#)subchan.c	1.6 01/06/20 Copyright 2000 J. Schilling";
#endif
/*
 *	Subchannel processing
 *
 *	Copyright (c) 2000 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 <unixstd.h>
#include <standard.h>
#include <string.h>
#include <utypes.h>
#include <schily.h>

#include <scg/scsitransp.h>

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

EXPORT	int     do_leadin	__PR((int tracks, track_t *trackp));
EXPORT	int     write_leadin	__PR((SCSI *scgp, cdr_t *dp, int tracks, track_t *trackp, int leadinstart));
EXPORT	int     write_leadout	__PR((SCSI *scgp, cdr_t *dp, int tracks, track_t *trackp));
EXPORT	void	fillsubch	__PR((track_t *trackp, Uchar *sp, int secno, int nsecs));
EXPORT	void	filltpoint	__PR((Uchar *sub, int ctrl_adr, int point, msf_t *mp));
EXPORT	void	fillttime	__PR((Uchar *sub, msf_t *mp));
LOCAL	void	filldsubq	__PR((Uchar *sub, int ca, int t, int i, msf_t *mrp, msf_t *mp));
LOCAL	void	qpto16		__PR((Uchar *sub, Uchar *subq, int dop));
EXPORT	void	qpto96		__PR((Uchar *sub, Uchar *subq, int dop));
EXPORT	void	addrw		__PR((Uchar *sub, Uchar	*subrwptr));
EXPORT	void	qwto16		__PR((Uchar *subq, Uchar *subptr));
EXPORT	void	subrecodesecs	__PR((track_t *trackp, Uchar *bp, int address, int nsecs));
LOCAL	void	subinterleave	__PR((Uchar *sub));

/*#define	TEST_CRC*/
#ifdef	TEST_CRC
LOCAL	void	testcrc		__PR((void));
#endif

/*Die 96 Bits == 12 Bytes haben folgendes Aussehen:*/

struct q { 
	Uchar ctrl_adr;	/*  0 (ctrl << 4) | adr		 */
	Uchar track;	/*  1 current track #		 */
	Uchar index;	/*  2 current index #		 */
	Uchar pmin;	/*  3 Relative time minutes part */
	Uchar psec;	/*  4 Relative time seconds part */
	Uchar pframe;	/*  5 Relative time frames part  */
	Uchar zero;	/*  6 */
	Uchar amin;	/*  7 Absolute time minutes part */
	Uchar asec;	/*  8 Absolute time seconds part */
	Uchar aframe;	/*  9 Absolute time frames part  */
	Uchar crc1;	/* 10	all bits inverted. Polynom is	*/
	Uchar crc2;	/* 11	X^16 + X^12 + X^5 + 1		*/
};

EXPORT	Uchar	_subq[110][12];
EXPORT	int	_nsubh;

extern	int	lverbose;
extern	int	xdebug;

EXPORT int do_leadin(int tracks, track_t *trackp)
{
	msf_t	m;
	int	ctrl;
	int	i;
	int	toctype = trackp[0].tracktype;

	if (_nsubh) {
		if (xdebug)
			printf("Using CLONE TOC....\n");
		return (0);
	}
	if (xdebug)
		printf("Leadin TOC Type: %d\n", trackp[0].tracktype);
	if (lverbose > 1) {
		for (i=1; i <= tracks+1; i++)
			printf("Track %d start %ld\n", i, trackp[i].trackstart);
	}

#ifdef	TEST_CRC
	testcrc();
/*	exit(1);*/
#endif

	memset(_subq, 0, sizeof(_subq));

	/*
	 * Fill in point 0xA0 for first track # on disk
	 */
	ctrl = (st2mode[trackp[0].sectype & ST_MASK]) << 4;
	m.msf_min = trackp[1].trackno;
	/*
	 * Disk Type: 0 = AUDIO/DATA, 0x10 = CDI, 0x20 = XA mode 2
	 */
	m.msf_sec = toc2sess[toctype & TOC_MASK];
	m.msf_sec = from_bcd(m.msf_sec);		/* convert to BCD */
	m.msf_frame = 0;
	filltpoint(_subq[0], ctrl|0x01, 0xA0, &m);
	if (lverbose > 1)
		scg_prbytes("", _subq[0], 12);

	/*
	 * Fill in point 0xA1 for last track # on disk
	 */
	ctrl = (st2mode[trackp[tracks].sectype & ST_MASK]) << 4;
	m.msf_min = trackp[tracks].trackno;
	m.msf_sec = 0;
	m.msf_frame = 0;
	filltpoint(_subq[1], ctrl|0x01, 0xA1, &m);
	if (lverbose > 1)
		scg_prbytes("", _subq[1], 12);

	/*
	 * Fill in point 0xA2 for lead out start time on disk
	 */
	lba_to_msf(trackp[tracks+1].trackstart, &m);
	ctrl = (st2mode[trackp[tracks].sectype & ST_MASK]) << 4;
	filltpoint(_subq[2], ctrl|0x01, 0xA2, &m);
	if (lverbose > 1)
		scg_prbytes("", _subq[2], 12);

	/*
	 * Fill in track start times.
	 */
	for (i=1; i <= tracks; i++) {
		lba_to_msf(trackp[i].trackstart, &m);
		ctrl = (st2mode[trackp[i].sectype & ST_MASK]) << 4;
		filltpoint(_subq[i-1+3], ctrl|0x01, to_bcd(trackp[i].trackno), &m);	/* track n */
		if (lverbose > 1)
			scg_prbytes("", _subq[i-1+3], 12);
	}
	return (0);
}

EXPORT int
write_leadin(SCSI *scgp, cdr_t *dp, int tracks, track_t *trackp, int leadinstart)
{
	msf_t	m;
	int	i;
	int	j;
	Uchar	*bp = (Uchar*)scgp->bufptr;
	Uchar	*subp;
	Uchar	*sp;
	int	secsize;
	int	secspt;
	int	bytespt;
	long	amount;
	int	startsec;
	long	bytes = 0L;
	int	textoff = 0;
	msf_t	msf;

	secsize = trackp[0].secsize;
	secspt = trackp[0].secspt;
	bytespt = secspt * secsize;

	lba_to_msf(leadinstart, &msf);

	memset(bp, 0, bytespt);

	if (_nsubh) {
		if (xdebug)
			printf("Using CLONE LEADIN\n");
	}
	if (xdebug) {
		printf("Leadinstart: %d %d:%d/%d",
			leadinstart,
			msf.msf_min, msf.msf_sec, msf.msf_frame);
		printf(" FLAGS: 0x%X sect: %X RAW16:%d secs: %d spt: %d\n",
			trackp[0].flags, trackp[0].sectype,
			is_raw16(&trackp[0]), secsize, secspt);
	}

	startsec = leadinstart;
	sp = bp;
	subp = bp + 2352;
	for (i=leadinstart,j=0; i < -150; i++,j++) {
		/*
		 * TOC hat folgende unterschiedliche Sub Q Frames:
		 * A0		First Track
		 * A1		Last Track
		 * A3		Lead out start
		 * 1..99	Tracks
		 * ==	3 + N* tracks
		 * Jeder Frame wird 3x wiederholt.
		 */
		if (_nsubh) {
			if (j >= (3*_nsubh))
				j = 0;
		} else {
			if (j >= (3*3 + 3*tracks))
				j = 0;
		}
		lba_to_msf((long)i, &m);
		fillttime(_subq[j/3], &m);
		fillcrc(_subq[j/3], 12);
		if (xdebug > 2)
			scg_prbytes("", _subq[j/3], 12);
		if (is_raw16(&trackp[0])) {
			qpto16(subp, _subq[j/3], 0);
		} else {
			extern Uchar *textsub;
			extern int  textlen;

			qpto96(subp, _subq[j/3], 0);
			if (textsub) {
				addrw(subp, &textsub[textoff]);
				textoff += 96;
				if (textoff >= textlen)
					textoff = 0;
			}
/*			if (is_raw96p(&trackp[0]))*/
/*				subinterleave(subp);*/
		}
		if ((startsec+secspt-1) == i || i == -151) {
			if ((i-startsec+1) < secspt) {
				secspt = i-startsec+1;
				bytespt = secspt * secsize;
			}
			encsectors(trackp, bp, startsec, secspt);

			amount = write_secs(scgp, dp,
					(char *)bp, startsec, bytespt, secspt, FALSE);
			if (amount < 0) {
				printf("write leadin data: error after %ld bytes\n",
							bytes);
				return (-1);
			}
			bytes += amount;
			startsec = i+1;
			sp = bp;
			subp = bp + 2352;
			continue;
		}
		sp += secsize;
		subp += secsize;
	}
	return (0);
}

EXPORT int write_leadout(SCSI *scgp, cdr_t *dp, int tracks, track_t *trackp)
{
	msf_t	m;
	msf_t	mr;
	int	ctrl;
	int	i;
	int	j;
	Uchar	*bp = (Uchar*)scgp->bufptr;
	Uchar	*subp;
	Uchar	*sp;
	int	secsize;
	int	secspt;
	int	bytespt;
	long	amount;
	long	startsec;
	long	endsec;
	long	bytes = 0L;
	int	leadoutstart;
	Uchar	sub[12];
	BOOL	p;
	msf_t	msf;

	memset(sub, 0, 12);

	secsize = trackp[tracks+1].secsize;
	secspt = trackp[tracks+1].secspt;
	bytespt = secspt * secsize;

	leadoutstart = trackp[tracks+1].trackstart;
	lba_to_msf(leadoutstart, &msf);

	memset(bp, 0, bytespt);

	if (xdebug) {
		printf("Leadoutstart: %d %d:%d/%d amt %ld",
			leadoutstart,
			msf.msf_min, msf.msf_sec, msf.msf_frame,
			trackp[tracks+1].tracksecs);
		printf(" FLAGS: 0x%X sect: %X RAW16:%d secs: %d spt: %d\n",
			trackp[tracks+1].flags, trackp[tracks+1].sectype,
			is_raw16(&trackp[tracks+1]), secsize, secspt);
	}

	startsec = leadoutstart;
	endsec = startsec + trackp[tracks+1].tracksecs;
	sp = bp;
	subp = bp + 2352;
	ctrl = (st2mode[trackp->sectype & ST_MASK]) << 4;

	for (i=leadoutstart,j=0; i < endsec; i++,j++) {

		lba_to_msf((long)i, &m);
		sec_to_msf((long)j, &mr);
		filldsubq(sub, ctrl|0x01, 0xAA, 1, &mr, &m);
		sub[1] = 0xAA;
		fillcrc(sub, 12);
		p = (j % 150) < 75;
		if (j < 150)
			p = FALSE;
		if (xdebug > 2)
			scg_prbytes(p?(char*)"P":(char*)" ", sub, 12);

		if (is_raw16(&trackp[0])) {
			qpto16(subp, sub, p);
		} else {
			qpto96(subp, sub, p);
/*			if (is_raw96p(&trackp[0]))*/
/*				subinterleave(subp);*/
		}
		if ((startsec+secspt-1) == i || i == (endsec-1)) {
			if ((i-startsec+1) < secspt) {
				secspt = i-startsec+1;
				bytespt = secspt * secsize;
			}
			encsectors(trackp, bp, startsec, secspt);

			amount = write_secs(scgp, dp,
					(char *)bp, startsec, bytespt, secspt, FALSE);
			if (amount < 0) {
				printf("write leadout data: error after %ld bytes\n",
							bytes);
				return (-1);
			}
			bytes += amount;
			startsec = i+1;
			sp = bp;
			subp = bp + 2352;
			continue;
		}
		sp += secsize;
		subp += secsize;
	}
	return (0);
}

EXPORT void fillsubch(track_t *trackp, Uchar *sp, int secno, int nsecs)
{
	msf_t	m;
	msf_t	mr;
	int	ctrl;
	int	i;
	int	rsecno;
	int	secsize = trackp->secsize;
	int	trackno = trackp->trackno;
	int	nindex = trackp->nindex;
	int	index;
	long	*tindex = NULL;
	long	nextindex = 0L;
	Uchar	sub[12];
	Uchar	*sup;

	memset(sub, 0, 12);

	rsecno = secno - trackp->trackstart;
	sup = sp + 2352;
	ctrl = (st2mode[trackp->sectype & ST_MASK]) << 4;

/*error("Tracknl %d nindex %d trackstart %ld rsecno %d index0start %ld nsecs %d\n", trackno, nindex, trackp->trackstart, rsecno, trackp->index0start, nsecs);*/

	if (rsecno < 0) {
		int end = trackp->trackstart;
		int j;

		for (i=rsecno, j=1; i < end; i++,j++) {

			lba_to_msf((long)secno, &m);
			sec_to_msf((long)end-1-i, &mr);
			filldsubq(sub, ctrl|0x01, trackno, 0, &mr, &m);
			fillcrc(sub, 12);
			if (xdebug > 2)
				scg_prbytes("P", (Uchar *)sub, 12);
			if (is_raw16(trackp)) {
				qpto16(sup, sub, TRUE);
			} else {
				qpto96(sup, sub, TRUE);
/*				if (is_raw96p(trackp))*/
/*					subinterleave(sup);*/
			}
			if (j >= nsecs)
				return;

			secno++;
			rsecno++;
			sup += secsize;
		}
		return;
	}
	if (rsecno > trackp->index0start) {
		/*
		 * This track contains pregap of next track.
		 */
		trackno++;
		index = 0;
	} else {
		index = 1;
		if (nindex > 1) {
			tindex = trackp->tindex;
			nextindex = trackp->tracksecs;
			/*
			 * find current index in list
			 */
			for (index=nindex; index >= 1; index--) {
				if (rsecno >= tindex[index]) {
					if (index < nindex)
						nextindex = tindex[index+1];
					break;
				}
			}
		}
	}

	for (i=0; i < nsecs; i++) {

		if (tindex != NULL && rsecno >= nextindex) {
			if (index < nindex) {
				index++;
				nextindex = tindex[index+1];
			}
		}
		if (rsecno == trackp->index0start) {
			/*
			 * This track contains pregap of next track.
			 */
			trackno++;
			index = 0;
		}
		lba_to_msf((long)secno, &m);
		sec_to_msf((long)rsecno, &mr);
		filldsubq(sub, ctrl|0x01, trackno, index, &mr, &m);
		fillcrc(sub, 12);
		if (xdebug > 2)
			scg_prbytes(index==0?(char*)"P":(char*)" ", sub, 12);
		if (is_raw16(trackp)) {
			qpto16(sup, sub, index==0);
		} else {
			qpto96(sup, sub, index==0);
/*			if (is_raw96p(trackp))*/
/*				subinterleave(sup);*/
		}
		secno++;
		rsecno++;
		sup += secsize;
	}
}

/*
 * Fill TOC Point
 * Ax Werte einfüllen.
 */
EXPORT void filltpoint(Uchar *sub, int ctrl_adr, int point, msf_t *mp)
{
	sub[0] = ctrl_adr;
	sub[2] = point;
	sub[7] = to_bcd(mp->msf_min);
	sub[8] = to_bcd(mp->msf_sec);
	sub[9] = to_bcd(mp->msf_frame);
}

/*
 * Fill TOC time
 * Aktuelle Zeit in TOC Sub-Q einfüllen.
 */
EXPORT void fillttime(Uchar *sub, msf_t *mp)
{
	sub[3] = to_bcd(mp->msf_min);
	sub[4] = to_bcd(mp->msf_sec);
	sub[5] = to_bcd(mp->msf_frame);
}

/*
 * Q-Sub in Datenbereich füllen.
 */
LOCAL void filldsubq(Uchar *sub, int ca, int t, int i, msf_t *mrp, msf_t *mp)
{
	sub[0] = ca;
	sub[1] = to_bcd(t);
	sub[2] = to_bcd(i);
	sub[3] = to_bcd(mrp->msf_min);
	sub[4] = to_bcd(mrp->msf_sec);
	sub[5] = to_bcd(mrp->msf_frame);

	sub[7] = to_bcd(mp->msf_min);
	sub[8] = to_bcd(mp->msf_sec);
	sub[9] = to_bcd(mp->msf_frame);
}

/*
 * Q-Sub auf 16 Bytes blähen und P-Sub addieren
 *
 * OUT: sub, IN: subqptr 
 */
LOCAL void qpto16(Uchar *sub, Uchar *subqptr, int dop)
{
	if (sub != subqptr)
		memmove(sub, subqptr, 12);
	sub[12] = '\0';
	sub[13] = '\0';
	sub[14] = '\0';
	sub[15] = '\0';
	if (dop)
		sub[15] |= 0x80;

}

/*
 * Q-Sub auf 96 Bytes blähen und P-Sub addieren
 *
 * OUT: sub, IN: subqptr 
 */
EXPORT void qpto96(Uchar *sub, Uchar *subqptr, int dop)
{
	Uchar	tmp[16];
	Uchar	*p;
	int	c;
	int	i;

	if (subqptr == sub) {
		/*
		 * Remember 12 byte subchannel data if subchannel
		 * is overlapping.
		 */
		memmove(tmp, subqptr, 12);
		subqptr = tmp;
	}
	/*
	 * Clear all subchannel bits in the 96 byte target space.
	 */
	memset(sub, 0, 96);

	if (dop) for (i=0, p = sub; i < 96; i++) {
		*p++ |= 0x80;
	}
	for (i=0, p = sub; i < 12; i++) {
		c = subqptr[i] & 0xFF;
/*printf("%02X\n", c);*/
		if (c & 0x80)
			*p++ |= 0x40;
		else
			p++;
		if (c & 0x40)
			*p++ |= 0x40;
		else
			p++;
		if (c & 0x20)
			*p++ |= 0x40;
		else
			p++;
		if (c & 0x10)
			*p++ |= 0x40;
		else
			p++;
		if (c & 0x08)
			*p++ |= 0x40;
		else
			p++;
		if (c & 0x04)
			*p++ |= 0x40;
		else
			p++;
		if (c & 0x02)
			*p++ |= 0x40;
		else
			p++;
		if (c & 0x01)
			*p++ |= 0x40;
		else
			p++;
	}
}

/*
 * Add R-W-Sub (96 Bytes) to P-Q-Sub (96 Bytes)
 *
 * OUT: sub, IN: subrwptr 
 */
EXPORT void addrw(register Uchar *sub, register Uchar *subrwptr)
{
	register int	i;

#define	DO8(a)	a;a;a;a;a;a;a;a;

	for (i=0; i < 12; i++) {
		DO8(*sub++ |= *subrwptr++ & 0x3F);
	}
}

/*
 * Q-W-Sub (96 Bytes) auf 16 Bytes schrumpfen
 *
 * OUT: subq, IN: subptr 
 */
EXPORT void qwto16(Uchar *subq, Uchar *subptr)
{
	register int	i;
	register int	np = 0;
	register Uchar	*p;
		 Uchar	tmp[96];

	p = subptr;
	for (i=0; i < 96; i++)
		if (*p++ & 0x80)
			np++;
	p = subptr;
	if (subptr == subq) {
		/*
		 * Remember 96 byte subchannel data if subchannel
		 * is overlapping.
		 */
		memmove(tmp, subptr, 96);
		p = tmp;
	}

	for (i=0; i < 12; i++) {
		subq[i] = 0;
		if (*p++ & 0x40)
			subq[i] |= 0x80;
		if (*p++ & 0x40)
			subq[i] |= 0x40;
		if (*p++ & 0x40)
			subq[i] |= 0x20;
		if (*p++ & 0x40)
			subq[i] |= 0x10;
		if (*p++ & 0x40)
			subq[i] |= 0x08;
		if (*p++ & 0x40)
			subq[i] |= 0x04;
		if (*p++ & 0x40)
			subq[i] |= 0x02;
		if (*p++ & 0x40)
			subq[i] |= 0x01;
	}
	subq[12] = 0;
	subq[13] = 0;
	subq[14] = 0;
	if (np > (96/2))
		subq[15] = 0x80;
}

/*
 * Recode subchannels of sectors from 2352 + 96 bytes to 2352 + 16 bytes
 */
EXPORT void subrecodesecs(track_t *trackp, Uchar *bp, int address, int nsecs)
{
	bp += 2352;
	while (--nsecs >= 0) {
		qwto16(bp, bp);
		bp += trackp->isecsize;
	}
}

#ifndef	HAVE_LIB_EDC_ECC
EXPORT void encsectors(track_t *trackp, Uchar *bp, int address, int nsecs)
{
	int	sectype = trackp->sectype;

	if ((sectype & ST_MODE_MASK) == ST_MODE_AUDIO)
		return;

	comerrno(EX_BAD, "Can only write audio sectors in RAW mode.\n");
}

EXPORT void scrsectors(track_t *trackp, Uchar *bp, int address, int nsecs)
{
	comerrno(EX_BAD, "Cannot write in clone RAW mode.\n");
}
#endif

/*--------------------------------------------------------------------------*/
#ifdef	TEST_CRC

Uchar	tq[12] = { 0x01, 0x00, 0xA0, 0x98, 0x06, 0x12, 0x00, 0x01, 0x00, 0x00, 0xE3, 0x74 };

/*
01 00 A0 98 06 12 00 01 00 00 E3 74 
01 00 A0 98 06 13 00 01 00 00 49 25 
01 00 A1 98 06 14 00 13 00 00 44 21 
01 00 A1 98 06 15 00 13 00 00 EE 70 
01 00 A1 98 06 16 00 13 00 00 00 A2 
01 00 A2 98 06 17 00 70 40 73 E3 85 
01 00 A2 98 06 18 00 70 40 73 86 7C 
01 00 A2 98 06 19 00 70 40 73 2C 2D 
01 00 01 98 06 20 00 00 02 00 3B 71 
01 00 01 98 06 21 00 00 02 00 91 20 
01 00 01 98 06 22 00 00 02 00 7F F2 
01 00 02 98 06 23 00 03 48 45 BE E0 
01 00 02 98 06 24 00 03 48 45 D9 34 

*/

LOCAL	int	b	__PR((int bcd));


LOCAL int b(int bcd)
{
	return ((bcd & 0x0F) + 10 * (((bcd)>> 4) & 0x0F));
}

LOCAL void testcrc()
{
	struct q q;
	int	ocrc;
	int	crc1;
	int	crc2;

	memcpy(&q, &tq, 12);
	crc1 = q.crc1 & 0xFF;
	crc2 = q.crc2 & 0xFF;

	/*
	 * Per RED Book, CRC Bits on disk are inverted.
	 * Invert them again to make calcCRC() work.
	 */
	q.crc1 ^= 0xFF;
	q.crc2 ^= 0xFF;

	ocrc = calcCRC((Uchar *)&q, 12);
	printf("AC: %02X t: %3d (%02X) i: %3d (%02X) %d:%d:%d %d:%d:%d %02X%02X %X (%X)\n",
		q.ctrl_adr,
		b(q.track),
		b(q.track) & 0xFF,
		b(q.index),
		q.index & 0xFF,
		b(q.pmin),
		b(q.psec),
		b(q.pframe),
		b(q.amin),
		b(q.asec),
		b(q.aframe),
		crc1, crc2,
		ocrc,
		fillcrc((Uchar *)&q, 12) & 0xFFFF);
}
#endif

#ifdef	sss
96 / 24 = 4

index 1 < - > 18
index 2 < - > 5
index 3 < - > 23

delay index mod 8
#endif

/*
 * Sub 96 Bytes Interleave
 */
LOCAL void subinterleave(Uchar *sub)
{
	Uchar	*p;
	int	i;

	for (i=0, p = sub; i < 4; i++) {
		Uchar	save;

		/*
		 * index 1 < - > 18
		 * index 2 < - > 5
		 * index 3 < - > 23
		 */
		save  = p[1];
		p[1]  = p[18];
		p[18] = save;

		save  = p[2];
		p[2]  = p[5];
		p[5]  = save;

		save  = p[3];
		p[3]  = p[23];
		p[23] = save;

		p += 24;
	}
}
