/*
 * $RCSfile: lib_main.cpp,v $
 * $Author: swaj $
 * $Revision: 1.10 $
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <stdlib.h>

#include <linux/cdrom.h>

#include "defines.h"
#include "debugging.h"
#include "lib_time.h"
#include "lib_cdinfo.h"
#include "lib_main.h"


Ccdrom::Ccdrom ()
{
	// printf("Ccdrom::Ccdrom()");
	Reset();

	strncpy (cdrom_dev, DEFAULT_CDROM_DEV,  FILE_MAX);
}

Ccdrom::Ccdrom (const char *a_cdrom_dev)
{
	// printf("Ccdrom::Ccdrom()");

	Reset();

	if (cdrom_dev)
		strncpy (cdrom_dev, a_cdrom_dev, FILE_MAX);
	else
		strncpy (cdrom_dev, DEFAULT_CDROM_DEV, FILE_MAX);
}

Ccdrom::~Ccdrom ()
{
	// printf("Ccdrom::~Ccdrom()");
	if (fd > 0) {
		close (fd);
		fd = -1;
	}
	ClosePlotFile ();
	DeallocateTimings();
}

void Ccdrom::Reset ()
{
	fd = -1;
	enable_info = false;
	memset (cdrom_dev, 0x0, FILE_MAX);
	memset (last_err, 0x0, ERR_STR_MAX);
	pf = 0;
}

const char * Ccdrom::Error(void)
{
	return last_err;
}

void Ccdrom::EnableInfo(bool a_enable_info)
{
	// printf("Ccdrom::EnableInfo(bool a_enable_info = %d", (int)a_enable_info);
	enable_info =  a_enable_info;
}

int Ccdrom::ReadTOC()
{
	// printf("Ccdrom::ReadTOC()");
	// printf("opening '%s'", cdrom_dev);
	fd = open (cdrom_dev, O_RDONLY|O_LARGEFILE);

	if (fd < 0) {
		snprintf (last_err, ERR_STR_MAX,
			  "unable to open cdrom '%s', reason: %s", cdrom_dev, strerror(errno) );
		return RET_FAILED;
	}

	if ( ioctl (fd, CDROMREADTOCHDR, &toc_header) ) {
		snprintf (last_err, ERR_STR_MAX, "unable to read TOC, reason: %s", strerror(errno) );
		return RET_FAILED;
	}

	if (enable_info) {
		fprintf (stdout, "Track list (%i-%i):\n", toc_header[0],toc_header[1] );
	}

	struct cdrom_tocentry toc;
	struct cdrom_tocentry tocs[CDROM_LEADOUT+1];
	memset (&tocs, 0x0, sizeof (tocs));

	int sectors=-1;
	int first_data = -1;
	int num_data = 0;
	int first_audio = -1;
	int num_audio = 0;

	for (int i = toc_header[0]; i <= CDROM_LEADOUT; i++) {

		memset (&toc, 0x0, sizeof(struct cdrom_tocentry));
		toc.cdte_track  = i;
		toc.cdte_format = CDROM_MSF;

		if (ioctl(fd, CDROMREADTOCENTRY,& toc )) {
			snprintf (last_err, ERR_STR_MAX,
				  "read TOC entry ioctl failed for track %i, reason: %s",
				  toc.cdte_track, strerror(errno) );
			return RET_FAILED;
		}

		memcpy (&tocs[i], &toc, sizeof (struct cdrom_tocentry)); 

		if (enable_info) {
			fprintf (stdout, "%3d: %02d:%02d:%02d (sec: %06d) %s%s\n",
				 toc.cdte_track,
				 toc.cdte_addr.msf.minute,
				 toc.cdte_addr.msf.second,
				 toc.cdte_addr.msf.frame,

				 toc.cdte_addr.msf.frame +
				 toc.cdte_addr.msf.second*75 +
				 toc.cdte_addr.msf.minute*75*60 - 150,

				 (toc.cdte_ctrl & CDROM_DATA_TRACK) ? "data ":"audio",
				CDROM_LEADOUT == i ? "(leadout)" : "" 
				);

		}

		if (i == CDROM_LEADOUT) {
			if (toc.cdte_ctrl & CDROM_DATA_TRACK)
				sectors = toc.cdte_addr.msf.frame +
					toc.cdte_addr.msf.second*75 +
					toc.cdte_addr.msf.minute*75*60 - 150;

			break;
		}

		if (toc.cdte_ctrl & CDROM_DATA_TRACK) {
			num_data++;
			if (first_data == -1)
				first_data = toc.cdte_track;
		} else {
			num_audio++;
			if (first_audio == -1)
				first_audio = toc.cdte_track;
		}

		// skip to leadout
		if (i == (toc_header[1]) ) {
			i = CDROM_LEADOUT-1;
		}
	}

	sectors_tot = sectors;

	// printf("sectors = %d", sectors); 

	/* get mcn */
	/*
	if (enable_info) {
		fprintf(stderr, "mcn: "); fflush (stdout);
	}
	struct cdrom_mcn mcn;
	if (ioctl(fd, CDROM_GET_MCN, &mcn)) {
		if (enable_info)
			fprintf (stdout, "failed, reason: %s\n", strerror(errno) );
	} else {
		if (enable_info)
			fprintf (stdout, "%s\n", (char*)mcn.medium_catalog_number );
	}
	*/

	/* get disk status */
	if (enable_info) {
		fprintf(stdout, "\nDisc status: "); fflush(stdout);
	}
	
	int rvs = ioctl (fd, CDROM_DISC_STATUS,0);
	// printf("rvs = %d", rvs);
	if (enable_info) {
		switch (rvs) {
		case CDS_NO_INFO: fprintf (stdout, "no info"); break;
		case CDS_NO_DISC: fprintf (stdout, "no disc"); break;
		case CDS_AUDIO:   fprintf (stdout, "audio"); break;
		case CDS_DATA_1:  fprintf (stdout, "data mode 1"); break;
		case CDS_DATA_2:  fprintf (stdout, "data mode 2"); break;
		case CDS_XA_2_1:  fprintf (stdout, "XA mode 1"); break;
		case CDS_XA_2_2:  fprintf (stdout, "XA mode 2"); break;
		default:          fprintf (stdout, "unknown (failed?)");
		}
		fprintf (stdout, "\n");
	}
	disc_status = rvs;

	//------------------------ start extinfo

	isofs_size = 0;
	int start_track=0;
	int ms_offset=0;
	int data_start=0;
	int fs=0;
	int need_lf=0;

	if (enable_info) {
		//Multisession 
		struct cdrom_multisession  ms;
		fprintf (stdout, "Multisession: "); fflush(stdout);
		ms.addr_format = CDROM_LBA;

		if (ioctl (fd ,CDROMMULTISESSION,&ms))
			fprintf (stdout, "failed to get, reason: %s\n", strerror(errno) );
		else
			fprintf (stdout,"%d%s\n",ms.addr.lba,ms.xa_flag?" XA":"");


		//* get audio status
		struct cdrom_subchnl sub;
		fprintf (stdout, "Audio status: "); fflush(stdout);
		sub.cdsc_format = CDROM_MSF;

		if (ioctl (fd, CDROMSUBCHNL, &sub))
			fprintf (stdout, "failed to get, reason: %s\n", strerror(errno) );
		else {
			switch (sub.cdsc_audiostatus) {
			case CDROM_AUDIO_INVALID:    fprintf (stdout, "invalid\n");   break;
			case CDROM_AUDIO_PLAY:       fprintf (stdout, "playing");     break;
			case CDROM_AUDIO_PAUSED:     fprintf (stdout, "paused");      break;
			case CDROM_AUDIO_COMPLETED:  fprintf (stdout, "completed\n"); break;
			case CDROM_AUDIO_ERROR:      fprintf (stdout, "error\n");     break;
			case CDROM_AUDIO_NO_STATUS:  fprintf (stdout, "no status\n"); break;
			default:                     fprintf (stdout, "Oops: unknown\n");
			}
			if (sub.cdsc_audiostatus == CDROM_AUDIO_PLAY ||
			    sub.cdsc_audiostatus == CDROM_AUDIO_PAUSED) {
				printf(" at: %02d:%02d abs / %02d:%02d track %d\n",
				       sub.cdsc_absaddr.msf.minute,
				       sub.cdsc_absaddr.msf.second,
				       sub.cdsc_reladdr.msf.minute,
				       sub.cdsc_reladdr.msf.second,
				       sub.cdsc_trk);
			}
		}

		
		fprintf (stdout, "\nTry to find out what sort of CD this is...\n");
		// try to find out what sort of CD we have
				
		if (0 == num_data) {

			// no data track, may be a "real" audio CD or hidden track CD
			start_track = (int)tocs[1].cdte_addr.msf.frame +
				(int)tocs[1].cdte_addr.msf.second*75 + 
				(int)tocs[1].cdte_addr.msf.minute*75*60 - 150;
			// CD-I/Ready says start_track <= 30*75 then CDDA
			if (start_track > 100 /* 100 is just a guess */) {

		fs = guess_filesystem(0);
		if ((fs & FS_MASK) != FS_UNKNOWN)
			fs |= HIDDEN_TRACK;
		else {
			fs &= ~FS_MASK; /* del filesystem info */
			printf("Oops: %i unused sectors at start, but hidden track check failed.\n",start_track);
		}
	}
		} else {
			// we have data track(s)
			for (int j = 2, i = first_data; i <= toc_header[1]; i++) {
				if (!(tocs[i].cdte_ctrl & CDROM_DATA_TRACK))
					break;
				start_track = (i == 1) ? 0 :
					(int)tocs[i].cdte_addr.msf.frame +
					(int)tocs[i].cdte_addr.msf.second*75 + 
					(int)tocs[i].cdte_addr.msf.minute*75*60
					-150;
				// save the start of the data area
				if (i == first_data)
					data_start = start_track;
	    
				// skip tracks which belong to the current walked session
				if (start_track < data_start + isofs_size)
					continue;

				fs = guess_filesystem(start_track);
				if (!(((fs & FS_MASK) == FS_ISO_9660 ||
				       (fs & FS_MASK) == FS_ISO_HFS ||
				       (fs & FS_MASK) == FS_ISO_9660_INTERACTIVE) && (fs & XA)))
					break;	// no method for non-iso9660 multisessions

				if (i > 1) {
					// track is beyond last session -> new session found
					ms_offset = start_track;
					printf("session #%d starts at track %2i, offset %6i, isofs size %6i\n",
					       j++,tocs[i].cdte_track,start_track,isofs_size);
					fs |= MULTISESSION;
				}
			}
		}
	
	switch(fs & FS_MASK) {
	case FS_NO_DATA:
		if (num_audio > 0)
			printf("Audio CD\n");
		break;
	case FS_ISO_9660:
		printf("CD-ROM with iso9660 fs\n");
		break;
	case FS_ISO_9660_INTERACTIVE:
		printf("CD-ROM with CD-RTOS and iso9660 fs\n");
		break;
	case FS_HIGH_SIERRA:
		printf("CD-ROM with high sierra fs\n");
		break;
	case FS_INTERACTIVE:
		printf("CD-Interactive%s\n", num_audio > 0 ? "/Ready" : "");
		break;
	case FS_HFS:
		printf("CD-ROM with Macintosh HFS\n");
		break;
	case FS_ISO_HFS:
		printf("CD-ROM with both Macintosh HFS and iso9660 fs\n");
		break;
	case FS_UFS:
		printf("CD-ROM with Unix UFS\n");
		break;
	case FS_EXT2:
		printf("CD-ROM with Linux second extended filesystem\n");
		break;
	case FS_UNKNOWN:
		printf("CD-ROM with unknown filesystem\n");
		break;
	}

	switch(fs & FS_MASK) {
	case FS_ISO_HFS:
#define PRINT_ISO9660 printf("iso9660: %i MB size, label '%.32s'\n", isofs_size/512,buffer+40);
		PRINT_ISO9660;
		break;

	case FS_ISO_9660:
	case FS_ISO_9660_INTERACTIVE:
		PRINT_ISO9660;
		if (strlen(sbuffer3)>1)
			printf ("Creating software: '%.80s'\n", sbuffer3);

		if (strlen(sbuffer)>1)
			printf ("Publisher: '%.80s'\n", sbuffer);

		if (strlen(sbuffer2)>1)
			printf ("Preparer: '%.80s'\n", sbuffer2);

		break;
	}

	need_lf = 0;
	if (first_data == 1 && num_audio > 0)
		need_lf += printf("mixed mode CD   ");
	if (fs & XA)
		need_lf += printf("XA sectors   ");
	if (fs & MULTISESSION)
		need_lf += printf("Multisession, offset = %i   ",ms_offset);
	if (fs & HIDDEN_TRACK)
		need_lf += printf("Hidden Track   ");
	if (fs & PHOTO_CD)
		need_lf += printf("%sPhoto CD   ", num_audio > 0 ? " Portfolio " : "");
	if (fs & CDTV)
		need_lf += printf("Commodore CDTV   ");
	if (first_data > 1)
		need_lf += printf("CD-Plus/Extra   ");
	if (fs & BOOTABLE)
		need_lf += printf("bootable CD   ");
	if (fs & VIDEOCDI && num_audio == 0)
		need_lf += printf("Video CD   ");
	if (need_lf) puts("");
	}

	//------------------------ end extinfo
	
	//printf ("s = %d %s \n", s, strerror(errno) );
	return RET_SUCCESS;
}

int Ccdrom::OpenPlotFile (const char *plotfile)
{
	// printf("Ccdrom::OpenPlotFile(const char *)")
	pf = fopen (plotfile, "w+");
	return (pf) ? RET_SUCCESS : RET_FAILED;
}

void Ccdrom::ClosePlotFile ()
{
	// printf("Ccdrom::ClosePlotFile ()");
	if (pf) {
		fclose (pf);
		pf = NULL;
	}
}

int Ccdrom::ReadCD ()
{
	//DBG ("Ccdrom::ReadCD\n");

	if (disc_status != CDS_DATA_1) {
		// printf("disc_status != CDS_DATA_1");
		snprintf (last_err, ERR_STR_MAX, "disc_status != CDS_DATA_1");		
		return RET_FAILED;
	}

	if (sectors_tot <= 0) {
		// printf("sectors_tot <= 0");
		snprintf (last_err, ERR_STR_MAX, "sectors_tot <= 0");		
		return RET_FAILED;
	}

	int s  = (int) ( (double)lseek (fd, 0, SEEK_END) / 2048.00);
	lseek (fd, 0, SEEK_SET);

	if (s != sectors_tot) {
		fprintf (stdout,
			 "\n! TOC and lseek() return different information about size "
			 ", using lseek()'s number of sectors which is %d\n\n", s);  
 		sectors_tot = s;
	}

	///DEBUG
	//sectors_tot = 50;

	fprintf (stdout, "Reading sectors 1-%d\n", sectors_tot);

	char buf[ SECTOR_SIZE];

	if (lseek ( fd, 0, SEEK_SET) < 0) {
		// printf("lseek error: %s", strerror(errno) );
		snprintf (last_err, ERR_STR_MAX, "lseek error: %s", strerror(errno) );
		return RET_FAILED;
	}
	for (int i=0; i< 10; i++ ) {
		 read (fd, buf,  SECTOR_SIZE);
	}
	if (lseek ( fd, 0, SEEK_SET) < 0) {
		// printf("lseek error: %s", strerror(errno) );
		snprintf (last_err, ERR_STR_MAX, "lseek error: %s", strerror(errno) );
		return RET_FAILED;
	}

	int rv;
	struct timeval t1, t2, td;

	DeallocateTimings();
	timings = (int*) smalloc ( sizeof(int) * sectors_tot );

	if (!timings) {
		// printf("malloc error");
		snprintf (last_err, ERR_STR_MAX, "malloc error");
		return RET_FAILED;
	}

	for (int i=0; i< sectors_tot; i++ ) {

		gettimeofday(&t1, NULL);
		rv = read (fd, buf,  SECTOR_SIZE);
		gettimeofday(&t2, NULL);

		//// printf("%d.%d -- %d.%d", t2.tv_sec, t2.tv_usec, t1.tv_sec, t1.tv_usec ); 

		time_substract_timeval (&t2, &t1, &td);
		timings[i] = time_get_time_usec (&td);
		//printf ("T>%d<\n", tt );

		if (rv != SECTOR_SIZE) {
			// printf("read error: %s", strerror(errno) );
			snprintf (last_err, ERR_STR_MAX, "read error: %s", strerror(errno) ); 
			timings[i] = SECTOR_ERROR;
			fprintf (stdout, "! unable to read sector %d, reason: %s \n", i+1, strerror(errno)); 
			lseek (fd, (off_t)((double)(i+1) * 2048.0), SEEK_SET);
		} else {
			if ( (i & 0x3) == 0x3   ) {
				fprintf (stdout, "%d ok\r", i+1); 
			}
		}
	}

	fprintf (stdout, "\n\n"); 
	return RET_SUCCESS;
}

void Ccdrom::WritePlotData()
{
	if (! timings  || !pf )
		return;

	fprintf (pf, "# sector -- read time (usec)\n");
	
	int t;
	for (int i=0; i< sectors_tot; i++) {
		t = timings[i];  
		if (t<0)
			t = -10;

		fprintf (pf, "%d %d\n", i, t);
	}
}

void Ccdrom::AnalyzeResults ()
{
	if (! timings )
		return;

	int min_time = 1000000;
	int max_time = 0;
	int avg_time = 0;
	int bad_sectors = 0;
	int good_sectors = 0;

	int t;
	double td = 0;

	for (int i=0; i< sectors_tot; i++) {
		//printf ("> %d \n", timings[i]);

		t = timings[i];

		if (t >= 0 ) {
			good_sectors++;

			if (t > max_time)
				max_time = t;

			if (t < min_time)
				min_time = t;


			td += (double)t;
		} else {
			bad_sectors++;
		}
	}

	fprintf (stdout, "CD overall:\n");
	fprintf (stdout, "   Sectors total: %d:\n", sectors_tot);
	fprintf (stdout, "   Good sectors: %d:\n", good_sectors);
	fprintf (stdout, "   Bad sectors (incl. with poor timing): %d\n", bad_sectors);

	avg_time = (int) ( td / (double)good_sectors);  

	fprintf (stdout, "CD timings:\n");
	fprintf (stdout, "   Minimal = %d usec (%fs)\n", min_time, (double)min_time / 1000000.0 );
	fprintf (stdout, "   Maximal = %d usec (%fs)\n", max_time, (double)max_time / 1000000.0 );
	fprintf (stdout, "   Average = %d usec (%fs)\n", avg_time, (double)avg_time / 1000000.0 );

	fprintf (stdout, "\nConclusion:\n");

	if (bad_sectors > 0) {
		fprintf (stdout, "   Disc contains BAD or even readable sectors, put it into trash can!\n");
		return;
	}

	if (max_time < 100000 ) { // < 0.1
		fprintf (stdout, "   Excellent disc!\n");
		return;
	}

	if (max_time < 1000000 ) { // < 0.1
		fprintf (stdout, "   Satisfactory disc\n");
		return;
	}

	fprintf (stdout, "   Even there is no unreadable sectors, disc is unstable!\n");
}

void Ccdrom::DeallocateTimings()
{
	// printf("Ccdrom::DeallocateTimings()");
	sfree (timings);
}

void * Ccdrom::smalloc (size_t size)
{
	void* ptr;

	ptr = malloc (size);

	if (ptr) {
		memset (ptr, 0x0, size);
	}

	return ptr;
}

void Ccdrom::sfree (void*& ptr)
{
	if (ptr) {
		free (ptr);
		ptr = 0;
	}
}

void Ccdrom::sfree (int*& ptr)
{
	if (ptr) {
		free (ptr);
		ptr = 0;
	}
}



/*
 * __END__
 */
