/*  TT8 specific includes  */
#include        <TT8.h>                 /* Tattletale Model 8 Definitions */
#include        <tat332.h>              /* 68332 Tattletale (7,8) Hardware Definitions */
#include        <sim332.h>              /* 68332 System Integration Module Definitions */
#include        <qsm332.h>              /* 68332 Queued Serial Module Definitions */
#include        <tpu332.h>              /* 68332 Time Processing Unit Definitions */
#include        <dio332.h>              /* 68332 Digital I/O Port Pin Definitions */
#include        <tt8pic.h>              /* Model 8 PIC Parallel Slave Port Definitions */
#include        <tt8lib.h>              /* definitions and prototypes for Model 8 library */

//  general C includes
#include        <stdio.h>
#include        <stdlib.h>
#include		<string.h>
#include        <ctype.h>

//  disk related includes
#include		<pcdisk.h>

//  data logger related includes
#include		"ASSERT.h"
#include		"logdisk.h"
#include		"criterr.h"
#include		"text_log.h"
#include		"logtime.h"
#include		"misc.h"
#include		"science.h"

//  This is the number of times that we cycle power and retry a disk on failure.
//  The low level disk software also implements retries at that level (without
//  a power cycle) in addition to this retry
#define MAX_RETRIES 1


//  local subroutines not for general use	
int logger_disk_test_drive (char drive_char);
void logger_show_drive_status (char* path);


//  This array contains the list available drives, one at a time
//  The drives will be used in the order listed
struct disk_info  {
	short partition_char;	//  letter of drive
	short usable;				//  non-zero if full
	short errno;			//  errno which caused us to switch from
							//  this partition
	unsigned long partition_size;	//  total number of blocks (512 bytes) on drive
	unsigned long free_size;		//  number of unused blocks on drive
	unsigned int cluster_size;		//  number of bytes in a sector
};

//  The following array is modified at startup to reflect which disks are actually
//  present and their parameters.  The code starts with four slots because the LP1
//  can have up to two controllers, each with up to two drives on it.  Slots with
//  a null drive character are unused
struct disk_info partitions[] = {
	{'\0', 1, 0, 0L, 0L, 0L},
	{'\0', 1, 0, 0L, 0L, 0L},
#if (IDE_DRIVESPERCONTROLLER == 2)				
	//  if only 1 drive per controller, then only 2 drives possible
	{'\0', 1, 0, 0L, 0L, 0L},
	{'\0', 1, 0, 0L, 0L, 0L}
#endif	
};
const int n_partition_slots = sizeof (partitions) / sizeof (struct disk_info);
int current_partition;
int n_partitions;


//  logger_disk_power_on_init
//  User callable
//
//  This routine should be called once when the logger is powered on to
//  init various parts of the array which these routine use
//
void logger_disk_power_on_init (void)  {
	int n;
	extern char wd[];
	
	printf ("Disk initialization.\n");
	printf ("Testing for %d possible disks.\n", n_partition_slots);
	critical_error_logging (0);			//  don't print out failure info
	critical_error_set_n_tries (1);     //  don't retry at all
	n_partitions = 0;

#if (IDE_DRIVESPERCONTROLLER == 2)
	//  Test drives A and B;  if there are two drives per controller, then they share
	//  a power supply
	printf ("Testing drive A (Master disk on controller 1)\n");
	disk_power_on ("A:");
	if (logger_disk_test_drive ('A'))  {
		printf ("Testing drive B (Slave disk on controller 1)\n");
		logger_disk_test_drive ('B');
	}  else
		printf ("Slave disk B cannot be accessed because master A is not present.\n");
	disk_power_off ("A:");
		
	//  And the same for C and D

	printf ("Testing drive C (Master disk on controller 2)\n");
	disk_power_on ("C:");
	if (logger_disk_test_drive ('C'))  {
		printf ("Testing drive D (Slave disk on controller 2)\n");
		logger_disk_test_drive ('D');
	}  else
		printf ("Slave disk D cannot be accessed because master C is not present.\n");
	disk_power_off ("C:");
#else
	printf ("Testing drive A (Master disk on controller 1)\n");
	disk_power_on ("A:");
	logger_disk_test_drive ('A')	
	disk_power_off ("A:");
		
	printf ("Testing drive B (Master disk on controller 2)\n");
	disk_power_on ("B:");
	logger_disk_test_drive ('B')	
	disk_power_off ("B:");
#endif

	//  mark all unused slots as unusable
	for (n = n_partitions;  n < n_partition_slots;  n++)  {
		partitions[n].partition_char = '\0';
		partitions[n].partition_size = 0;
		partitions[n].free_size = 0;
		partitions[n].cluster_size = 0;
		partitions[n].errno = 0;
	}	

	//  restore normal error handling
	critical_error_logging (1);			//  print and log all failures
	critical_error_set_n_tries (3);     //  retry twice on failure

	//  start writing data to the first partition
	current_partition = 0;
	if (current_partition < n_partitions)
		wd[0] = partitions[current_partition].partition_char;
	else
		wd[0] = 'A';
	wd[1] = ':';
	wd[2] = '\\';
	wd[3] = 0;	
	return;
}





int logger_disk_test_drive (char drive_char)  {
	dword total_blocks, free_blocks;
	char path[8];
	ASSERT (n_partitions < n_partition_slots);

	path[0] = drive_char;
	path[1] = ':';
	path[2] = 0;
	
	//  Test whether there is actually a drive available for this partition
	//  If the drive is not present, then pc-free will fail, and total_blocks
	//  and free_blocks will remain at -1
	total_blocks = free_blocks = -1;
	critical_error_reset ();
	pc_free (path, &total_blocks, &free_blocks);
	if (total_blocks != -1)  {
		ASSERT (total_blocks > 0);
		partitions[n_partitions].partition_char = drive_char;
		partitions[n_partitions].partition_size = total_blocks;
		partitions[n_partitions].free_size = free_blocks;
		critical_error_reset ();
		partitions[n_partitions].cluster_size = pc_cluster_size (path);
		partitions[n_partitions].errno = 0;
		partitions[n_partitions].usable = 1;
		printf ("Drive %c has %ld total blocks, %ld of which are free\n",
									drive_char, total_blocks, free_blocks);
		if (((float)free_blocks/(float)total_blocks) < 0.90)  {
			printf ("Drive %c has an unexpectedly large portion of its data space already\n", drive_char);
			printf ("filled.  Please verify that this is what you expected!\n");
		}
		n_partitions++;
		return 1;
	}  else  {
		printf ("Drive %c did not respond and will not be used or tested again\n", drive_char);
		return 0;
	}
	//  cannot get here
	printf ("logger_disk_test_drive error\n");
	return 0;
}	



//  logger_disk_open
//
//	This is a essentially the po_open command with some extras which
//  are special to the data logger.
//
//  The routine must do the following:
//		Assign a partition letter to the path which was passed
//		Power the associated drive up
//		Try to open the file.  If successful, return fd
//		If not, check errno to figure out why
//			If the path doesn't yet exist, create it and try again
//			If there is no space, try the next drive
//			If the drive isn't responding, power it down and up, and try
//				one more time.  Then try the next drive.
//			
//  The return values from this routine are the same as those of the
//  po_open routine
//
//	Requirements:
//		open file if possible and return fd
//		on failure, check errno for reason.
//			If no path, create it
//			If no device, power down, then up, and try one more time.  If still bad
//				move to next drive.  If no next drive, shut down logger
//			If file already exists, choose new name (?)
//			If no space, move to next disk
//		
PCFD logger_disk_open (char* filename, unsigned int flag, unsigned int mode)  {
	//  the EMAXPATH variable is part of the disk drive header files
	char path[EMAXPATH];
	PCFD fd;
	int index;
	long free;
	int n;
	extern const long DISK_FREQ;

	//  Don't allow the user to specify the partition.  We want to keep control
	//  of that in this subroutine so we can switch when one gets full
	ASSERT (filename[1] != ':');
	ASSERT (current_partition >= 0);
	ASSERT (current_partition <= n_partitions);
	ASSERT (SimGetFSys () == DISK_FREQ);
	if (current_partition >= n_partitions)  {
		//  we are out of disk partitions!
		return -1;
	}	

	//  Combine the partition char with the file name to create the full path name
	sprintf (path, "%c:%s", (char)partitions[current_partition].partition_char, filename);

	//  make sure the disk is powered on, and if not, turn it on
	#ifdef DEBUG
	if (is_disk_power_on (path) == 0)
		printf ("Failing on path '%s', partition %d\n", path, current_partition);
	ASSERT (is_disk_power_on (path) != 0);
	#endif
	
	//  try the open
	critical_error_reset ();
	#ifdef DEBUG
	printf ("logger_disk_open: trying %s\n", path);
	#endif
	fd = po_open (path, flag, mode);
	if (fd >= 0)  {
		//  This ASSERT could be removed if necessary.  It is here because
		//  the current code opens only a single file at once, and closes that
		//  file when it is done.  Any fd other than 0 would indicate that we
		//  had forgotten to close a file.
		#ifdef DEBUG
			if (fd != 0)  {
				printf ("logger_disk_open: Unexpected file descriptor value of %d\n", fd);
			}	
			ASSERT (fd == 0);
			printf ("logger_disk_open: opened '%s'\n", path);
		#endif	
		return fd;
	}	
	log_printf ("logger_disk_open: Failed to open '%s'\n", path);		
	log_printf ("  Errno %d => '%s'\n", get_errno(), get_errno_string (-1));
		
	//  All is not well.  Try to figure out why
	switch (get_errno ())  {
		case PEDEVICE:
			//  The disk drive isn't there!	 But it should be, so try a few
			//  more times, then move to the next partition
			for (n = 0;  n < MAX_RETRIES;  n++)  {
				log_printf ("Disk not present; cycle power; try again\n");			
				disk_power_off (path);
            	DelayMilliSecs (500L);
				disk_power_on (path);
            	DelayMilliSecs (500L);
				critical_error_reset ();
				fd = po_open (path, flag, mode);
				if (fd >= 0)
					return fd;
			}
			//  we have failed multiple times.  Return error and let the upper level code
			//  move to the next partition
        	return -1;
			
        case PENOENT:
        	//  The path isn't there.  Create the full path - this isn't an unexpected
        	//  error for certain paths through the code
        	log_printf ("  Creating directory for '%s'\n", path);
        	logger_disk_make_dir (path);
			critical_error_reset ();
			fd = po_open (path, flag, mode);
			return fd;
        	
        case PEACCES:
        	//  attempt to open a read-only or special file	
        	//  should probably try a new file name.  How do we tell the calling
        	//  routine of this?
        	return -1;
        	
        case PEMFILE:
        	//  too many files already open
        	//  This indicates a serious programming error, as we should
        	//  be closing files as we finish with them	
        	log_printf ("  PEMFILE errno should never happen.\n");
        	//  REVIEW
        	//  what should we do here for error recovery?
        	ASSERT (0);
        	return -1;
        	
        case PENOSPC:
        	//  out of space on this drive/partition
        	//  should switch to the next drive or partition
			log_printf ("  PENOSPC error should never happen\n");
			return -1;
			
		default:
			disk_power_off (path);
        	log_printf ("  Unknown error %d should never happen.\n", get_errno());
			ASSERT (0);
	}
	ASSERT (0);
	return -1;
}



//  This routine includes a number of printf statements that are useful for debugging.
//  They are not #ifdef'ed because I consider this partition switching code important
//  enough to always print the info.  In the case of an actual deployment, this info
//  is lost, but the power wasted is insignificant, so I don't worry.  During land based
//  testing, we always get the info, which will be useful is something isn't working
//  properly.
//
int logger_disk_next_partition (void)  {
	dword blocks_total, blocks_free;
	
	//  check to see if we are already out of disk partitions
	if (current_partition >= n_partitions)
		return -1;
	printf ("logger_disk_next_partition: Finished with disk partition '%c:'\n",
		(char)partitions[current_partition].partition_char);
	printf ("This is partition number %d\n", current_partition);		
	
	log_printf ("logger_disk_next_partition: Finished with disk partition '%c:'\n",
			(char)partitions[current_partition].partition_char);
	if (get_errno() != 0)
		log_printf ("  Errno %d => '%s'\n", get_errno(), get_errno_string (-1));
	else
		log_printf ("  No disk error was encountered\n");	

	if (get_errno() != 0)
		printf ("  Errno %d => '%s'\n", get_errno(), get_errno_string (-1));
	else
		printf ("  No disk error was encountered\n");	

	partitions[current_partition].errno = get_errno ();
	partitions[current_partition].free_size = blocks_free;
	partitions[current_partition].usable = 0;

	blocks_total = blocks_free = -1;
	critical_error_reset ();
	pc_free(logger_disk_get_partition(), &blocks_total, &blocks_free);
	log_printf ("  At finish, %ld free blocks remained.\n", blocks_free);
	printf ("  At finish, %ld free blocks remained.\n", blocks_free);

    //  depower current disk, switch partitions, and repower disk if it exists
	logger_disk_power_disk_off ();
	
	
	printf ("Switching partitions\n");	
	current_partition++;
	if (current_partition >= n_partitions)  {
		//  we are out of partitions.  Because of the importance of the data,
		//  spend some time re-checking each disk for free space.  In the unlikely
		//  event of a coding error having incorrectly closed down a disk this
		//  might allow us to find it again.
		log_printf ("Out of disk space.  Should recheck all disks.\n");		
		printf ("Out of disk space.  Should recheck all disks.\n");		
		return -1;
	}
	printf ("About to power on disk\n");
	logger_disk_power_disk_on ();
	log_printf ("Disk partition changed successfully\n");
	printf ("Disk partition changed successfully\n");
	printf ("This is now partition '%c:'\n",
		(char)partitions[current_partition].partition_char);
	printf ("This is partition number %d\n", current_partition);		
	printf ("logger_disk_next_partition done\n");
#ifdef CREATE_DIRS	
	sci_logger_setup_partition ();
#endif	
	return 0;
}




char* logger_disk_get_partition (void)  {
	static char part[4];
	if (current_partition < n_partitions)  {
		part[0] = (char)partitions[current_partition].partition_char;
		part[1] = ':';
		part[2] = '\\';
		part[3] = '\0';
	}  else  {
		part[0] = '\0';
	}
	return (&part[0]);
}		
	        
	


int logger_disk_make_dir (const char* filename)  {
	char dir[EMAXPATH];
	int index, n;

	//  make a copy of the path since we will be modifying it
	strncpy (dir, filename, sizeof(dir));
	dir[sizeof(dir)-1] = '\0';
	
	//  Now delete the file name from the end of the path, leaving only
	//  the directory name
	index = strlen (dir) - 1;
	while (index > 0)  {
		if (dir[index] == '\\')
			break;
		index--;
	}
	dir[index+1] = '\0';
	
	#ifdef DEBUG
	printf ("logger_disk_make_dir: testing for '%s'\n", dir);
	critical_error_reset ();
	if (pc_isdir(dir) != FALSE)  {
		printf ("Directory is '%s'\n", dir);
	}
	critical_error_reset ();
	ASSERT (pc_isdir(dir) == FALSE);
	#endif

	for (n = 0;  dir[n] != '\0';  n++)  {
		if (dir[n] != '\\')
			continue;
			
		//  temporarily overwrite the \.  Then verify that the path exists, and
		//  if not, create it.  Any failure is a hard failure.  Log it and give
		//  up
		dir[n] = '\0';
//		printf ("logger_disk_make_dir: about to test dir %s\n", dir);		
		critical_error_reset ();
		#ifdef DEBUG
		printf ("logger_disk_make_dir: testing for dir '%s'\n", dir);	
		#endif
		if (pc_isdir (dir) == FALSE)  {
			#ifdef DEBUG
			printf ("logger_disk_make_dir: about to make dir %s\n", dir);		
			#endif
			critical_error_reset ();
			#ifdef DEBUG
			printf ("logger_disk_make_dir: making dir '%s'\n", dir);	
			#endif
			if (pc_mkdir (dir) == FALSE)  {
				log_printf ("logger_disk_make_dir: Couldn't create directory '%s'.\n", dir);
				log_printf ("  Errno %d => %s\n", get_errno (), get_errno_string (-1));
				return -1;
			}
			#ifdef DEBUG
			else
				printf ("logger_disk_make_dir: made '%s'\n", dir);
			#endif	
		}
		#ifdef DEBUG			
		else
			printf ("path '%s' already exists.\n", dir);
		#endif	
		dir[n] = '\\';
	}
	return 0;
}
		


//  logger_disk_cluster_size (char* path)
//
//  Returns the cluster size of the drive on which the given
//  path will write
//
//	This is important because the the standard DOS file system uses
//  16 bit numbers to specify which file is using which blocks.  For
//  large disks, the number of blocks quickly exceeds 2^16, and the
//  file system therefore allocates blocks in groups called clusters.
//  Therefore, in order to efficiently use the disk without wasting space
//  we must choose a file size that fills a cluster as completely as
//  possible.  If we don't do this, the unused part of the cluster is
//  lost.
//
long logger_disk_cluster_size (char* path)  {
	critical_error_reset ();
	return pc_cluster_size (path);
}





//  logger_disk_power_disk_off (void)  {
//
//  Turns off the power to the current disk
//
void logger_disk_power_disk_off (void)  {
	short fd;
	char path[16];
	char trash[128];

	strcpy (path, logger_disk_get_partition ());
	//  don't do anything if the current disk is non-existent
	if (path[0] == '\0')
		return;
	strcat (path, "LOG.TXT");
//	printf ("logger_disk_power_disk_off: opening '%s'\n", path);		

	//  REVIEW
	//  This is a temporary kluge which is inserted to insure that the disk
	//  actually flushes the data from its internal buffers to the disk
	//  itself.  Some disks apparently don't do the write immediately.  By
	//  forcing a seek we force the disk to actually flush the buffer first
	//	
	//  This sequence tries to insure that all of the data which has been written to the
	//  disk's buffer has actually been flushed out to the disk.  It isn't clear from
	//  any spec what is necessary to do this, but I can make some guesses.  First, I hope
	//  that any drive actually processes data in the order the requests are received, so
	//  I open a file, which probably doesn't exist, in the root directory, and hope that
	//  this forces the flush of previously queued data.  Second, I hope that any drive makes
	//  a reasonable effort to move data out to the disk, and that if I give it half a second
	//  of delay this will be enough.  These measures may NOT be enough.  Who knows?  If
	//  the file system starts producing unexpected errors, try something else here to see
	//  if that fixes the problems.
	//  SJM 3/7/99
	
	critical_error_reset ();
	fd = po_open (path, (PO_BINARY|PO_RDWR), PS_IWRITE);
	if (fd >= 0)  {
		critical_error_reset ();
		po_lseek (fd, 0L, PSEEK_SET);
		critical_error_reset ();
		po_read (fd, (byte*)trash, sizeof(trash));
		critical_error_reset ();
		po_close (fd);
	}

	//  now actually depower the disk
	disk_power_off (path);
	return;
}




void logger_disk_power_disk_on (void)  {
	char path[8];
	
	//  This is my own addition to the file system calls.  It completely resets the
	//  systems knowledge of the disk system and forces a re-read of all pertinent
	//  info.  This may be important when switching disks.
	pc_file_system_reset ();
	strcpy (path, logger_disk_get_partition ());
	if (path[0] != '\0')  {
		disk_power_on (path);
	}
#ifdef DEBUG	
	else
		printf ("logger_disk_power_disk_on: No disk to power on	\n");
#endif		
}
		




void logger_disk_show_status (void)  {
	int n;
	
	printf ("The system checked for %d disks at power-up.\n", n_partition_slots);
	//  obviously not necessary, but we have plenty of code space
	if (n_partitions == 1)
		printf ("There was 1 disk that responded.\n");
	else	
		printf ("There were %d disks that responded.\n", n_partitions);

#if (IDE_DRIVESPERCONTROLLER == 2)
	disk_power_on ("A:");
	logger_show_drive_status ("A:");
	logger_show_drive_status ("B:");
	disk_power_off ("A:");
	disk_power_on ("C:");
	logger_show_drive_status ("C:");
	logger_show_drive_status ("D:");
	disk_power_off ("D:");
#else	
	disk_power_on ("A:");
	logger_show_drive_status ("A:");
	disk_power_off ("A:");
	disk_power_on ("B:");
	logger_show_drive_status ("B:");
	disk_power_off ("B:");
#endif
		
	if (current_partition >= n_partitions)  {
		printf ("All disks are currently full, and the data logger is disabled\n");
		#ifdef DEBUG
		for (n = 0;  n < n_partitions;  n++)
			ASSERT (partitions[n].usable == 0);
		#endif
	}  else  {
		ASSERT (partitions[current_partition].usable != 0);
		printf ("Data is currently being written to disk %d which is assigned drive char %c\n",
									current_partition+1, partitions[current_partition].partition_char);
	}									
}										



void logger_show_drive_status (char* path)  {
	dword blocks_free, blocks_total;
	long nfree;
	int n;
	
	for (n = 0;  n < n_partitions;  n++)
		if ((char)partitions[n].partition_char == path[0])
			break;
	if (n >= n_partitions)
		return;
				
	printf ("Drive %d is assigned drive character %c.\n", n+1, (char)partitions[n].partition_char);
	printf ("  It has %ld blocks of 512 bytes each.\n", partitions[n].partition_size);
	printf ("  These blocks are allocated in clusters of %u bytes each.\n", partitions[n].cluster_size);
	blocks_free = blocks_total = -1;

	critical_error_reset ();
	nfree = pc_free (path, &blocks_total, &blocks_free);
	if (blocks_free != -1)  {
		printf ("  There are %ld free blocks on this drive.\n", blocks_free);
	}  else  {
		printf ("  This drive did not respond.\n");
	}

	if (partitions[n].usable != 0)
		printf ("  This drive is currently available for data storage.\n");
	else  {
		printf ("  This drive is no longer available for data storage.\n");	
		printf ("  It was disabled when a write returned error number %d.\n", partitions[n].errno);
		printf ("  This error is '%s'.\n", get_errno_string (partitions[n].errno));
	}
	return;
}	




//  logger_disk_final_flush ()
//
//  Flushes the text log to the disk if possible.  This is not
//  foolproof - it fails if the disks were abandoned because they were
//  bad.  However, if they were abandoned because of a space limitation,
//  then we know there is enough space left for the small amount of
//  data in the text log, and we want to capture it.  This routine
//  temporarily tweakes the current partition for the write, then
//  marks us as out of partitions again.
//
void logger_disk_final_flush (void)  {
	long freq, baud;
	int freq_change;
	int result;
	ASSERT (current_partition == n_partitions);
	printf ("logger_disk_final_flush\n");
	freq = SimGetFSys ();
	baud = SerGetBaud (0L, 0L);
	if (freq != DISK_FREQ)  {
		SimSetFSys (DISK_FREQ);
		SerSetBaud (baud, 0L);
		freq_change = 1;
	} else
		freq_change = 0;
	
	while (current_partition > 0)  {
		current_partition--;
		printf ("logger_disk_final_flush: trying partition %d\n", current_partition); 		
		logger_disk_power_disk_on ();
		result = log_flush (NULL);
		logger_disk_power_disk_off ();
		
		if (result == 0)
			break;
	}	
	current_partition = n_partitions;
	printf ("logger_disk_final_flush: done\n");	
	//  be sure to restore the frequency and baud if needed
	if (freq_change)  {
		SimSetFSys (freq);
		SerSetBaud (baud, 0L);
	}
	return;
}
	
		
