/* -*- indent-tabs-mode: t; tab-width: 8; c-basic-offset: 8; -*- */

#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include "error.h"
#include "options.h"
#include "transport.h"
#include "libburn.h"
#include "drive.h"
#include "sector.h"
#include "crc.h"
#include "debug.h"
#include "lec.h"
#include "toc.h"
#include "write.h"

/*static unsigned char isrc[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";*/

#define sector_common(X) d->alba++; d->rlba X;

static void uncook_subs(unsigned char *dest, unsigned char *source)
{
	int i, j, code;

	memset(dest, 0, 96);

	for (i = 0; i < 12; i++) {
		for (j = 0; j < 8; j++) {
			for (code = 0; code < 8; code++) {
				if (source[code * 12 + i] & 0x80)
					dest[j + i * 8] |= (1 << (7 - code));
				source[code * 12 + i] <<= 1;
			}
		}
	}
}

/* 0 means "same as inmode" */
static int get_outmode(struct burn_write_opts *o)
{
	if (o->write_type == BURN_WRITE_SAO)
		return 0;
	else
		switch (o->block_type) {
		case BURN_BLOCK_RAW0:
			return BURN_MODE_RAW;
		case BURN_BLOCK_RAW16:
			return BURN_MODE_RAW | BURN_SUBCODE_P16;
		case BURN_BLOCK_RAW96P:
			return BURN_MODE_RAW | BURN_SUBCODE_P96;
		case BURN_BLOCK_RAW96R:
			return BURN_MODE_RAW | BURN_SUBCODE_R96;
		case BURN_BLOCK_MODE1:
			return BURN_MODE1;
		}
	assert(0);		/* return BURN_MODE_UNIMPLEMENTED :) */
}

static void get_bytes(struct burn_track *track, int count, unsigned char *data)
{
	int valid, shortage, curr;

/* no track pointer means we're just generating 0s */
	if (!track) {
		memset(data, 0, count);
		return;
	}

/* first we use up any offset */
	valid = track->offset - track->offsetcount;
	if (valid > count)
		valid = count;

	if (valid) {
		track->offsetcount += valid;
		memset(data, 0, valid);
	}
	shortage = count - valid;

	if (!shortage)
		return;

/* Next we use source data */
	curr = valid;
	if (!track->eos) {
		valid = track->source->read(track->source, data + curr, count - curr);
	} else valid = 0;

	if (valid == -1) {
		track->eos = 1;
		valid = 0;
	}

	curr += valid;
	shortage = count - curr;

	if (!shortage)
		return;

/* Before going to the next track, we run through any tail */

	valid = track->tail - track->tailcount;
	if (valid > count - curr)
		valid = count - curr;

	if (valid) {
		track->tailcount += valid;
		memset(data + curr, 0, valid);
	}
	curr += valid;
	shortage -= valid;

	if (!shortage)
		return;

/* If we're still short, and there's a "next" pointer, we pull from that.
   if that depletes, we'll just fill with 0s.
*/
	if (track->source->next) {
		struct burn_source *src;
		printf("pulling from next track\n");
		src = track->source->next;
		valid = src->read(src, data + curr, shortage);
		if (valid > 0) {
			shortage -= valid;
			curr += valid;
		}
	}
	if (!shortage)
		return;
	memset(data + curr, 0, shortage);
}
static unsigned char *get_sector(struct burn_write_opts *opts, int inmode)
{
	struct burn_drive *d = opts->drive;
	struct buffer *out = d->buffer;
	int outmode;
	int seclen;
	unsigned char *ret;

	outmode = get_outmode(opts);
	if (outmode == 0)
		outmode = inmode;

	seclen = burn_sector_length(outmode) + burn_subcode_length(outmode);

	if (out->bytes + (seclen) >= BUFFER_SIZE) {
		int err;
		err = d->write(d, d->nwa, out);
		if (err == BE_CANCELLED)
			return NULL;
		d->nwa += out->sectors;
		out->bytes = 0;
		out->sectors = 0;
	}

	ret = out->data + out->bytes;
	out->bytes += seclen;
	out->sectors++;

	return ret;
}

/* either inmode == outmode, or outmode == raw.  anything else is bad news */
static void convert_data(struct burn_write_opts *o, struct burn_track *track,
			 int inmode, unsigned char *data)
{
	int outlen, inlen;
	int offset = -1;
	int outmode;

	outmode = get_outmode(o);
	if (outmode == 0)
		outmode = inmode;

	outlen = burn_sector_length(outmode);
	inlen = burn_sector_length(inmode);
	assert(outlen >= inlen);

	if ((outmode & BURN_MODE_BITS) == (inmode & BURN_MODE_BITS)) {
		get_bytes(track, inlen, data);
		return;
	}

	assert(outmode & BURN_MODE_RAW);

	if (inmode & BURN_MODE1)
		offset = 16;
	if (inmode & BURN_MODE_RAW)
		offset = 0;
	if (inmode & BURN_AUDIO)
		offset = 0;
	assert(offset != -1);
	get_bytes(track, inlen, data + offset);
}
static void convert_subs(struct burn_write_opts *o, int inmode,
			 unsigned char *subs, unsigned char *sector)
{
	unsigned char *out;
	int outmode;

	outmode = get_outmode(o);
	if (outmode == 0)
		outmode = inmode;
	sector += burn_sector_length(outmode);
/* XXX for sao with subs, we'd need something else... */

	switch (o->block_type) {
	case BURN_BLOCK_RAW96R:
		uncook_subs(sector, subs);
		break;

	case BURN_BLOCK_RAW16:
		memcpy(sector, subs + 12, 12);
		out = sector + 12;
		out[0] = 0;
		out[1] = 0;
		out[2] = 0;
/*XXX find a better way to deal with partially damaged P channels*/
		if (subs[2] != 0)
			out[3] = 0x80;
		else
			out[3] = 0;
		out = sector + 10;

		out[0] = ~out[0];
		out[1] = ~out[1];
		break;
	}
}

static void subcode_toc(struct burn_drive *d, int mode, unsigned char *data)
{
	unsigned char *q;
	int track;
	int crc;
	int min, sec, frame;

	track = d->toc_temp / 3;
	memset(data, 0, 96);
	q = data + 12;

	burn_lba_to_msf(d->rlba, &min, &sec, &frame);
/*XXX track numbers are BCD
a0 - 1st track ctrl
a1 - last track ctrl
a2 - lout ctrl
*/
	q[0] = (d->toc_entry[track].control << 4) + 1;
	q[1] = 0;
	if (d->toc_entry[track].point < 100)
		q[2] = dec_to_bcd(d->toc_entry[track].point);
	else
		q[2] = d->toc_entry[track].point;
	q[3] = dec_to_bcd(min);
	q[4] = dec_to_bcd(sec);
	q[5] = dec_to_bcd(frame);
	q[6] = 0;
	q[7] = dec_to_bcd(d->toc_entry[track].pmin);
	q[8] = dec_to_bcd(d->toc_entry[track].psec);
	q[9] = dec_to_bcd(d->toc_entry[track].pframe);
	crc = crc_ccitt(q, 10);
	q[10] = crc >> 8;
	q[11] = crc & 0xFF;
	d->toc_temp++;
	d->toc_temp %= (d->toc_entries * 3);
}

int sector_toc(struct burn_write_opts *o, int mode)
{
	struct burn_drive *d = o->drive;
	unsigned char *data;
	unsigned char subs[96];

	data = get_sector(o, mode);
	if (!data)
		return 0;
	convert_data(o, NULL, mode, data);
	subcode_toc(d, mode, subs);
	convert_subs(o, mode, subs, data);
	sector_headers(o, data, mode, 1);
	sector_common(++)
	return 1;
}

int sector_pregap(struct burn_write_opts *o,
		   unsigned char tno, unsigned char control, int mode)
{
	struct burn_drive *d = o->drive;
	unsigned char *data;
	unsigned char subs[96];

	data = get_sector(o, mode);
	if (!data)
		return 0;
	convert_data(o, NULL, mode, data);
	subcode_user(o, subs, tno, control, 0, NULL, 1);
	convert_subs(o, mode, subs, data);
	sector_headers(o, data, mode, 0);
	sector_common(--)
	return 1;
}

int sector_postgap(struct burn_write_opts *o,
		    unsigned char tno, unsigned char control, int mode)
{
	struct burn_drive *d = o->drive;
	unsigned char subs[96];
	unsigned char *data;

	data = get_sector(o, mode);
	if (!data)
		return 0;
	convert_data(o, NULL, mode, data);
/* use last index in track */
	subcode_user(o, subs, tno, control, 1, NULL, 1);
	convert_subs(o, mode, subs, data);
	sector_headers(o, data, mode, 0);
	sector_common(++)
	return 1;
}

static void subcode_lout(struct burn_write_opts *o, unsigned char control,
			 unsigned char *data)
{
	struct burn_drive *d = o->drive;
	unsigned char *q;
	int crc;
	int rmin, min, rsec, sec, rframe, frame;

	memset(data, 0, 96);
	q = data + 12;

	burn_lba_to_msf(d->alba, &min, &sec, &frame);
	burn_lba_to_msf(d->rlba, &rmin, &rsec, &rframe);

	if (((rmin == 0) && (rsec == 0) && (rframe == 0)) ||
	    ((rsec >= 2) && !((rframe / 19) % 2)))
		memset(data, 0xFF, 12);
	q[0] = (control << 4) + 1;
	q[1] = 0xAA;
	q[2] = 0x01;
	q[3] = dec_to_bcd(rmin);
	q[4] = dec_to_bcd(rsec);
	q[5] = dec_to_bcd(rframe);
	q[6] = 0;
	q[7] = dec_to_bcd(min);
	q[8] = dec_to_bcd(sec);
	q[9] = dec_to_bcd(frame);
	crc = crc_ccitt(q, 10);
	q[10] = crc >> 8;
	q[11] = crc & 0xFF;
}

static char char_to_isrc(char c)
{
	if (c >= '0' && c <= '9')
		return c - '0';
	if (c >= 'A' && c <= 'Z')
		return 0x11 + (c - 'A');
	if (c >= 'a' && c <= 'z')
		return 0x11 + (c - 'a');
	assert(0);
	return 0;
}

void subcode_user(struct burn_write_opts *o, unsigned char *subcodes,
		  unsigned char tno, unsigned char control,
		  unsigned char indx, struct isrc *isrc, int psub)
{
	struct burn_drive *d = o->drive;
	unsigned char *p, *q;
	int crc;
	int m, s, f, c, qmode;	/* 1, 2 or 3 */

	memset(subcodes, 0, 96);

	p = subcodes;
	if ((tno == 1) && (d->rlba == -150))
		memset(p, 0xFF, 12);

	if (psub)
		memset(p, 0xFF, 12);
	q = subcodes + 12;

	qmode = 1;
	/* every 1 in 10 we can do something different */
	if (d->rlba % 10 == 0) {
		/* each of these can occur 1 in 100 */
		if ((d->rlba / 10) % 10 == 0) {
			if (o->has_mediacatalog)
				qmode = 2;
		} else if ((d->rlba / 10) % 10 == 1) {
			if (isrc && isrc->has_isrc)
				qmode = 3;
		}
	}

	assert(qmode == 1 || qmode == 2 || qmode == 3);

	switch (qmode) {
	case 1:
		q[1] = dec_to_bcd(tno);	/* track number */
		q[2] = dec_to_bcd(indx);	/* index XXX read this shit
						   from the track array */
		burn_lba_to_msf(d->rlba, &m, &s, &f);
		q[3] = dec_to_bcd(m);	/* rel min */
		q[4] = dec_to_bcd(s);	/* rel sec */
		q[5] = dec_to_bcd(f);	/* rel frame */
		q[6] = 0;	/* zero */
		burn_lba_to_msf(d->alba, &m, &s, &f);
		q[7] = dec_to_bcd(m);	/* abs min */
		q[8] = dec_to_bcd(s);	/* abs sec */
		q[9] = dec_to_bcd(f);	/* abs frame */
		break;
	case 2:
		/* media catalog number */
		q[1] = (o->mediacatalog[0] << 4) + o->mediacatalog[1];
		q[2] = (o->mediacatalog[2] << 4) + o->mediacatalog[3];
		q[3] = (o->mediacatalog[4] << 4) + o->mediacatalog[5];
		q[4] = (o->mediacatalog[6] << 4) + o->mediacatalog[7];
		q[5] = (o->mediacatalog[8] << 4) + o->mediacatalog[9];
		q[6] = (o->mediacatalog[10] << 4) + o->mediacatalog[11];
		q[7] = o->mediacatalog[12] << 4;

		q[8] = 0;
		burn_lba_to_msf(d->alba, &m, &s, &f);
		q[9] = dec_to_bcd(f);	/* abs frame */
		break;
	case 3:
		c = char_to_isrc(isrc->country[0]);
		/* top 6 bits of [1] is the first country code */
		q[1] = c << 2;
		c = char_to_isrc(isrc->country[1]);
		/* bottom 2 bits of [1] is part of the second country code */
		q[1] += (c >> 4);
		/* top 4 bits if [2] is the rest of the second country code */
		q[2] = c << 4;

		c = char_to_isrc(isrc->owner[0]);
		/* bottom 4 bits of [2] is part of the first owner code */
		q[2] += (c >> 2);
		/* top 2 bits of [3] is the rest of the first owner code */
		q[3] = c << 6;
		c = char_to_isrc(isrc->owner[1]);
		/* bottom 6 bits of [3] is the entire second owner code */
		q[3] += c;
		c = char_to_isrc(isrc->owner[2]);
		/* top 6 bits of [4] are the third owner code */
		q[4] = c << 2;

		/* [5] is the year in 2 BCD numbers */
		q[5] = dec_to_bcd(isrc->year % 100);
		/* [6] is the first 2 digits in the serial */
		q[6] = dec_to_bcd(isrc->serial % 100);
		/* [7] is the next 2 digits in the serial */
		q[7] = dec_to_bcd((isrc->serial / 100) % 100);
		/* the top 4 bits of [8] is the last serial digit, the rest is 
		   zeros */
		q[8] = dec_to_bcd((isrc->serial / 10000) % 10) << 4;
		burn_lba_to_msf(d->alba, &m, &s, &f);
		q[9] = dec_to_bcd(f);	/* abs frame */
		break;
	}
	q[0] = (control << 4) + qmode;

	crc = crc_ccitt(q, 10);
	q[10] = crc >> 8;
	q[11] = crc & 0xff;
}

int sector_lout(struct burn_write_opts *o, unsigned char control, int mode)
{
	struct burn_drive *d = o->drive;
	unsigned char subs[96];
	unsigned char *data;

	data = get_sector(o, mode);
	if (!data)
		return 0;
	convert_data(o, NULL, mode, data);
	subcode_lout(o, control, subs);
	convert_subs(o, mode, subs, data);
	sector_headers(o, data, mode, 0);
	sector_common(++)
	return 1;
}

int sector_data(struct burn_write_opts *o, struct burn_track *t, int psub)
{
	struct burn_drive *d = o->drive;
	unsigned char subs[96];
	unsigned char *data;

	data = get_sector(o, t->mode);
	if (!data)
		return 0;
	convert_data(o, t, t->mode, data);

	if (!t->source->read_sub)
		subcode_user(o, subs, t->entry->point,
			     t->entry->control, 1, &t->isrc, psub);
	else if (!t->source->read_sub(t->source, subs, 96))
		subcode_user(o, subs, t->entry->point,
			     t->entry->control, 1, &t->isrc, psub);
	convert_subs(o, t->mode, subs, data);

	sector_headers(o, data, t->mode, 0);
	sector_common(++)
	return 1;
}

int burn_msf_to_lba(int m, int s, int f)
{
	if (m < 90)
		return (m * 60 + s) * 75 + f - 150;
	else
		return (m * 60 + s) * 75 + f - 450150;
}

void burn_lba_to_msf(int lba, int *m, int *s, int *f)
{
	if (lba >= -150) {
		*m = (lba + 150) / (60 * 75);
		*s = (lba + 150 - *m * 60 * 75) / 75;
		*f = lba + 150 - *m * 60 * 75 - *s * 75;
	} else {
		*m = (lba + 450150) / (60 * 75);
		*s = (lba + 450150 - *m * 60 * 75) / 75;
		*f = lba + 450150 - *m * 60 * 75 - *s * 75;
	}
}

int dec_to_bcd(int d)
{
	int top, bottom;

	top = d / 10;
	bottom = d - (top * 10);
	return (top << 4) + bottom;
}

void sector_headers(struct burn_write_opts *o, unsigned char *out,
		    int mode, int leadin)
{
	struct burn_drive *d = o->drive;
	unsigned int crc;
	int min, sec, frame;
	int modebyte = -1;

	if (mode & BURN_AUDIO)	/* no headers for "audio" */
		return;
	if (o->write_type == BURN_WRITE_SAO)
		return;
	if (mode & BURN_MODE1)
		modebyte = 1;

	assert(modebyte == 1);

	out[0] = 0;
	memset(out + 1, 0xFF, 10);	/* sync */
	out[11] = 0;

	if (leadin) {
		burn_lba_to_msf(d->rlba, &min, &sec, &frame);
		out[12] = dec_to_bcd(min) + 0xA0;
		out[13] = dec_to_bcd(sec);
		out[14] = dec_to_bcd(frame);
		out[15] = modebyte;
	} else {
		burn_lba_to_msf(d->alba, &min, &sec, &frame);
		out[12] = dec_to_bcd(min);
		out[13] = dec_to_bcd(sec);
		out[14] = dec_to_bcd(frame);
		out[15] = modebyte;
	}
	if (mode & BURN_MODE1) {
		crc = crc_32(out, 2064);
		out[2064] = crc & 0xFF;
		crc >>= 8;
		out[2065] = crc & 0xFF;
		crc >>= 8;
		out[2066] = crc & 0xFF;
		crc >>= 8;
		out[2067] = crc & 0xFF;
	}
	if (mode & BURN_MODE1) {
		memset(out + 2068, 0, 8);
		parity_p(out);
		parity_q(out);
	}
	scramble(out);
}

#if 0
void process_q(struct burn_drive *d, unsigned char *q)
{
	unsigned char i[5];
	int mode;

	mode = q[0] & 0xF;
/*      burn_print(12, "mode: %d : ", mode);*/
	switch (mode) {
	case 1:
/*              burn_print(12, "tno = %d : ", q[1]);
                burn_print(12, "index = %d\n", q[2]);
*/
		/* q[1] is the track number (starting at 1) q[2] is the index
		   number (starting at 0) */
#warning this is totally bogus
		if (q[1] - 1 > 99)
			break;
		if (q[2] > d->toc->track[q[1] - 1].indices) {
			burn_print(12, "new index at %d\n", d->alba);
			d->toc->track[q[1] - 1].index[q[2]] = d->alba;
			d->toc->track[q[1] - 1].indices++;
		}
		break;
	case 2:
		/* XXX dont ignore these */
		break;
	case 3:
/*              burn_print(12, "ISRC data in mode 3 q\n");*/
		i[0] = isrc[(q[1] << 2) >> 2];
/*              burn_print(12, "0x%x 0x%x 0x%x 0x%x 0x%x\n", q[1], q[2], q[3], q[4], q[5]);
                burn_print(12, "ISRC - %c%c%c%c%c\n", i[0], i[1], i[2], i[3], i[4]);
*/
		break;
	default:
		assert(0);
	}
}
#endif

/* this needs more info.  subs in the data? control/adr? */
#warning sector_identify needs to be written
int sector_identify(unsigned char *data)
{
	scramble(data);
/*
check mode byte for 1 or 2
test parity to see if it's a valid sector
if invalid, return BURN_MODE_AUDIO;
else return mode byte  (what about mode 2 formless?  heh)
*/
	return BURN_MODE1;
}