//! \file rtc.c
//! \author tkl
//! \date Jul 8, 2012
//! \brief Source file of the architecture independent rtc implementation.
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include "rtc.h"

//-----------------------------------------------------------------------------
int rtc_open(const struct rtc *device) {
	if(NULL == device) {
		return (-1);
	}

	rtc_fp_open_t open = device->fp->open;
	return (open(device->arch_dep_device));
}

//-----------------------------------------------------------------------------
int rtc_close(const struct rtc *device) {
	if(NULL == device) {
		return (-1);
	}

	rtc_fp_close_t close = device->fp->close;
	return (close(device->arch_dep_device));
}

//-----------------------------------------------------------------------------
void rtc_set_time(const struct rtc *device, const struct loki_time *time) {
	if(NULL == device) {
		return;
	}

	rtc_fp_set_time_t set_time = device->fp->set_time;
	set_time(device->arch_dep_device, time);
}

//-----------------------------------------------------------------------------
struct loki_time *rtc_get_time(const struct rtc *device, struct loki_time *time) {
	if(NULL == device) {
		return (NULL);
	}

	rtc_fp_get_time_t get_time = device->fp->get_time;
	return (get_time(device->arch_dep_device, time));
}

#ifndef __isleap
//! Nonzero if YEAR is a leap year (every 4 years, except every 100th isn't, and every 400th is).
#define	__isleap(year)	\
  ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))
#endif

//! Seconds per hour.
#define	SECS_PER_HOUR	(long)(60 * 60)

//! Seconds per day.
#define	SECS_PER_DAY	(long)(SECS_PER_HOUR * 24)

//! Clocks per sec.
#define CLOCKS_PER_SEC  1000


static const uint8_t __mon_lengths[2][12] = {
		{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },	/* Normal years.  */
		{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }	/* Leap years.  */
};

//-----------------------------------------------------------------------------
uint32_t time_to_tick(const struct loki_time *time) {
	if(NULL == time) {
		return (0);
	}
	uint32_t ret = 0;
	uint32_t year = time->year - 1970;
	ret = time->sec + time->min * 60 + time->hour * 3600;
	uint32_t days_in_year = 0;
	switch(time->mon) {
	case 1:
		days_in_year = time->day;
		break;
	case 2:
		days_in_year = time->day + 31;
		break;
	case 3:
		days_in_year = time->day + 31 + 28;
		break;
	case 4:
		days_in_year = time->day + 31 + 28 + 31;
		break;
	case 5:
		days_in_year = time->day + 31 + 28 + 31 + 30;
		break;
	case 6:
		days_in_year = time->day + 31 + 28 + 31 + 30 + 31;
		break;
	case 7:
		days_in_year = time->day + 31 + 28 + 31 + 30 + 31 + 30;
		break;
	case 8:
		days_in_year = time->day + 31 + 28 + 31 + 30 + 31 + 30 + 31;
		break;
	case 9:
		days_in_year = time->day + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31;
		break;
	case 10:
		days_in_year = time->day + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30;
		break;
	case 11:
		days_in_year = time->day + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31;
		break;
	case 12:
		days_in_year = time->day + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30;
		break;
	}
	if(days_in_year > 0) {
		days_in_year--;
	}
	uint32_t leap_days = 0;
	uint32_t y = time->year;
	while(y >= 1970) {
		leap_days += __isleap(y) ? 1 : 0;
		y--;
	}
	if(__isleap(time->year)) {
		if(days_in_year < 59) {
			if(leap_days > 0) {
				leap_days--;
			}
		}
	}
	ret += (days_in_year + leap_days) * 60 * 60 * 24;
	ret += (year * 60 * 60 * 24 * 365);
	return (ret);
}

//-----------------------------------------------------------------------------
struct loki_time tick_to_time(uint32_t tick) {
	struct loki_time ret;
	uint32_t days, rem;
	uint32_t y;
	char *ip;

	days = tick / SECS_PER_DAY;
	rem = tick % SECS_PER_DAY;
	while (rem < 0) {
		rem += SECS_PER_DAY;
		--days;
	}
	while (rem >= SECS_PER_DAY) {
		rem -= SECS_PER_DAY;
		++days;
	}
	ret.hour = rem / SECS_PER_HOUR;
	rem %= SECS_PER_HOUR;
	ret.min = rem / 60;
	ret.sec = rem % 60;
	y = 1970;
	while (days >= (rem = __isleap(y) ? 366 : 365)) {
		++y;
		days -= rem;
	}
	while (days < 0) {
		--y;
		days += __isleap(y) ? 366 : 365;
	}
	ret.year = y;
	ip = (char *)__mon_lengths[__isleap(y)];
	for (y = 0; days >= ip[y]; ++y)
	days -= ip[y];
	ret.mon = y + 1;
	ret.day = days + 1;
	return (ret);
}


//-----------------------------------------------------------------------------
int rtc_start_interval_event(const struct rtc *device, enum rtc_interval interval,
		const void *callback, const void *argument) {
	if(NULL == device) {
		return (-1);
	}
	rtc_fp_start_interval_event start_interval = device->fp->start_interval;
	return (start_interval(device->arch_dep_device, interval, callback, argument));
}

//-----------------------------------------------------------------------------
int rtc_stop_interval_event(const struct rtc *device) {
	if(NULL == device) {
		return (-1);
	}
	rtc_fp_stop_interval_event stop_interval = device->fp->stop_interval;
	return (stop_interval(device->arch_dep_device));
}

//-----------------------------------------------------------------------------
int rtc_start_alarm_event(const struct rtc *device,
		const struct loki_time *alarm_time, enum rtc_alarm_mask alarm_mask,
		const void *callback, const void *argument)
{
	if(NULL == device) {
		return (-1);
	}
	rtc_fp_start_alarm_event start_alarm = device->fp->start_alarm;
	return (start_alarm(device->arch_dep_device, alarm_time, alarm_mask,
			callback, argument));
}

//-----------------------------------------------------------------------------
int rtc_stop_alarm_event(const struct rtc *device) {
	if(NULL == device) {
		return (-1);
	}
	rtc_fp_stop_alarm_event stop_alarm = device->fp->stop_alarm;
	return (stop_alarm(device->arch_dep_device));
}