/*  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        <dio332.h>              /* 68332 Digital I/O Port Pin 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>

/*  includes for our particular program  */
#include        "assert.h"
#include        "logtime.h"
#include        "text_log.h"
#include		"tputime.h"
#ifdef SIMULATE_DATA
#include		"AD7716.h"
#endif

#define         CHAR_BACKSPACE          0x08



struct logger_time  {
	long tick_offset;
	long tick_rate;
	short sync_set_flag;
	#ifdef SIMULATE_DATA
		//  when simulating data we need to also simulate the time in order to keep
		//  keep the file names progressing properly.  Therefore, when time is simulated,
		//  we co-opt the normal set and get time routines.  This variable is used to track
		//  the starting time seconds as set by the user.  We leave as much as possible of
		//  the normal time tracking code in as well, even though it isn't used.
		long sim_start_secs;
	#endif
//	volatile unsigned long IRQ6_hits;
} logger_time;

void wait_for_IRQ6 (void);

//  logger_time_power_on_init
//
//  Inits the time.  Starts by checking to see if the time is illegal, and
//  if so, forcing it to a default value.  Then, asks the user if he
//  wants to set the time.  If there is no response in a fixed length of time,
//  assume the answer is no and move on.  If the user answers yes, allow him
//  to set it
//
void logger_time_power_on_init (void)  {
	unsigned char answer;
	struct tm tm;
	struct tm* tp;
	time_tt now;
	int result;

	//  reset the IRQ pin as an input.
	PConfInp (F, 6);

	#ifdef SIMULATE_DATA
		logger_time.sim_start_secs = 0;
	#endif

	//  start by initing the structure we use to track the logger time and
	//  setting the tick rate to give a resolution of 1 mS.
	logger_time.tick_offset = 0;
	logger_time.sync_set_flag = 0;
//	logger_time.IRQ6_hits = 0;
	SetTickRate (40000);			//  choose a resolution for the tick rate
	logger_time.tick_rate = GetTickRate ();    
	TPU_InitializeTimeKeeping();
                                                             
	
	//  Now verify that the time is something reasonable.  If not, reset
	//  it to a large value which this logger will realistically never see.
	//
	//  If the year is earlier than 1997, then it is obviously wrong, and we
	//  should reset it.  If the high order bit of the number of seconds since
	//  1970 is set, then the number appears negative and screws up the localtime()
	//  routine, producing an unprintable and obviously incorrect date.  This is a
	//  bug in the Aztec routines.  It corresponds roughly to the year 2038.
	now = logger_time_get_time ();
	tp = localtime (&now.secs);
	if ((tp->tm_year < 97) || (now.secs&0x80000000L))  {
	    log_printf("The starting date is unacceptable: %s", logger_time_get_string (&now));
		tp->tm_mon = 0;
		tp->tm_mday = 1;
		tp->tm_year = 110;                      //  year = 2010
		tp->tm_hour = 0;
		tp->tm_min = 0;
		tp->tm_sec = 0;
		logger_time_set_time (tp);
		now = logger_time_get_time ();
		//  the <return> is included by logger_time_get_string()
		log_printf ("Resetting the time to %s", logger_time_get_string (&now));
	}
	
	now = logger_time_get_time ();
	printf ("The time is: %s", logger_time_get_string (&now));
	printf ("This will default to No in 30 seconds.\n");
	printf ("Do you want to set the time using the sync pulse? ");
	fflush (stdout);

	//  wait up to 30 seconds for the user to respond y or n
	StopWatchStart ();
	answer = 'N';
	while ((StopWatchTime()/1e6) < 30)  {
		if (SerByteAvail())  {
			answer = SerGetByte ();
			putchar (answer);
			answer = toupper (answer);
			if ((answer == 'Y') || (answer == 'N'))
				break;  
			//  unknown answer; erase it and continue
			putchar (CHAR_BACKSPACE);
			fflush (stdout);
		}
	}
	printf ("\n");
	if (answer == 'Y')  {
		printf ("The time will be set when the sync pulse is low.\n");
		result = logger_time_query_time (&tm);  
		if (result == 0)  {
			//  REVIEW - change this back to the sync set when the disk is debugged
			//
			logger_time_sync_set_time (&tm);
			printf ("The time is: ");
		}  else
			printf ("Time was not changed; it is: ");       
	}  else
		printf ("Time was not changed; it is: ");       
	now = logger_time_get_time ();
	printf ("%s", logger_time_get_string (&now));
	return; 
}





//  logger_time_get_time ()
//
//      Returns the time using the standard TT8 time structure, which
//  includes a variable for fractions of a second.  Corrects for
//  the inability of the TT8 to set the fractions of a second using
//  an offset variable which was recorded when the user set the time
//  using either the set_logger_time or the sync_set_logger_time
//  routines
//
time_tt logger_time_get_time (void)  {
	time_tt cTime;        
	#ifdef SIMULATE_DATA
		long ds;
		long n_conversions;		
		cTime.secs = logger_time.sim_start_secs;
		//  integer math to get n_conversions
		if (AD7716_get_n_channels () == 0)  {
			cTime.ticks = 0;
			return cTime;
		}
			
		//  floating math + convert to int to get n seconds elapsed
		n_conversions = AD7716_get_sim_count () / AD7716_get_n_channels();
		ds = n_conversions / AD7716_get_conversion_rate ();
		cTime.secs += ds;
		cTime.ticks = GetTickRate() * ((n_conversions / AD7716_get_conversion_rate ()) - ds);
		if (cTime.ticks == 0)  {
			//  don't adjust time unless we have simulated a complete set of channels
			if ((AD7716_get_sim_count () % AD7716_get_n_channels()) == 0)  {
//				printf ("****logger_time_get_time: Reset sim count****\n");
//				printf ("  ds = %ld\n", ds);
//				printf ("  sim_count = %ld\n", AD7716_get_sim_count());
//				printf ("  conversion rate = %f\n", AD7716_get_conversion_rate());
				logger_time.sim_start_secs += ds;
				AD7716_reset_sim_count ();
			}	
		}
		return cTime;
	#endif

	//  REVIEW
	//  We can't access the ttmnow() function unless the clock is faster than
	//  1 MHz
	ASSERT (SimGetFSys () > 1e6);
	ASSERT (GetTickRate () == logger_time.tick_rate);
	cTime = TPU_ttmnow();

//	printf ("Tick Offset, Ticks = %ld %ld\n",logger_time.tick_offset,cTime.ticks);
	//  make sure the ticks aren't negative; correct if so
	cTime.ticks -= logger_time.tick_offset;
	if (cTime.ticks < 0)  {
		cTime.secs--;
		cTime.ticks += GetTickRate();
	}
	return cTime;
}




//  logger_time_get_seconds (void)
//
//	Implements a safe version of the get_time function.  The "safe"
//  version avoids calling the ttmnow() function, which sometimes screws
//  up the counting of pulses which define the time.  The disadvantage
//  of the safe version is that it is not capable of reading the ticks.
//  The ticks variable is set to 0.
//
time_tt logger_time_get_seconds (void)  {
	time_tt cTime;
	
	cTime.secs = RtcToCtm ();
	cTime.ticks = 0;
	return cTime;
}
	

//  logger_time_set_time ()
//
//  sets the logger time immediately
//
void logger_time_set_time (struct tm* tp)  {
	time_tt now;
	ASSERT (SimGetFSys () > 1e6);

	#ifdef SIMULATE_DATA
		logger_time.sim_start_secs = mktime (tp);
		return;
	#endif
	
	//  grab the tick offset immediately
	now = TPU_ttmnow ();
	
	//  then set the time, which doesn't change the offset
	TPU_SetTimeTM (tp, NULL);

	//  and use previously acquired time to adjust the offset
	logger_time.tick_offset = now.ticks;

	//  don't allow changes in the tick rate
	ASSERT (GetTickRate () == logger_time.tick_rate);

//      printf ("Returned from SetTimeTM\n");
//      printf ("Requested time: %02d:%02d:%02d on %d/%d/%d\n", tp->tm_hour, tp->tm_min, tp->tm_sec,
//                                                                                      tp->tm_mon, tp->tm_mday, tp->tm_year);
//      printf ("Additional info wday = %d, yday = %d, isdst = %d, hsec = %d\n",
//                              tp->tm_wday, tp->tm_yday, tp->tm_isdst, tp->tm_hsec);                                                                                   
	return;
}




//  logger_time_get_string
//
//      Returns a pointer to a static character buffer which holds the ASCII
//  string corrseponding to the time which was passed to this routine
//
//      Review: really should improve the format of this
//
const char wday[7][4] = {
	"Sun",
	"Mon",
	"Tue",
	"Wed",
	"Thu",
	"Fri",
	"Sat"
};
const char month[12][4] = {
	"Jan",
	"Feb",
	"Mar",
	"Apr",
	"May",
	"Jun",
	"Jul",
	"Aug",
	"Sep",
	"Oct",
	"Nov",
	"Dec"
};

const short n_mdays[12] = {
	31,
	28,
	31,
	30,
	31,
	30,
	31,
	31,
	30,
	31,
	30,
	31
};
	
	
char* logger_time_get_string (time_tt* t)  {
	static char logger_time_string[64];
	struct tm* tm;
	long usec;
	ASSERT (GetTickRate () == logger_time.tick_rate);

//      sprintf (logger_time_string, "%s ", ctime (&t->secs));
//      sprintf (&logger_time_string[strlen(logger_time_string)], "%ld mSecs\n", t->ticks);
	
	//  Because of a bug in the Aztec localtime routines, dates in which the
	//  number of seconds appears negative do not print correctly.  Short
	//  circuit this bug here.
	if (t->secs&0x80000000L)  {
		sprintf (logger_time_string, "Sometime after the year 2038.\n");
		return logger_time_string;
	}       
	
	tm = localtime (&(t->secs));
//      printf ("localtime returns the following values:\n");
//      printf (" tm->tm_mon  = %d\n", tm->tm_mon);
//      printf (" tm->tm_mday = %d\n", tm->tm_mday);
//      printf (" tm->tm_year = %d\n", tm->tm_year);
//      printf (" tm->tm_hour = %d\n", tm->tm_hour);
//      printf (" tm->tm_min  = %d\n", tm->tm_min);
//      printf (" tm->tm_sec  = %d\n", tm->tm_sec);
//      printf (" tm->tm_wday = %d\n", tm->tm_wday);
//      printf (" tm->tm_yday = %d\n", tm->tm_yday);
//      printf ("seconds = %ld = 0x%08lX\n", t->secs, t->secs);
	
	if (tm->tm_year < 70)
		tm->tm_year += 2000;
	else
		tm->tm_year += 1900;    
	usec = (long)(1000000L * ((float)t->ticks / GetTickRate ()));	
	sprintf (logger_time_string, "%s %s %2d %d @ %02d:%02d:%02d.%06ld\n",
												wday[tm->tm_wday],
												month[tm->tm_mon],
												tm->tm_mday,
												tm->tm_year,
												tm->tm_hour,
												tm->tm_min,
												tm->tm_sec,
												usec);
	return logger_time_string;
}

//  logger_time_query_time
//      user callable
//
//      Prompts user to enter the date and time.  Times out on inactivity.
//  Verifies that entered date and time are legal.  Puts entered values
//  in the tm structure which user supplies pointer to.  Returns 0 if
//  the tm structure contains a good time value, and non-zero if it
//  should be ignored
//
int logger_time_query_time (struct tm* tm)  {
	int n, illegal;
	char buffer[64];
	ASSERT (GetTickRate () == logger_time.tick_rate);
	
	printf ("Please use Universal Coordinated Time, NOT local time.\n");
	printf ("The time set operation will time out after 30 seconds of inactivity.\n");
	printf ("Hit <return> to abort the time set operation\n");
	printf ("Enter the current date and time: ");
	fflush (stdout);
	
	//  wait no more than 30 seconds between input characters before
	//  timing out
	StopWatchStart ();
	n = 0;
	memset (buffer, 0, sizeof(buffer));
	while ((StopWatchTime()/1e6) < 30)  {
		if (SerByteAvail())  {
			StopWatchStart ();
			buffer[n] = SerGetByte ();
			if (buffer[n] != CHAR_BACKSPACE)  {
				putchar (buffer[n]);
				if (buffer[n] == '\r')
					break;
				if (n < (sizeof(buffer)-1))
					n++;
				else
					break;  
			}  else  {
				if (n > 0)  {
					putchar (buffer[n]);
					buffer[n] = 0;
					n--;
				}
			}
			fflush (stdout);
		}
	}
	if ((StopWatchTime()/1e6) > 30)  {
		printf ("Timed out due to inactivity.\n");
		return -1;
	}       
	buffer[n] = 0;
	putchar ('\n');
	fflush (stdout);
	
	//  we reached this point either because of a timeout or
	//  because the user hit <return>
	n = sscanf (buffer, "%d/%d/%d %d:%d:%d", &tm->tm_mon, &tm->tm_mday, &tm->tm_year,
												&tm->tm_hour, &tm->tm_min, &tm->tm_sec);
	if (n != 6)  {
		printf ("Date not accepted - entry format is month/day/year hour:min:sec\n");
		return -1;
	}
	//  correct month for 0 based reference used by computer
	tm->tm_mon--;

	//  allow multiple formats for the year
	if (tm->tm_year > 1970)
		tm->tm_year -= 1900;
	if ((tm->tm_year < 70) && (tm->tm_year >= 0))
		tm->tm_year += 100;
		
	//  verify that the entered date and time are legal
	illegal = 0;
	if ((tm->tm_mon < 0) || (tm->tm_mon >= 12))
		illegal = 1;
	if ((tm->tm_mday <= 0) || (tm->tm_mday > n_mdays[tm->tm_mon]))  {
		if (((tm->tm_year%4) != 0) || (tm->tm_mon != 1) || (tm->tm_mday != 29))
			illegal = 1;
	}       
	if ((tm->tm_year < 0) || (tm->tm_year > 206))
		illegal = 1;
	if ((tm->tm_hour < 0) || (tm->tm_hour > 23))
		illegal = 1;
	if ((tm->tm_min < 0) || (tm->tm_min > 59))
		illegal = 1;
	if ((tm->tm_sec < 0) || (tm->tm_sec > 59))
		illegal = 1;
	if (illegal)  {
		printf ("Date not accepted - illegal date entered\n");
		return -1;
	}
	return 0;
}       





//  logger_time_sync_set_time ()
//
//  Sets the logger time on when IRQ6 drops low
//  IRQ6 is on TPU pin 15
//
//  This routine will be most accurate if it is run with the
//  clock turned up to high frequencies.  It does not actually
//  turn the clock up itself because of the time lost as the
//  phase locked loop locks to a new frequency.  The user should
//  turn the clock up before calling this routine.
//
void logger_time_sync_set_time (struct tm* tp)  {
	time_tt now;
	
	//  then set the time, which doesn't change the offset
	TPU_SetTimeTM(tp, wait_for_IRQ6);

	//  grab the tick offset immediately
	now = TPU_ttmnow ();
	logger_time.tick_offset = now.ticks;
	ASSERT (GetTickRate () == logger_time.tick_rate);
	//	logger_time.tick_rate = GetTickRate ();

	//  The flag is non-zero if we have set the time using the sync set function
	//  Allows some level of error checking.  The user should always have called
	//  this function at least once before disconnecting.
	logger_time.sync_set_flag = 1;
    printf ("sync_set: tick offset is %ld\n", logger_time_get_tick_offset());
	return;
}





//  logger_time_sync_get_time ()
//
//      Returns the time on the falling edge of the input to
//      TPU pin 15
//
//  This routine will be most accurate if it is run with the
//  clock turned up to high frequencies.  It does not actually
//  turn the clock up itself because of the time lost as the
//  phase locked loop locks to a new frequency.  The user should
//  turn the clock up before calling this routine.
//
time_tt logger_time_sync_get_time (void)  {
	time_tt temp;
	
	wait_for_IRQ6 ();        
		
	//  collect the current time
	temp = logger_time_get_time ();
	
	ASSERT (GetTickRate () == logger_time.tick_rate);

	//  and return the time
	return temp;
}       
		



int logger_time_verify_set (void)  {
	ASSERT (GetTickRate () == logger_time.tick_rate);
	return logger_time.sync_set_flag;
}







//  wait_for_IRQ6
//
//  Waits for IRQ6 to go low.  The calling routine must have already
//  set the the counter to 0.
//
//  This routine will not return until an interrupt occurs, and it 
//  will not return if the user forgot to activate the interrupt,
//  so don't forget it
//
void wait_for_IRQ6 (void)  {
	//  make sure F6 is an input
	PConfInp (F, 6);

#asm
;	save a0 before we start
	move.l	a0,-(sp)
;	load it with the address of Port F
;	The address should be encoded as PORTF, defined in tat332.h and tt8lib.h,
;	but the preprocessor doesn't seem to work on assembly code, so we hardwire
;	it instead.
	move.l	#$FFFFFA19,a0
	
;	wait for pin 6 to go high
F6low
	btst	#6,(a0)
	beq		F6low
	
;	now wait for it to go low
F6high		
	btst	#6,(a0)
	bne		F6high
	
;	restore a0 and return
	move.l	(sp)+,a0
#endasm

/*
	//  this is the equivalent C code to the assembler above.  The assembler
	//  is much faster, which is important when we are trying to catch the
	//  short pulses which some GPS units put out.
	while (Pin (F, 6) == 0)
		{;}
	while (Pin (F, 6))
		{;}
*/		
	return; 
}




long logger_time_get_tick_offset (void)  {
	return logger_time.tick_offset;
}

void logger_time_set_tick_offset (long value)  {
	ASSERT (value >= 0);
	ASSERT (value < 40000L);
	
	if ((value >= 0) && (value < 40000))
		logger_time.tick_offset = value;
		
//	printf ("time routines: setting tick offset to %ld\n", logger_time.tick_offset);
	return;	
}		