#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <limits.h>
#include <ctype.h>

#include "error.h"

#define zulu_IMPORT
#include "zulu.h"


static int days_per_month[2][12] = {
	{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
	{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};


int zulu_isLeapYear(int year)
{
	return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0);
}


static int zulu_secondsPerYear(int year)
{
	return zulu_isLeapYear(year)? 86400 * 366 : 86400 * 365;
}


void zulu_timestampToDate(int ts, zulu_Date *d)
{
	int year = 1970;

	while(ts < 0){
		year--;
		ts += zulu_secondsPerYear(year);
	}

	do {
		int seconds_per_year = zulu_secondsPerYear(year);
		if( ts < seconds_per_year )
			break;
		ts -= seconds_per_year;
		year++;
	} while(1);
	d->year = year;

	int month = 0;
	int leap_year_idx = zulu_isLeapYear(year)? 1 : 0;
	do {
		int seconds_per_month = 86400 * days_per_month[leap_year_idx][month];
		if( ts < seconds_per_month )
			break;
		ts -= seconds_per_month;
		month++;
	} while(1);
	d->month = month + 1;

	d->day = ts / 86400 + 1;  ts -= 86400 * (d->day-1);
	d->hour = ts / 3600;  ts -= 3600 * d->hour;
	d->minutes = ts / 60;  ts -= 60 * d->minutes;
	d->seconds = ts;
}


double zulu_timestampToYear(int ts)
{
	int year = 1970;
	int seconds_per_year = zulu_secondsPerYear(year);

	while(ts < 0){
		year--;
		seconds_per_year = zulu_secondsPerYear(year);
		ts += seconds_per_year;
	}

	while(ts >= seconds_per_year){
		ts -= seconds_per_year;
		year++;
		seconds_per_year = zulu_secondsPerYear(year);
	}

	return year + (double) ts / seconds_per_year;
}


int zulu_dateToTimestamp(zulu_Date *d)
{
	int ts = 0;
	int year = 1970;

	while(d->year < year){
		year--;
		ts -= zulu_secondsPerYear(year);
		// Check underflow. With 32-bits it underflows at around 1901-12-13:
		// if this happen just for that year, we ignore this here and will check
		// the sign of the final result next so that the latest days of the 1901
		// are supported as well to cover the whole int range. Nice!
		if( ts > 0 && year != 1901 )
			error_internal("timestamp underflow for year %d", d->year);
	}

	while(year < d->year){
		ts += zulu_secondsPerYear(year);
		if( ts <= 0 )
			error_internal("timestamp overflow for year %d", d->year);
		year++;
	}
	int leap_year_idx = zulu_isLeapYear(year)? 1 : 0;

	// Increments ts by previous months [1,d->month-1]:
	if( !(1 <= d->month && d->month <= 12) )
		error_internal("month out of the allowed range: %d", d->month);
	int month = 1;
	while(month < d->month){
		ts += 86400 * days_per_month[leap_year_idx][month-1];
		month++;
	}

	// Increments ts by days, hours, minutes and seconds:
	if( !(1 <= d->day && d->day <= days_per_month[leap_year_idx][d->month-1]) )
		error_internal("day out of the allowed range: %d-%02d-%02d", d->day);
	if( !(0 <= d->hour && d->hour <= 23
		&& 0 <= d->minutes && d->minutes <= 59
		&& 0 <= d->seconds && d->seconds <= 59) )
		error_internal("invalid time: %02d:%02d:%02d", d->hour, d->minutes, d->seconds);
	ts += 86400 * (d->day-1) + 3600 * d->hour + 60 * d->minutes + d->seconds;
	if( ts < 0 && year > 1970 )
		error_internal("timestamp overflow for year %d", year);
	// This may happen only with 32-bits int at year 1901 as explained above:
	if( ts >= 0 && year < 1970 )
		error_internal("timestamp underflow for year %d", year);
	return ts;
}


double zulu_dateToYear(zulu_Date *d)
{
	zulu_Date d2 = *d;
	if( zulu_isLeapYear(d2.year) )
		d2.year = 1972;
	else
		d2.year = 1970;
	return zulu_timestampToYear(zulu_dateToTimestamp(&d2)) - d2.year + d->year;
}


int zulu_yearToTimestamp(double year)
{
	if( !(INT_MIN <= year && year <= INT_MAX) )
		error_internal("year overflows int capacity: %g", year);
	int year_i = (int) year;
	int ts = 0;
	int base_year = 1970;

	while(base_year > year_i){
		base_year--;
		ts -= zulu_secondsPerYear(base_year);
		// Check underflow. With 32-bits it underflows at around 1901-12-13:
		// if this happen just for that year, we ignore this here and will check
		// the sign of the final result next so that the latest days of the 1901
		// are supported as well to cover the whole int range. Nice!
		if( ts > 0 && year_i != 1901 )
			error_internal("timestamp underflow for year %g", year);
	}

	while(base_year < year_i){
		ts += zulu_secondsPerYear(base_year);
		base_year++;
		if( ts < 0 )
			error_internal("timestamp overflow for year %g", year);
	}

	int seconds_per_year = zulu_secondsPerYear(year_i);
	// The "(int)" cast below is required to prevent gcc rounding instead of
	// trunk when ts < 0!
	ts += (int) ((year - year_i) * seconds_per_year + 0.5);
	if( ts < 0 && year_i >= 1970 )
		error_internal("timestamp overflow for year %g", year);
	// This may happen only with 32-bits int at year 1901 as explained above:
	if( ts >= 0 && year_i < 1970 )
		error_internal("timestamp underflow for year %g", year);
	return ts;
}


void zulu_yearToDate(double year, zulu_Date *d)
{
	if( !(INT_MIN <= year && year <= INT_MAX) )
		error_internal("year out of the range: %g", year);
	int year_i = (int) year;
	if( zulu_isLeapYear(year_i) )
		// Set month, day, h:m:s for (any) leap year - adjust year next.
		zulu_timestampToDate(zulu_yearToTimestamp(year - year_i + 1972), d);
	else
		// Set month, day, h:m:s for (any) non leap year - adjust year next.
		zulu_timestampToDate(zulu_yearToTimestamp(year - year_i + 1970), d);
	// Set year.
	d->year = year_i;
}


static int zulu_scanDigits(char **s, int n, int *res)
{
	*res = 0;
	while( n > 0 && isdigit(**s) ){
		*res = 10 * *res + **s - '0';
		(*s)++;
		n--;
	}
	return n == 0;
}


int zulu_dateParse(char *s, zulu_Date *z)
{
	if( s == NULL )
		return 0;
	int n = 0, year, month, day = 1, hour = 0, minutes = 0, seconds = 0;
	if( ! zulu_scanDigits(&s, 4, &year) ) return 0;
	if( *s != '-' ) return 0;
	s++;
	if( ! zulu_scanDigits(&s, 2, &month) ) return 0;
	n = 2;
	if( *s == '-' ){
		s++;
		if( ! zulu_scanDigits(&s, 2, &day) ) return 0;
		n++;
		if( *s == 'T' ){
			s++;
			if( ! zulu_scanDigits(&s, 2, &hour) )  return 0;
			if( *s != ':' ) return 0;
			s++;
			if( ! zulu_scanDigits(&s, 2, &minutes) )  return 0;
			n += 2;
			if( *s == ':' ){
				s++;
				if( ! zulu_scanDigits(&s, 2, &seconds) )  return 0;
				n++;
			}
		}
	}
	if( *s != 0 )
		return 0;
	
	if( !( 1583 <= year && year <= 9999
	&& 1 <= month && month <= 12
	&& 1 <= day && day <= 31
	&& day <= days_per_month[zulu_isLeapYear(year)? 1 : 0][month-1] ) )
		return 0;
	z->year = year;  z->month = month;  z->day = day;
	z->hour = hour;  z->minutes = minutes;  z->seconds = seconds;
	return n;
}


void zulu_dateFormat(zulu_Date *d, char *s, int s_capacity)
{
	if( s_capacity >= 20 )
		snprintf(s, s_capacity, "%04d-%02d-%02dT%02d:%02d:%02d",
			d->year, d->month, d->day, d->hour, d->minutes, d->seconds);
	else if( s_capacity >= 17 )
		snprintf(s, s_capacity, "%04d-%02d-%02dT%02d:%02d",
			d->year, d->month, d->day, d->hour, d->minutes);
	else if( s_capacity >= 11 )
		snprintf(s, s_capacity, "%04d-%02d-%02d",
			d->year, d->month, d->day);
	else if( s_capacity >= 8 )
		snprintf(s, s_capacity, "%04d-%02d",
			d->year, d->month);
	else
		error_internal("destination string buffer too small: %d", s_capacity);
}


int zulu_dateCompare(zulu_Date *a, zulu_Date *b)
{
	int r;
	if( (r = a->year    - b->year   ) )  return r;
	if( (r = a->month   - b->month  ) )  return r;
	if( (r = a->day     - b->day    ) )  return r;
	if( (r = a->hour    - b->hour   ) )  return r;
	if( (r = a->minutes - b->minutes) )  return r;
	if( (r = a->seconds - b->seconds) )  return r;
	return 0;
}