#include <stdint.h>
#include <stdio.h>
#include "../include/DateTime.h" // conflicts with datetime.h from python includes
#include "Utils.h"
#include "Test.h"


BEGIN_NAMESPACE


const int epochYear = -20000;   //  20,000 BC

const int janOff = 0;         const int febOff = janOff+31; const int marOff = febOff+28;
const int aprOff = marOff+31; const int mayOff = aprOff+30; const int junOff = mayOff+31;
const int julOff = junOff+30; const int augOff = julOff+31;	const int sepOff = augOff+31;
const int octOff = sepOff+30; const int novOff = octOff+31; const int decOff = novOff+30;


const int yearOffsetByMonth[2][13] = {
	/* Non-leap years  (first index is dummy for Jan being represented by 01, no 00) */
	{ 0, janOff, febOff, marOff, aprOff, mayOff, junOff,
							   julOff, augOff, sepOff, octOff, novOff, decOff },
	/* Offsets for leap years */
	{ 0, janOff, febOff, marOff+1, aprOff+1, mayOff+1, junOff+1,
							   julOff+1, augOff+1, sepOff+1, octOff+1, novOff+1, decOff+1 }
};


static bool isLeapYear(int year)
{
	bool leapYear = false;
	if ( (year % 4) == 0 ) {
		if ( (year % 100) == 0 ) {
			if ( (year % 400) == 0 ) {
				leapYear = true;
			}
		} else {
			leapYear = true;
		}
	}
	return leapYear;
}


void DateTime::FromString(const char* a_str)
{
	int year, month, day; 
	int hour, minute;
	double seconds; 
	uint64_t microSeconds = 0ULL;
	if (sscanf(a_str, "%d-%d-%d %d:%d:%lf", &year, &month, &day, &hour, &minute, &seconds) == 6)
	{
		bool leapYear = isLeapYear(year);

		/* We will assume that always a leap year for easier de-coding of the microSeconds value */
		/* As an int it will still be useful for comparisons, however the calculation for elapsed time */
		/* is no longer so straight forward, particularly if calculating over a period of years
			so it wouldn't be recommended to use this for that, or a special elapsed function is needed */
		day = (year - epochYear) * (366) + yearOffsetByMonth[leapYear?1:0][month] + day;

		microSeconds = (uint64_t)(seconds * 1000000.0);
		microSeconds += minute * 60000000ULL;
		microSeconds += hour * 3600000000ULL;
		microSeconds += day * 86400000000ULL;
	}
	m_microSecondsFromEpoch = microSeconds;
}


String DateTime::ToString() const
{
	int year, month, day; 
	int hour, minute;
	int seconds; 
	uint64_t microSeconds = m_microSecondsFromEpoch;
	
	microSeconds -= (day = int(microSeconds / 86400000000ULL)) * 86400000000ULL;
	microSeconds -= (hour = int(microSeconds / 3600000000ULL)) * 3600000000ULL;
	microSeconds -= (minute = int(microSeconds / 60000000ULL)) * 60000000ULL;
	microSeconds -= (seconds = int(microSeconds / 1000000ULL)) * 1000000ULL;

	day -= (year = day / 366) * 366;
	year += epochYear;

	bool leapYear = isLeapYear(year);
	month = 0;
	for (int i = 0; i < 13; i++)
	{
		if (yearOffsetByMonth[leapYear?1:0][i] > day)
		{
			month = i - 1;
			break;
		}
	}
	
	day -= yearOffsetByMonth[leapYear?1:0][month];

	//std::string str = string_format("%d-%02d-%02d %02d:%02d:%02d.%03d", year, month, day, hour, minute, seconds, (int)microSeconds);
	String str("%d-%02d-%02d %02d:%02d:%02d.%03d", year, month, day, hour, minute, seconds, (int)microSeconds);
	return str;
}


uint64_t DateTime::Elapsed(const DateTime& a_startTime) const
{
	int day = int(m_microSecondsFromEpoch / 86400000000ULL);
	int startDay = int(a_startTime.m_microSecondsFromEpoch / 86400000000ULL);
	if (day != startDay)
	{
		// we are crossing a day boundary so we need to do more checks
		int year = day / 366;
		int startYear = startDay / 366;
		if (year != startYear)
		{
			int year1 = (year < startYear) ? year : startYear;
			int year2 = (year > startYear) ? year : startYear;
			// we are crossing a year boundary so need to do more work
			int nonLeapYearCount = 0;
			// we count the non-leap years between the years
			for (int y = year1; y < year2; y++)
				nonLeapYearCount += isLeapYear(y) ? 1 : 0;  // TODO: fix this brute force approach
			// Fixup for the fact we store years as if they have space for leap years for all years even though they don't
			return (m_microSecondsFromEpoch - a_startTime.m_microSecondsFromEpoch) - 86400000000ULL*nonLeapYearCount;
		}
	}
	// Simple case if we are inside the same year
	return m_microSecondsFromEpoch - a_startTime.m_microSecondsFromEpoch;
}


uint64_t DateTime::ToInt() const
{
	return m_microSecondsFromEpoch;
}


UNIT_TEST(TestLeapYear, 0)
{
	CHECK(isLeapYear(1997) == false);
	CHECK(isLeapYear(1900) == false);
	CHECK(isLeapYear(2000) == true);
	CHECK(isLeapYear(2012) == true);
}


UNIT_TEST(TestDateTime, 0)
{
	DateTime t;

	t.FromString("2012-01-01 01:01:01.000");
	CHECK(t.ToString() == "2012-01-01 01:01:01.000");
	
	t.FromString("2012-11-05 01:01:01.000");
	CHECK(t.ToString() == "2012-11-05 01:01:01.000");

	t.FromString("2001-01-01 01:01:01.000");
	CHECK(t.ToString() == "2001-01-01 01:01:01.000");
	
	t.FromString("2001-11-05 01:01:01.000");
	CHECK(t.ToString() == "2001-11-05 01:01:01.000");
}


END_NAMESPACE
