#include "utilities.h"
#include "tariff_log.h"

static int protection_counter = 0;

/// <summary>
/// Helper function
/// </summary>
/// <param name="pra_price"></param>
/// <returns></returns>
double Utilities::CalculatePricePerUnit(double pra_price, double durationUnit)
{
	try
	{
		double price_per_unit = pra_price;
        double unit = durationUnit;

        if(unit < 0 || unit > 65535 ) unit = 60.0f;
        price_per_unit /= unit; // Divided by 60 because price per unit is set per hour and we are using minutes
		//printf("Price per unit (min) is: %lf\n", price_per_unit);
		return price_per_unit;
	}
	catch (...)
	{
		throw std::invalid_argument("An error has occurred in CalculatePricePerUnit() function\n");
	}
}

/// <inheritdoc/>
time_t Utilities::GetCurrentLocalTime()
{
	try
	{
		time_t curr_time = time(NULL);
		tm tm_curr_time = {};
		memset(&tm_curr_time, '\0', sizeof(struct tm));

		tm_curr_time = *localtime(&curr_time);
        curr_time = mktime(&tm_curr_time); //- timezone;
		return curr_time;
	}
	catch (...)
	{
		throw std::invalid_argument("An error has occurred in GetCurrentLocalTime() function\n");
	}
}

/// <inheritdoc/>
int Utilities::ZellersAlgorithm(int day, int month, int year)
{
	int mon;
	if (month > 2) mon = month; //for march to december month code is same as month
	else {
		mon = (12 + month); //for Jan and Feb, month code will be 13 and 14
		year--; //decrease year for month Jan and Feb
	}
	int y = year % 100; //last two digit
	int c = year / 100; //first two digit
	int w = (day + floor((13 * (mon + 1)) / 5) + y + floor(y / 4) + floor(c / 4) + (5 * c));
	w = ((w + 5) % 7) + 1; //w % 7;
	return w;
}

/// <inheritdoc/>
struct tm Utilities::DateToStructTm(const char* dateStr)
{
	struct tm t = {};
	memset(&t, '\0', sizeof(struct tm));

	if (dateStr == nullptr || strlen(dateStr) <= 0) throw std::invalid_argument("DateToStructTm has failed parsing date string (null or empty)\n");
	try
	{
		int success = sscanf(dateStr, "%d-%d-%d", &t.tm_year, &t.tm_mon, &t.tm_mday);
		if (success != 3) throw std::invalid_argument("DateToStructTm() has failed parsing datetime string\n");

		t.tm_year = t.tm_year - 1900;
		t.tm_mon = t.tm_mon - 1;
		t.tm_isdst = 0;
		return t;
	}
	catch (...)
	{
		throw std::invalid_argument("An error has occurred in DateToStructTm() function\n");
	}
}

/// <inheritdoc/>
struct tm Utilities::TimeToStructTm(const char* timeStr, int year, int mon, int mday, int wday)
{
	struct tm t = {};
	memset(&t, '\0', sizeof(struct tm));

	if (timeStr == nullptr || strlen(timeStr) <= 0) throw std::invalid_argument("TimeToStructTm() has failed parsing time string (null or empty)\n");
	try
	{
		int success_time = sscanf(timeStr, "%d:%d:%d", &t.tm_hour, &t.tm_min, &t.tm_sec);
		if (success_time != 3) throw std::invalid_argument("TimeToStructTm() has failed parsing time string\n");

		struct tm tm_struct;
		t.tm_year = year;
		t.tm_mon = mon;
		t.tm_mday = mday;
		t.tm_wday = wday;
		t.tm_isdst = 0;
		return t;
	}
	catch (...)
	{
		throw std::invalid_argument("An error has occurred in TimeToStructTm() function\n");
	}
}

/// <inheritdoc/>
struct tm Utilities::DateTimeToStructTm(const char* dateTimeStr)
{
	struct tm t = {};
	memset(&t, '\0', sizeof(struct tm));

	if (dateTimeStr == nullptr || strlen(dateTimeStr) <= 0) throw std::invalid_argument("DateTimeToStructTm() has failed parsing date string (null or empty)");
	try
	{
		int success = sscanf(dateTimeStr, "%d-%d-%dT%d:%d:%dZ", &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &t.tm_sec);
		if (success != 6) throw std::invalid_argument("DateTimeToStructTm() has failed parsing datetime string\n");

		t.tm_year = t.tm_year - 1900;
		t.tm_mon = t.tm_mon - 1;
		t.tm_isdst = 0;
		return t;
	}
	catch (...)
	{
		throw std::invalid_argument("An error has occurred in DateTimeToStructTm() function\n");
	}
}

/// <inheritdoc/>
DayOfWeek Utilities::GetDayOfWeek(struct tm* t)
{
	if (t == nullptr) throw std::invalid_argument("GetDayOfWeekFromDate() => parameter 't' is null\n");
	try
	{
		int d = t->tm_mday;
		int m = t->tm_mon + 1;
		int y = t->tm_year + 1900;

		int wd = Utilities::ZellersAlgorithm(d, m, y);
		return static_cast<DayOfWeek>(wd);
	}
	catch (...)
	{
		throw std::invalid_argument("An error has occurred in GetDayOfWeekFromDate() function\n");
	}
}

/// <inheritdoc/>
bool Utilities::IsYearPeriodActive(Configuration* cfg, struct tm* currentDateTime_tm)
{
	if (cfg == nullptr) throw std::invalid_argument("IsYearPeriodActive() = > Configuration not set\n");
	if (currentDateTime_tm == nullptr) throw std::invalid_argument("IsYearPeriodActive() = > Current datetime not set\n");

	try
	{
		//// Parse input date
		int dayCurrent = currentDateTime_tm->tm_mday;
		int monthCurrent = currentDateTime_tm->tm_mon + 1;

		// Current date time
		int cdt = (monthCurrent * 100) + dayCurrent;

		multimap<int, ATBPeriodYear>::iterator year_period_itr;
		for (year_period_itr = cfg->YearPeriod.begin(); year_period_itr != cfg->YearPeriod.end(); ++year_period_itr)
		{
			int dStart = year_period_itr->second.pye_start_day;
			int dEnd = year_period_itr->second.pye_end_day;

			int mStart = year_period_itr->second.pye_start_month;
			int mEnd = year_period_itr->second.pye_end_month;

			int start = (mStart * 100) + dStart;
			int end = (mEnd * 100) + dEnd;

			if (cdt >= start && cdt <= end)
			{
				return true;
			}
		}
		return false;
	}
	catch (...)
	{
		cout << "IsYearPeriodActive() => An exception has occurred, ignoring check, returning true" << endl;
		return true;
	}
}

/// <inheritdoc/>
bool Utilities::CheckSpecialDay(Configuration* cfg, const char* currentDateTimeStr, int* specialDayId, double* specialDayPrice)
{
	try
	{
		*specialDayId = -1;
		*specialDayPrice = 0.0f;

		if (cfg == nullptr) throw std::invalid_argument("CheckSpecialDay() => configuration is not set\n");
		if (currentDateTimeStr == nullptr) throw std::invalid_argument("CheckSpecialDay() => invalid date/time string set\n");


		struct tm current_tm = Utilities::DateTimeToStructTm(currentDateTimeStr);
		//cout << "CheckSpecialDay() => Current: " << asctime(&current_tm) << endl;

		multimap<int, ATBSpecialDays>::iterator spec_days_itr;

		for (spec_days_itr = cfg->SpecialDays.begin(); spec_days_itr != cfg->SpecialDays.end(); spec_days_itr++)
		{
			int repeat_every_year = 0;
			repeat_every_year = spec_days_itr->second.ped_year;

			string start = spec_days_itr->second.ped_date_start;
			if (start.length() <= 0) continue;
			//cout << "CheckSpecialDay() => Start: " << start << endl;

			string end = spec_days_itr->second.ped_date_end;
			if (end.length() <= 0) continue;
			//cout << "CheckSpecialDay() => End: " << end << endl;

			struct tm start_tm = Utilities::DateToStructTm(start.c_str());
			//cout << "CheckSpecialDay() => Start: " << asctime(&start_tm) << endl;

			struct tm end_tm = Utilities::DateToStructTm(end.c_str());
			//cout << "CheckSpecialDay() => End: " << asctime(&end_tm) << endl;

			if (repeat_every_year <= 0)
			{
				//cout << "CheckSpecialDay() => Repeat every year is: 0" << endl;
				if ((current_tm.tm_year == start_tm.tm_year) && (current_tm.tm_year == end_tm.tm_year))
				{
					if ((current_tm.tm_mon >= start_tm.tm_mon) && (current_tm.tm_mon <= end_tm.tm_mon))
					{
						//cout << "CheckSpecialDay() => Month is in range between start and end" << endl;
						if ((current_tm.tm_mday >= start_tm.tm_mday) && (current_tm.tm_mday <= end_tm.tm_mday))
						{
							LOG_DEBUG("CheckSpecialDay() => SPECIAL DAY");
							*specialDayId = spec_days_itr->second.ped_id;
							*specialDayPrice = cfg->SpecialDaysWorktime.find(*specialDayId)->second.pedwt_price;
							return true;
						}
					}
				}
			}
			else
			{
				if ((current_tm.tm_mon >= start_tm.tm_mon) && (current_tm.tm_mon <= end_tm.tm_mon))
				{
					//cout << "CheckSpecialDay() => Month is in range between start and end" << endl;
					if ((current_tm.tm_mday >= start_tm.tm_mday) && (current_tm.tm_mday <= end_tm.tm_mday))
					{
						LOG_DEBUG("CheckSpecialDay() => SPECIAL DAY");
						*specialDayId = spec_days_itr->second.ped_id;
						*specialDayPrice = cfg->SpecialDaysWorktime.find(*specialDayId)->second.pedwt_price;
						return true;
					}
				}
			}
		}
		//cout << "CheckSpecialDay() => NOT SPECIAL DAY" << endl;
		return false;
	}
	catch (...)
	{
		throw std::invalid_argument("CheckSpecialDay() => An error has occurred\n");
		return false;
	}
}