// clock.cpp: implementation of the Clock class.
//
//////////////////////////////////////////////////////////////////////
#include <time.h>
#include "ostime.h"
#include "const.h"

#ifdef _WIN32_
#include <memory.h>
#include <stdafx.h>
#endif

#ifdef _VXWORKS_
/* includes */
#include <dosFsLib.h>
#include <string.h>
#include "vxWorks.h"
#include "time.h"
#include "intLib.h"
#include "pc.h"
extern "C" { 
int sysClkRateGet();// VxWorks headers fail to define this
int sysClkRateSet (int ticksPerSecond);// VxWorks headers fail to define this
unsigned long	tickGet (void);// VxWorks headers fail to define this
STATUS sysRtcInit ( void);
void sysRtcShutdown(void);
STATUS sysRtcSet( struct tm *timedate );
STATUS sysRtcGet ( struct tm *tm  );
void sysRtcSetWatchdog (int seconds);
void sysRtcResetWatchdog ( void);
STATUS dosFsDateTimeRetrieve( DOS_DATE_TIME* d );
};



/* macro to check that <v> is between <l> and <h> inclusive range */
#define	CHECK_RANGE(v,l,h)	(((v)>=(l))&&((v)<=(h)))

/* Macros to convert 2-digit BCD into binary and vice versa */
#define	BIN2BCD(b)	(((b)%10) | (((b)/10)<<4))
#define	BCD2BIN(b)	(((b)&0xf) + ((b)>>4)*10)

/* RTC register access, macros should be defined for a particular BSP */
#if	CPU_FAMILY==I80X86

#define	REG_SET(reg,val)	\
	(sysOutByte(RTC_INDEX,(reg)),sysOutByte(RTC_DATA,(val)))
#define	REG_GET(reg) (sysOutByte(RTC_INDEX,(reg)),sysInByte(RTC_DATA))

#else
#ifndef	RTC_JMP
#define	RTC_JMP		1
#endif

#ifndef	RTC_BASE
#define	RTC_BASE	0x800000
#endif

#define	RTC_REG(o)		((volatile u_char *)(RTC_BASE+(o)*RTC_JMP))
#define	REG_SET(reg,val)	(*RTC_REG(reg) = (val))
#define	REG_GET(reg)	(*RTC_REG(reg))
#endif

#endif


//////////////////////////////////////////////////////////////////////
// Module Initialization
//////////////////////////////////////////////////////////////////////

void Clock::initialize (void)
{
    /* Set time zone from TZ environment variable. If TZ is not set,
     * the operating system is queried to obtain the default value 
     * for the variable. 
     */
#ifdef _WIN32_
    _tzset();
#endif
#ifdef _VXWORKS_
	/* (re-)install the VxWorks DOS date/time hook in case the kernel init didn't */
	dosFsDateTimeInstall ((FUNCPTR) dosFsDateTimeRetrieve);
//	printf ("===> dosFsDateTimeInstall (dosFsDateTimeRetrieve)\n");
#endif
}


//////////////////////////////////////////////////////////////////////
// Construct and Destroy placeholders
//////////////////////////////////////////////////////////////////////
Clock::Clock(){};
Clock::~Clock(){};


Int32 Clock::getSystemClkTck(){
	return TimeWrapper::getSystemClkTck();
}
Int32 Clock::getSystemClock(){
	return TimeWrapper::getSystemClock();
}
Time Clock::getLocalTime()
{
	DateTime dt;
	TimeWrapper::getLocalTime(&dt);
	Time time(&dt);
	return time;
}
Time Clock::getgmDateTime()
{
	DateTime dt;
	TimeWrapper::getgmDateTime(&dt);
	Time time(&dt);
	return time;
}
Time Clock::convertgmToTime(Nat32 gmttime)
{	
	DateTime dt;
	struct tm *newtime;
#ifdef _VXWORKS_
	newtime = gmtime((const long unsigned int*) &gmttime);
#else
	newtime = gmtime((const long *) &gmttime);
#endif
	dt.dt_hr = newtime->tm_hour;
	dt.dt_min = newtime->tm_min;
	dt.dt_sec = newtime->tm_sec;
	dt.dt_yr = newtime->tm_year + 1900;
	dt.dt_mo = newtime->tm_mon + 1;
	dt.dt_yday = newtime->tm_yday + 1;
	dt.dt_mday = newtime->tm_mday;
	dt.dt_wday = newtime->tm_wday;
	Time time(&dt);
	return time;
}
gmTime Clock::getgmTime(DateTime *dtp)
{
	DateTime dt;
	if(dtp == 0) {
		TimeWrapper::getgmDateTime(&dt);
		return TimeWrapper::getgmTime(&dt);
	}else
		return TimeWrapper::getgmTime(dtp);

}
TimeWrapper::TimeWrapper(){};
TimeWrapper::~TimeWrapper(){};
void TimeWrapper::getLocalTime(DateTime *dt)
{
	if(dt == NULL)
		return;
#ifdef _VXWORKS_
	struct tm tms; 
	sysRtcGet(&tms);
	int m = 0;
	int d = 0;
	int y = 0;
	int jday = 0;
	dt->dt_hr = tms.tm_hour; // hours since midnight - [0,23]
	dt->dt_min = tms.tm_min; // minutes after the hour - [0,59]
	dt->dt_sec = tms.tm_sec; // seconds after the minute - [0,59]
	dt->dt_yr = tms.tm_year + 1900; //make 4 digit
	dt->dt_mo = tms.tm_mon+1;  // months since January - [0,11] 
	dt->dt_mday = tms.tm_mday; // day of the month - [1,31]
	dt->dt_wday = tms.tm_wday; // day of the week - [1,7]
	// days since January 1 - [0,365] 
	m = (int)dt->dt_mo;
	d = (int)dt->dt_mday;
	y = (int)dt->dt_yr;
	juldate_from_month_day(&jday,m,d,y);
	dt->dt_yday = jday;	
#endif
#ifdef _WIN32_
	time_t long_time;
	struct tm *newtime;
	time( &long_time );                // Get time as long integer.
	newtime = localtime( &long_time ); // Convert to local time.          
	dt->dt_hr = newtime->tm_hour; // hours since midnight - [0,23]
	dt->dt_min = newtime->tm_min; // minutes after the hour - [0,59]
	dt->dt_sec = newtime->tm_sec; // seconds after the minute - [0,59]
	 // years since 1900. Make 4 digit and correct for 2000+ years
	dt->dt_yr = (newtime->tm_year > 99 ? newtime->tm_year + 1900: newtime->tm_year);
	dt->dt_mo = newtime->tm_mon + 1;  // months since January - [0,11] 
	dt->dt_yday = newtime->tm_yday; // days since January 1 - [0,365] 
	dt->dt_mday = newtime->tm_mday; // day of the month - [1,31]
	dt->dt_wday = newtime->tm_wday;		// day of the week - [1,7]
#endif
}
void
TimeWrapper::getgmDateTime(DateTime *dt)
{	
	int m = 0;
	int d = 0;
	int y = 0;
	int wday = 0;
	if(dt == NULL)
		return;
	struct tm *newtime;
	time_t long_time = 0;
	TimeWrapper::getLocalTime(dt);
	m = (int)dt->dt_mo;
	d = (int)dt->dt_mday;
	y = (int)dt->dt_yr;
	wday = (int)dt->dt_wday;
	dt->dt_hr = (dt->dt_hr + 8) % 24; // hours since UTC 1/1/70 noon - [0,23]		
	if(m > 3 && m < 11) // if in the months April - October
	{
		//DST starts the first sunday of April past 2 am 
		if(m > 4 && m < 10)
			(dt->dt_hr > 0 ? dt->dt_hr -= 1: dt->dt_hr = 23);
		else if(m == 4) {

		if(d < 8) {
			if(wday == 7) {
				if(dt->dt_hr > 9)
					dt->dt_hr -= 1;
			}
			else if(d - wday >= 0)
				(dt->dt_hr > 0 ? dt->dt_hr -= 1: dt->dt_hr = 23);
			else 
				dt->dt_hr = dt->dt_hr;
		}
		else
			(dt->dt_hr > 0 ? dt->dt_hr -= 1: dt->dt_hr = 23);
		}//DST ends the last sunday of October past 2 am 		
		else if(m == 10) {
		
		if(d > 23) {
			if(wday == 7) {
				if(dt->dt_hr < 10)
					(dt->dt_hr > 0 ? dt->dt_hr -= 1: dt->dt_hr = 23);
			}
			else if(d - wday > 23)
				dt->dt_hr = dt->dt_hr;
			else 
				(dt->dt_hr > 0 ? dt->dt_hr -= 1: dt->dt_hr = 23);
		}
		else
			(dt->dt_hr > 0 ? dt->dt_hr -= 1: dt->dt_hr = 23);
		}
	}	
	long_time = TimeWrapper::getgmTime(dt);
#ifdef _VXWORKS_
	newtime = gmtime((const long unsigned int*) &long_time);
#else
	newtime = gmtime((const long *) &long_time);
#endif
	dt->dt_min = newtime->tm_min;
	dt->dt_sec = newtime->tm_sec;
	dt->dt_yr = newtime->tm_year + 1900;
	dt->dt_mo = newtime->tm_mon + 1;
	dt->dt_mday = newtime->tm_mday;
	dt->dt_wday = newtime->tm_wday;
	dt->dt_yday = newtime->tm_yday;
}
/************************************************************************/
/* Function: juldate_from_month_day					*/
/* Purpose : Calculate the Julian day from the month, day,  and year.   */
/* Inputs  : month, day, year                                           */
/* Outputs : Julian day                                                 */
/* Side FX : julday is  updated.                                        */
/* Comments:                                                            */
/************************************************************************/
Nat16 TimeWrapper::juldate_from_month_day( int *julday, int month, int day, int year)
{
  int days_since_jan1[] = {0, 31, 59, 90, 120, 151, 181, 212,
				243, 273, 304, 334};

  if ( (month > 12) || (month < 1) ||(day   > 31)  || (day   < 1))
       return ERROR;

  *julday = days_since_jan1[month - 1] + day;

  /* Add one extra day if in or past February during the leap years */
  if (!(year % 4) && month > 1)
       *julday += 1;
	
   return SUCCESS;
    
}
/************************************************************************/
/* Function    : epochDays										*/
/* Purpose     : Calculate number days since epoch (1/1/70)		*/
/* Inputs      : Year - YEAR0, month, day of month				*/
/* Outputs     : Days since 1/1/1990							*/
/* Comment     : The (year + 2)/4 term accounts for leap years, the	*/
/*		 first of which is 1972 (year - YEAR0 == 2)		*/
/************************************************************************/
Nat16 TimeWrapper::epochDays( Nat16 year, Nat16 month, Nat16 yearday )
{

    Reg Nat16	leap;
    Nat16 t;
    leap = (month > 2) ? ((year + 2)/4) : ((year + 1)/4);
    t = (365 * year) + leap + yearday - 1 ;
    return t;
}
gmTime TimeWrapper::getgmTime(DateTime *dtp)
{
    Reg Int16	yr;
    Reg Nat16	secs;
	
	if(dtp == 0)
		return 0;
	
    yr = (Int16)dtp->dt_yr - YEAR0;
    secs = (60 * dtp->dt_min) + dtp->dt_sec;
    return (SECS_PER_DAY * TimeWrapper::epochDays(yr, (Nat16)dtp->dt_mo, (Nat16)dtp->dt_yday)
	    + 3600L * dtp->dt_hr + secs);
}
Int32 TimeWrapper::getSystemClock()
{
#ifdef _WIN32_
	return clock();
#endif
#ifdef _VXWORKS_
	return tickGet();
#endif
return -1;
}
Int32 TimeWrapper::getSystemClkTck()
{
#ifdef _WIN32_
	return (Int32) CLK_TCK;
#endif
#ifdef _VXWORKS_
	return (Int32) sysClkRateGet();
#endif
return -1;
}
void TimeWrapper::setSystemClkTck(Int16 tcksPerSecond)
{
#ifdef _VXWORKS_
	sysClkRateSet(tcksPerSecond);
	//TODO: test this snippet
	if( (PC104_TIMEBASE % tcksPerSecond) != 0)
		sysClkRateSet(Euclid(PC104_TIMEBASE, tcksPerSecond));	
#endif
}
Int16 TimeWrapper::Euclid(Int16 a, Int16 b)
{
	if(b == 0)
		return a;
	return Euclid(b, a % b);
}

Timer::Timer()
{
	_startTime = 0;
	_stopTime = 0;
}
Timer::~Timer()
{
}
void Timer::reset()
{
	 _startTime = 0;
	 _stopTime = 0;
}
void Timer::start()
{
	_startTime = Clock::getSystemClock();
}

void Timer::stop()
{
	_stopTime = Clock::getSystemClock();
}

Int32 Timer::elapsedmin()
{     
   return (Int32)(elapsedsec()/60);
}
Int32 Timer::elapsedsec()
{
   return (Int32) (_stopTime - _startTime) / Clock::getSystemClkTck();   
}
Int32 Timer::elapsedmsec()
{
   return (Int32) 1000*(_stopTime - _startTime) / Clock::getSystemClkTck(); 
}
Time::Time()
{
}
Time::Time(DateTime *dt)
{
	if(dt != NULL)
		memcpy((void *)&_datetime, (void *)dt, sizeof(DateTime));
}
Time::~Time()
{

}
Time& Time::operator=(const Time &time) 
{
	this->_datetime.dt_wday = (Byte) time.weekday();
	this->_datetime.dt_mday = (Byte) time.monthday();
	this->_datetime.dt_yr = (Nat16) time.year();
	this->_datetime.dt_mo = (Byte) time.month();
	this->_datetime.dt_yday = (Nat16) time.yearday();
	this->_datetime.dt_hr = (Byte) time.hour();
	this->_datetime.dt_min = (Byte) time.minute();
	this->_datetime.dt_sec = (Byte) time.second();
	return *this;
}

Int16 Time::weekday() const
{
	return _datetime.dt_wday;
}

Int16 Time::year() const
{
	return _datetime.dt_yr;
}

Int16 Time::month() const
{
	return _datetime.dt_mo;
}

Int16 Time::yearday() const
{
	return _datetime.dt_yday;
}
Int16 Time::monthday() const
{
	return _datetime.dt_mday;
}
Int16 Time::hour() const
{
	return _datetime.dt_hr;
}

Int16 Time::minute() const
{
	return _datetime.dt_min;
}

Int16 Time::second() const
{
	return _datetime.dt_sec;
}

Boolean Time::isDay()
{
    if( _datetime.dt_hr < 12 )// hours since midnight - [0,23]
		return FALSE;

	return TRUE;
}
DateTime *Time::datetime()
{
	return &_datetime;
}
#ifdef _VXWORKS_
extern "C" {
/******************************************************************************
*
* sysRtcGet - get the current date abd time from a Real Time Clock chip
*
* The values are returned in a ANSI time structure.
* During initialization phase, the POSIX clock of the system is
* set according to the Real Time Clock time and date, thus
* it is recommended to use the POSIX functions and avoid calling
* this function from the application software,
* to acheive similar results but with greater portability.
*
* NOTE: Interrupts are locked during reading of the values from the chip.
*
* RETURNS: OK or ERROR
*
* SEE ALSO
* clockLib(), ansiTime
*
*/

STATUS sysRtcGet
    (
    struct tm *tm
    )
    {
    FAST int count = 200;
    FAST int ipl ;

    ipl = intLock();

    /* wait until registers update is done, then we got 244 us
     * to read the regs without loosing sanity
     */
    while((count--) && (REG_GET(0x0a) & 0x80));

    tm->tm_hour	= REG_GET( 0x04);	/* get BCD regs as is */
    tm->tm_min	= REG_GET( 0x02);	/* while ints are off, */
    tm->tm_sec	= REG_GET( 0x00);	/* and decode later */
    tm->tm_mon	= REG_GET( 0x08);
    tm->tm_mday	= REG_GET( 0x07);
    tm->tm_year	= REG_GET( 0x09);
    tm->tm_wday = REG_GET( 0x06);

    intUnlock(ipl);


    /* corrections - all registers are BCD, we need them in binary */
    tm->tm_hour	= BCD2BIN( tm->tm_hour );
    tm->tm_min	= BCD2BIN( tm->tm_min  );
    tm->tm_sec	= BCD2BIN( tm->tm_sec  );
    tm->tm_mon	= BCD2BIN( tm->tm_mon  );
    tm->tm_mday	= BCD2BIN( tm->tm_mday );
// ANSI says base at 1900 -- clock only holds from 00.99
    tm->tm_year	= BCD2BIN( tm->tm_year ) + 100;
    tm->tm_wday = BCD2BIN( tm->tm_wday );

    /* corrections -  some fields range is defined differently */
    if(tm->tm_mon > 0)
	tm->tm_mon -- ;	/* chip does 1-12, POSIX needs 0-11 */

    if(tm->tm_wday > 0)
	    tm->tm_wday -- ;	/* chip does 1-7, POSIX needs 0-6 */

    /* These fields are unknown, filled with 0 */
    tm->tm_yday = 0;	/* days since January 1		- [0, 365] */
    tm->tm_isdst= 0;	/* Daylight Saving Time flag */

    return OK ;
    }
/******************************************************************************
*
* sysRtcSet  - Set the time and date into the RTC chip
*
* NOTE
* Setting the time is done with interrupts locked, but it is expected
* to be called rarely.
*
* RETURNS: OK or ERROR if values are out of range.
*/

STATUS sysRtcSet
    (
    struct tm *timedate
    )
    {
    FAST int count = 200;
    FAST int ipl ;

    /* Check value ranges */
	timedate->tm_year -= 100;  //only support years in the 21st century!
 
    if( !CHECK_RANGE( timedate->tm_sec,  0, 59)) return ERROR ;
    if( !CHECK_RANGE( timedate->tm_min,  0, 59)) return ERROR ;
    if( !CHECK_RANGE( timedate->tm_hour, 0, 23)) return ERROR ;
    if( !CHECK_RANGE( timedate->tm_mday, 1, 31)) return ERROR ;
    if( !CHECK_RANGE( timedate->tm_mon , 0, 11)) return ERROR ;
    if( !CHECK_RANGE( timedate->tm_year, 0, 99)) return ERROR; 
    

    /* corrections - convert to BSD and add offset where needed. */
    timedate->tm_hour = BIN2BCD( timedate->tm_hour );
    timedate->tm_min  = BIN2BCD( timedate->tm_min ); 
    timedate->tm_sec  = BIN2BCD( timedate->tm_sec ); 
    timedate->tm_mon  = BIN2BCD( timedate->tm_mon+1 ); 
    timedate->tm_mday = BIN2BCD( timedate->tm_mday ); 
    timedate->tm_year = BIN2BCD( timedate->tm_year ); 
    timedate->tm_wday = BIN2BCD( timedate->tm_wday+1 ); 

    ipl = intLock();

    /* wait until registers update is done, then we got 244 us
     * to read the regs without loosing sanity
     */
    while((count--) && (REG_GET(0x0a) & 0x80));

    REG_SET( 0x04, timedate->tm_hour );
    REG_SET( 0x02, timedate->tm_min );
    REG_SET( 0x00, timedate->tm_sec );
    REG_SET( 0x08, timedate->tm_mon );
    REG_SET( 0x07, timedate->tm_mday);
    REG_SET( 0x09, timedate->tm_year);
    REG_SET( 0x06, timedate->tm_wday);

    intUnlock(ipl);

    return OK ;
    }

/******************************************************************************
*
* sysRtcSetWatchdog - set the watchdog timeout on the RTC chip
*
* This function initializes the watchdog alarm value. Generally, this
* is set to 30, 60 or 90 seconds depending on how long is takes 
* the target to boot
*
*/
void sysRtcSetWatchdog (int seconds)
    {
	/*Must be less than 0x99 seconds*/
	if(0x99 < BIN2BCD(seconds)) 
	{
		REG_SET( 0x0c, 0);				 /* read watchdog alarm register */
		REG_SET( 0x0d, BIN2BCD(seconds)); /* read watchdog alarm register */
    }
}

/******************************************************************************
*
* sysRtcResetWatchdog - reset the watchdog on the RTC chip
*
* This function should called every 30, 60 or 90 seconds depending on
* how the target is configured
*
*/
void sysRtcResetWatchdog ( void)
    {
    REG_GET( 0x0c); /* read watchdog alarm register */
    }

/******************************************************************************
*
* sysRtcInit - initialize the RTC chip
*
* This function should called from sysHwInit2(). With this particular
* device, there is nothing we need to do here.
*
*/
STATUS sysRtcInit ( void)
    {
    /* turn the oscilator on, just in case */
    REG_SET( 0x0a, 0x20 );
    REG_SET( 0x0b, 0x02 );	/* set 24-hr & BCD modes */
    return OK ;
    }

/******************************************************************************
*
* sysRtcShutdown - put the RTC chip in to sleep mode
*
* The sleep mode is designed to save on battery life during inactive
* storage of the equipment. During this time the date & time do not
* progress.
*
*/

void sysRtcShutdown(void)
    {
    REG_SET( 0x0a, 0x00 );
    }
/* End Of File */
};

STATUS dosFsDateTimeRetrieve( DOS_DATE_TIME* d )
  {
  struct tm t;
  /*
  * Per time.h:
  * map years since 1900 to current year
  * map January to 1
  * assume day of week not used.
  * See <vw>/h/time.h for struct tm definition;
  * See <vw>/h/dosFsLib for DOS_DATE_TIME [struct] definition.
  */

  if(sysRtcGet(&t) == OK) {

  d->dosdt_year = t.tm_year + 1900;
  d->dosdt_month = t.tm_mon + 1;
  d->dosdt_day = t.tm_mday ;
  d->dosdt_hour = t.tm_hour ;
  d->dosdt_minute = t.tm_min ;
  d->dosdt_second = t.tm_sec ;

  return( OK ) ; 
  }

  return( ERROR) ;

  } /* dosFsDateTimeRetrieve */

#endif// endif _VXWORKS_

