#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <limits.h>
#include <stdint.h>
#include <math.h>

#include "../../src/util/zulu.h"

static int err;

/**
 * Compares all the conversion routines together and vs. the expected values.
 * @param exp_ts Expected timestamp.
 * @param exp_year Expected year.
 * @param exp_d Expected date.
 */
static void test_all(int exp_ts, double exp_year, zulu_Date *exp_d)
{
/*
	printf("testing: ts=%d, year=%.14g, %d-%02d-%02d %02d:%02d:%02d\n",
		ts, year,
		d->year, d->month, d->day, d->hour, d->minutes, d->seconds);
*/
	
	zulu_Date got_d;
	
	// half second max err
	assert( fabs( zulu_timestampToYear(exp_ts) - exp_year ) < 0.5/(365*86400) );
	
	zulu_timestampToDate(exp_ts, &got_d);
	assert( zulu_dateCompare(&got_d, exp_d) == 0 );
	
	assert(zulu_yearToTimestamp(exp_year) == exp_ts);
	
	zulu_yearToDate(exp_year, &got_d);
	assert( zulu_dateCompare(&got_d, exp_d) == 0 );
	
	assert(zulu_dateToTimestamp(exp_d) == exp_ts);
	
	// half second max err
	assert( fabs( zulu_dateToYear(exp_d) - exp_year) < 0.5/(365*86400));
}


static void test_dateParse(char *s, int exp_n, zulu_Date *exp_d)
{
	zulu_Date got_d;
	int got_n = zulu_dateParse(s, &got_d);
	if( got_n != exp_n ){
		err++;
		printf("FAILED parsing date %s:\n\tgot_n=%d,\n\texp_n=%d\n", s, got_n, exp_n);
		return;
	}
	if( exp_n == 0 )
		return;
	if( zulu_dateCompare(&got_d, exp_d) != 0 ){
		err++;
		char got_d_s[20], exp_d_s[20];
		zulu_dateFormat(&got_d, got_d_s, sizeof(got_d_s));
		zulu_dateFormat( exp_d, exp_d_s, sizeof(exp_d_s));
		printf("FAILED parsing date %s:\n\tgot %s,\n\texp %s\n", s, got_d_s, exp_d_s);
	}
}


int main(int argc, char** argv)
{
	
	// 1970-01-01 00:00:00 UTC
	test_all(0, 1970.0, &(zulu_Date){1970, 1, 1, 0, 0, 0});
	
	// Test negative ts.
	test_all(
		//-31404304,  // $ date --date="1969-01-02T12:34:56Z" +%s
		-365*86400.0 + 86400.0 + 12*3600 + 34*60 + 56,
		1969.0 + (86400.0 + 12*3600 + 34*60 + 56)/(365*86400),
		&(zulu_Date){1969, 1, 2, 12, 34, 56}
	);
	
	// Check max timestamp only for 32-bits int (for 64-bits int our
	// dumb loops would take a while to complete... :-):
	test_all(INT32_MIN, 1901.9503155758, &(zulu_Date){1901, 12, 13, 20, 45, 52});
	test_all(INT32_MAX, 2038.0496843924, &(zulu_Date){2038, 1, 19, 3, 14, 7});
	
	// Exact middle of the year 2017.
	test_all(
		1498996800,  // $ date --date="2017-07-02T12:00:00Z" +%s
		2017.5,
		&(zulu_Date){2017, 7, 2, 12, 0, 0}
	);
	
	// 1970-01-01T12:34:56Z
	test_all(
		12 * 3600 + 34 * 60 + 56,
		1970 + (12 * 3600 + 34 * 60 + 56) / (365.0 * 86400),
		&(zulu_Date){1970, 1, 1, 12, 34, 56}
	);
	
	// Brute force test, only on the basic conversions functions: from ts to
	// year and date and back.
	// Step by a bit less than a day/hour/min to exercise all the fields of the date.
	{
		int ts;
		for(ts = INT32_MIN; 1 <= ts && ts <= INT32_MAX; ts += 80000){
			double year = zulu_timestampToYear(ts);
			zulu_Date d;
			zulu_timestampToDate(ts, &d);
			assert(zulu_dateToTimestamp(&d) == ts);
			assert( zulu_yearToTimestamp(year) == ts );
		}

	}
	
	// Invalid date and time strings:
	test_dateParse(NULL, 0, NULL);
	test_dateParse("", 0, NULL);
	test_dateParse(" ", 0, NULL);
	test_dateParse("x", 0, NULL);
	test_dateParse("200-01", 0, NULL); // year: few digits
	test_dateParse("20000-01", 0, NULL); // year: too many digits
	test_dateParse("1582-1", 0, NULL); // year not in range
	test_dateParse("2000-1", 0, NULL); // month: few digits
	test_dateParse("2000-001", 0, NULL); // month: too many digits
	test_dateParse("2000-00", 0, NULL); // month: not in range
	test_dateParse("2000-01-0", 0, NULL); // day: few digits
	test_dateParse("2000-01-001", 0, NULL); // day: too many digits
	test_dateParse("2000-01-00", 0, NULL); // day: not in range
	test_dateParse("2001-02-29", 0, NULL); // not a leap year
	test_dateParse("2001-01-01T", 0, NULL); // missing expected hour
	test_dateParse("2001-01-01T99", 0, NULL); // hour: out or range
	
	// Valid date and time strings:
	test_dateParse("2017-10-20T12:34:56", 6, &(zulu_Date){2017,10,20,12,34,56});
	test_dateParse("2017-10-20T12:34", 5, &(zulu_Date){2017,10,20,12,34,0});
	test_dateParse("2017-10-20", 3, &(zulu_Date){2017,10,20,0,0,0});
	test_dateParse("2017-10", 2, &(zulu_Date){2017,10,1,0,0,0});
	
	return err == 0? 0 : 1;
}

