/*
 * This file has been modified for the cdrkit suite.
 *
 * The behaviour and appearence of the program code below can differ to a major
 * extent from the version distributed by the original author(s).
 *
 * For details, see Changelog file distributed with the cdrkit package. If you
 * received this file from another source then ask the distributing person for
 * a log of modifications.
 *
 */

/* @(#)wm_packet.c	1.25 04/03/01 Copyright 1995, 1997, 2001-2004 J. Schilling */
/*
 *	CDR write method abtraction layer
 *	packet writing intercace routines
 *
 *	Copyright (c) 1995, 1997, 2001-2004 J. Schilling
 */
/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 *
 * 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 <timedefs.h>
#include <standard.h>
#include <utypes.h>
#include <schily.h>

#include <usal/scsitransp.h>
#include "wodim.h"
#include "xio.h"

extern	int	debug;
extern	int	verbose;
extern	int	lverbose;

extern	char	*buf;			/* The transfer buffer */

int	write_packet_data(SCSI *usalp, cdr_t *dp, track_t *trackp);

int
write_packet_data(SCSI *usalp, cdr_t *dp, track_t *trackp)
{
	int	track = trackp->trackno;
	int	f = -1;
	int	isaudio;
	long	startsec;
	Llong	bytes_read = 0;
	Llong	bytes	= 0;
	Llong	savbytes = 0;
	int	count;
	Llong	tracksize;
	int	secsize;
	int	secspt;
	int	bytespt;
	long	amount;
	int	pad;
	int	retried;
	long	nextblock;
	int	bytes_to_read;
	BOOL	neednl	= FALSE;
	BOOL	islast	= FALSE;
	char	*bp	= buf;
	struct timeval tlast;
	struct timeval tcur;
	float	secsps = 75.0;
long bsize;
long bfree;
#define	BCAP
#ifdef	BCAP
int per;
#ifdef	XBCAP
int oper = -1;
#endif
#endif

	if (dp->cdr_dstat->ds_flags & DSF_DVD)
		secsps = 676.27;

	usalp->silent++;
	if ((*dp->cdr_buffer_cap)(usalp, &bsize, &bfree) < 0)
		bsize = -1L;
	if (bsize == 0)		/* If we have no (known) buffer, we cannot */
		bsize = -1L;	/* retrieve the buffer fill ratio	   */
	else
		dp->cdr_dstat->ds_buflow = 0;
	usalp->silent--;

	if (trackp->xfp != NULL)
		f = xfileno(trackp->xfp);

	isaudio = is_audio(trackp);
	tracksize = trackp->tracksize;
	startsec = trackp->trackstart;

	secsize = trackp->secsize;
	secspt = trackp->secspt;
	bytespt = secsize * secspt;

	pad = !isaudio && is_pad(trackp);	/* Pad only data tracks */

	if (debug) {
		printf("secsize:%d secspt:%d bytespt:%d audio:%d pad:%d\n",
			secsize, secspt, bytespt, isaudio, pad);
	}

	if (lverbose) {
		if (tracksize > 0)
			printf("\rTrack %02d:    0 of %4lld MB written.",
				track, tracksize >> 20);
		else
			printf("\rTrack %02d:    0 MB written.", track);
		flush();
		neednl = TRUE;
	}

	gettimeofday(&tlast, (struct timezone *)0);
	do {
		bytes_to_read = bytespt;
		if (tracksize > 0) {
			if ((tracksize - bytes_read) > bytespt)
				bytes_to_read = bytespt;
			else
				bytes_to_read = tracksize - bytes_read;
		}
					/* XXX next wr addr ??? */
		count = get_buf(f, trackp, startsec, &bp, bytes_to_read);
		if (count < 0)
			comerr("read error on input file\n");
		if (count == 0)
			break;
		bytes_read += count;
		if (tracksize >= 0 && bytes_read >= tracksize) {
			count -= bytes_read - tracksize;
			if (trackp->padsecs == 0 || (bytes_read/secsize) >= 300)
				islast = TRUE;
		}

		if (count < bytespt) {
			if (debug) {
				printf("\nNOTICE: reducing block size for last record.\n");
				neednl = FALSE;
			}

			if ((amount = count % secsize) != 0) {
				amount = secsize - amount;
				fillbytes(&bp[count], amount, '\0');
				count += amount;
				printf("\nWARNING: padding up to secsize.\n");
				neednl = FALSE;
			}
			if (is_packet(trackp) && trackp->pktsize > 0) {
				if (count < bytespt) {
					amount = bytespt - count;
					count += amount;
					printf("\nWARNING: padding remainder of packet.\n");
					neednl = FALSE;
				}
			}
			bytespt = count;
			secspt = count / secsize;
			if (trackp->padsecs == 0 || (bytes_read/secsize) >= 300)
				islast = TRUE;
		}

		retried = 0;
		retry:
		/* XXX Fixed-packet writes can be very slow*/
		if (is_packet(trackp) && trackp->pktsize > 0)
			usal_settimeout(usalp, 100);
		/* XXX */
		if (is_packet(trackp) && trackp->pktsize == 0) {
			if ((*dp->cdr_next_wr_address)(usalp, trackp, &nextblock) == 0) {
				/*
				 * Some drives (e.g. Ricoh MPS6201S) do not
				 * increment the Next Writable Address value to
				 * point to the beginning of a new packet if
				 * their write buffer has underflowed.
				 */
				if (retried && nextblock == startsec) {
					startsec += 7;
				} else {
					startsec = nextblock;
				}
			}
		}

		amount =  write_secs(usalp, dp, bp, startsec, bytespt, secspt, islast);
		if (amount < 0) {
			if (is_packet(trackp) && trackp->pktsize == 0 && !retried) {
				printf("%swrite track data: error after %lld bytes, retry with new packet\n",
					neednl?"\n":"", bytes);
				retried = 1;
				neednl = FALSE;
				goto retry;
			}
			printf("%swrite track data: error after %lld bytes\n",
							neednl?"\n":"", bytes);
			return (-1);
		}
		bytes += amount;
		startsec += amount / secsize;

		if (lverbose && (bytes >= (savbytes + 0x100000))) {
			int	fper;
			int	nsecs = (bytes - savbytes) / secsize;
			float	fspeed;

			gettimeofday(&tcur, (struct timezone *)0);
			printf("\rTrack %02d: %4lld", track, bytes >> 20);
			if (tracksize > 0)
				printf(" of %4lld MB", tracksize >> 20);
			else
				printf(" MB");
			printf(" written");
			fper = fifo_percent(TRUE);
			if (fper >= 0)
				printf(" (fifo %3d%%)", fper);
#ifdef	BCAP
			if (bsize > 0) {			/* buffer size known */
				usalp->silent++;
				per = (*dp->cdr_buffer_cap)(usalp, (long *)0, &bfree);
				usalp->silent--;
				if (per >= 0) {
					per = 100*(bsize - bfree) / bsize;
					if (per < 5)
						dp->cdr_dstat->ds_buflow++;
					if (per < (int)dp->cdr_dstat->ds_minbuf &&
					    (startsec*secsize) > bsize) {
						dp->cdr_dstat->ds_minbuf = per;
					}
					printf(" [buf %3d%%]", per);
#ifdef	BCAPDBG
					printf(" %3ld %3ld", bsize >> 10, bfree >> 10);
#endif
				}
			}
#endif

			tlast.tv_sec = tcur.tv_sec - tlast.tv_sec;
			tlast.tv_usec = tcur.tv_usec - tlast.tv_usec;
			while (tlast.tv_usec < 0) {
				tlast.tv_usec += 1000000;
				tlast.tv_sec -= 1;
			}
			fspeed = (nsecs / secsps) /
				(tlast.tv_sec * 1.0 + tlast.tv_usec * 0.000001);
			if (fspeed > 999.0)
				fspeed = 999.0;
			printf(" %5.1fx", fspeed);
			printf(".");
			savbytes = (bytes >> 20) << 20;
			flush();
			neednl = TRUE;
			tlast = tcur;
		}
	} while (tracksize < 0 || bytes_read < tracksize);

	if ((bytes / secsize) < 300) {
		if ((trackp->padsecs + (bytes / secsize)) < 300)
			trackp->padsecs = 300 - (bytes / secsize);
	}
	if (trackp->padsecs > 0) {
		Llong	padbytes;

		/*
		 * pad_track() is based on secsize. Compute the amount of bytes
		 * assumed by pad_track().
		 */
		padbytes = (Llong)trackp->padsecs * secsize;

		if (neednl) {
			printf("\n");
			neednl = FALSE;
		}
		if ((padbytes >> 20) > 0) {
			neednl = TRUE;
		} else if (lverbose) {
			printf("Track %02d: writing %3lld KB of pad data.\n",
						track, (Llong)(padbytes >> 10));
			neednl = FALSE;
		}
		pad_track(usalp, dp, trackp, startsec, padbytes,
					TRUE, &savbytes);
		bytes += savbytes;
		startsec += savbytes / secsize;
	}
	printf("%sTrack %02d: Total bytes read/written: %lld/%lld (%lld sectors).\n",
		neednl?"\n":"", track, bytes_read, bytes, bytes/secsize);
	return (0);
}
