/*
** date.cpp
**
** VM exports for engine backend classes
**
**---------------------------------------------------------------------------
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
**    derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
**
*/ 

#include <time.h>
#include "c_dispatch.h"
#include "vm.h"
#include "zstring.h"
#include "printf.h"

time_t 			epochoffset = 0;		// epoch start in seconds (0 = January 1st, 1970)


//==========================================================================
//
// CCMD setdate
//
// Set the time to a specific value
//
//==========================================================================

UNSAFE_CCMD(setdate)
{
	if (argv.argc() != 3)
	{
		Printf("setdate HH:MM:SS DD-MM-YYYY: Set the current date\n");
		return;
	}

	time_t today;
	time(&today);
	struct tm* timeinfo = localtime(&today);
	if (timeinfo != nullptr)
	{
		auto clock = FString(argv[1]).Split(":");
		auto date  = FString(argv[2]).Split("-");
		if(clock.Size() != 3 || date.Size() != 3)
		{
			Printf("setdate HH:MM:SS DD-MM-YYYY: Set the current date\n");
			return;
		}

		if(!clock[0].IsInt())
		{
			Printf("Invalid hour\n");
			return;
		}
		if (!clock[1].IsInt())
		{
			Printf("Invalid minutes\n");
			return;
		}
		if (!clock[2].IsInt())
		{
			Printf("Invalid seconds\n");
			return;
		}
		if (!date[0].IsInt())
		{
			Printf("Invalid day\n");
			return;
		}
		if (!date[1].IsInt())
		{
			Printf("Invalid month\n");
			return;
		}
		if (!date[2].IsInt())
		{
			Printf("Invalid year\n");
			return;
		}

		//Set Date
		timeinfo->tm_hour = int( clock[0].ToLong() );
		timeinfo->tm_min  = int( clock[1].ToLong() );
		timeinfo->tm_sec  = int( clock[2].ToLong() );
		timeinfo->tm_mday = int(  date[0].ToLong() );
		timeinfo->tm_mon  = int(  date[1].ToLong() - 1);     // Month interally is 0 - 11
		timeinfo->tm_year = int(  date[2].ToLong() - 1900 ); // Year interally is 00 - 138

		time_t newTime = mktime(timeinfo);
		tm* t_old = localtime(&today);
		time_t oldTime = mktime(t_old);

		if (newTime == -1 || oldTime == -1)
		{
			Printf("Unable to set the date\n");
			return;
		}

		epochoffset = newTime - oldTime;

		// This deals with some inconsistent display behaviour for DST
		// In this case, we want to emulate GCC's behaviour
		today += epochoffset;
		struct tm* t_new = localtime(&today);
		if (t_new != nullptr)
		{
			char timeString[1024];
			if (strftime(timeString, sizeof(timeString), "%H", t_new))
			{
				auto hour = FString(timeString).ToLong();
				if (hour - clock[0].ToLong() == -1 || hour - clock[0].ToLong() == 23)
					epochoffset += 3600;
				else if (hour - clock[0].ToLong() == 1 || hour - clock[0].ToLong() == -23)
					epochoffset -= 3600;
			}
		}

		return;
	}
	else
	{
		Printf("Unable to set the date\n");
		return;
	}
}

CCMD(getdate)
{
	time_t now;
	time(&now);
	now += epochoffset;
	struct tm* timeinfo = localtime(&now);
	if (timeinfo != nullptr)
	{
		char timeString[1024];
		if (strftime(timeString, sizeof(timeString), "%H:%M:%S %d-%m-%Y%n", timeinfo))
			Printf("%s\n", timeString);
		else
			Printf("Error Retrieving Current Date\n");
	}
	else
	{
		Printf("Error Retrieving Current Date\n");
	}
}

//=====================================================================================
//
//
//
//=====================================================================================

extern time_t epochoffset;

static int GetEpochTime()
{
	time_t now;
	time(&now);
	return now != (time_t)(-1) ? int(now + epochoffset) : -1;
}

//Returns an empty string if the Strf tokens are valid, otherwise returns the problematic token
static FString CheckStrfString(FString timeForm)
{
	// Valid Characters after %
	const char validSingles[] = { 'a','A','b','B','c','C','d','D','e','F','g','G','h','H','I','j','m','M','n','p','r','R','S','t','T','u','U','V','w','W','x','X','y','Y','z','Z' };

	timeForm.Substitute("%%", "%a"); //Prevent %% from causing tokenizing problems
	timeForm = "a" + timeForm; //Prevent %* at the beginning from causing a false error from tokenizing

	auto tokens = timeForm.Split("%");
	for (auto t : tokens)
	{
		bool found = false;
		// % at end
		if (t.Len() == 0) return FString("%");

		// Single Character
		for (size_t i = 0; i < sizeof(validSingles) / sizeof(validSingles[0]); i++)
		{
			if (t[0] == validSingles[i])
			{
				found = true;
				break;
			}
		}
		if (found) continue;
		return FString("%") + t[0];
	}
	return "";
}

static void FormatTime(const FString& timeForm, int timeVal, FString* result)
{
	FString error = CheckStrfString(timeForm);
	if (!error.IsEmpty())
		ThrowAbortException(X_FORMAT_ERROR, "'%s' is not a valid format specifier of SystemTime.Format()", error.GetChars());

	time_t val = timeVal;
	struct tm* timeinfo = localtime(&val);
	if (timeinfo != nullptr)
	{
		char timeString[1024];
		if (strftime(timeString, sizeof(timeString), timeForm, timeinfo))
			*result = timeString;
	}
}

DEFINE_ACTION_FUNCTION_NATIVE(_SystemTime, Now, GetEpochTime)
{
	PARAM_PROLOGUE;
	ACTION_RETURN_INT(GetEpochTime());
}

DEFINE_ACTION_FUNCTION_NATIVE(_SystemTime, Format, FormatTime)
{
	PARAM_PROLOGUE;
	PARAM_STRING(timeForm);
	PARAM_INT(timeVal);
	FString result;
	FormatTime(timeForm, timeVal, &result);
	ACTION_RETURN_STRING(result);
}