local ormDate       = require "LuaOrm.Date"
require "reporter.mydate"
local m_utcTimezone = {offset = 0} -- Default to UTC

--=============================================================================
-- functions for slices
--=============================================================================
local function addYearToSlices(slices, d)
	table.insert(slices, {year = d})
end

local function addMonthToSlices(slices, d)
	table.insert(slices, {month = d})
end

local function addDayToSlices(slices, d)
	table.insert(slices, {day = d})
end

local function addHalfHourToSlices(slices, d)
	table.insert(slices, {halfHour = d})
end

local function sliceToString(slice)
	if slice.halfHour then
		return string.format("halfHour|%d-%0.2d-%0.2d %0.2d:%0.2d", slice.halfHour:year(), slice.halfHour:month(), slice.halfHour:day(), slice.halfHour:hour(), slice.halfHour:minute())
	elseif slice.day then
		return string.format("day|%d-%0.2d-%0.2d", slice.day:year(), slice.day:month(), slice.day:day())
	elseif slice.month then
		return string.format("month|%d-%0.2d", slice.month:year(), slice.month:month())
	else
		return string.format("year|%d", slice.year:year())
	end
end

local function slicesToStrings(slices)
	local strs = {}
	for _, slice in pairs(slices) do
		table.insert(strs, sliceToString(slice))
	end
	return strs
end
--=============================================================================
local function getNextYear(startDate)
	return startDate:add(1)
end

local function getNextMonth(startDate)
	return startDate:add(0, 1)
end

local function getNextDay(startDate)
	return startDate:add(0, 0, 1)
end

local function getNextHalfHour(startDate)
	if startDate:minute() >= 30 then
		local t = startDate:add(0, 0, 0, 1)
		return ormDate({year = t:year(), month = t:month(), day = t:day(), hour = t:hour(), min=0})
	else
		return ormDate({year = startDate:year(), month = startDate:month(), day = startDate:day(), hour = startDate:hour(), min=30})
	end
end
--=============================================================================
local function isStartOfYear(d)
	return d:month() == 1 and d:day()==1 and d:hour() == 0 and d:minute() == 0
end

local function isStartOfMonth(d)
	return d:day()==1 and d:hour() == 0 and d:minute() == 0
end

local function isStartOfDay(d)
	return d:hour() == 0 and d:minute() == 0
end

local function lessOrEqual(startDate, endDate)
	return startDate:compare(endDate) or startDate:equals(endDate)
end

--=============================================================================
--dates should be in the format YYYY-MM-DD HH:mm:SS
local function getSlicesOld(startDateStr, endDateStr)
	local startDate = ormDate(startDateStr)
	local endDate   = ormDate(endDateStr)
	if endDate:compare(startDate) then
		return nil, "invalid period!"
	end

	local currentDate = ormDate(startDate)
	local slices = {}
-- We will try to cover the gap between currentDate and endDate with the
--  bigger slice we can, that is first we will try with a year slice, then a month
--  and day slices, if none of them fit we will use half hour slices.
--
-- To determine if a slice fits the gap, two conditions must be true:
--  currentDate is the start of a slice, and adding aslice is not beyond endDate
	while currentDate:compare(endDate) do
		if isStartOfYear(currentDate) and lessOrEqual(getNextYear(currentDate), endDate) then
			addYearToSlices(slices, currentDate)
			currentDate = getNextYear(currentDate)
		elseif isStartOfMonth(currentDate) and lessOrEqual(getNextMonth(currentDate), endDate) then
			addMonthToSlices(slices, currentDate)
			currentDate = getNextMonth(currentDate)
		elseif isStartOfDay(currentDate) and lessOrEqual(getNextDay(currentDate), endDate) then
			addDayToSlices(slices, currentDate)
			currentDate = getNextDay(currentDate)
		else
			addHalfHourToSlices(slices, currentDate)
			currentDate = getNextHalfHour(currentDate)
		end
	end

	return slicesToStrings(slices)
end

local function Phase(origin, phase)
	return math.floor(origin/phase), (origin % phase)
end

-- Returns a time as HH:MM:SS
local function formatTime(seconds)
	if seconds < 0 then
		return "-" .. formatTime(-seconds)
	end
	local minutes, seconds = Phase(seconds, 60)
	local hours, minutes = Phase(minutes, 60)
	return ("%02d:%02d:%02d"):format( hours, minutes, seconds )
end

local function makeHalfHourSlice(utcDate)
	return "halfHour|" .. RPT.Date():toString(utcDate,"%Y-%m-%d %H:%M")
end

--=============================================================================
--dates should be in the format YYYY-MM-DD HH:mm:SS
local function getSlices(startDateStr, endDateStr)
	local startDate = RPT.Date():timeFromString(startDateStr)
	local endDate   = RPT.Date():timeFromString(endDateStr)
	if startDate > endDate then
		return nil, "invalid period!"
	end

	local currentDate = startDate
	local sliceSeconds = 30 * 60
	local slices = {}
	while currentDate <= endDate do
		table.insert(slices, makeHalfHourSlice(currentDate))
		currentDate = currentDate + sliceSeconds
	end
	return slices
end

--=============================================================================
--[[
	Esta funcion, retorna los Timezones a utilizar en las fechas Desde y Hasta.
	Si debe convertir a UTC lo hace sobre la fecha "Hasta".
	Esto porque para el caso de la ejecución de reportes Vía Schedulers:
		Cuando en el Scheduler en "Rango" y "Frecuencia" ha configurado:
			"Rango"     -> "Desde la última ejecución".
			"Frecuencia"-> "Cada N Horas".
	Ya que en este caso las fechas llegan en UTC y para la fecha Start (NO HAY que convertir a UTC)
	Pero la fecha Fin, sí para que la misma sea diferente a las "...23:59:59"

	Esto NO es así para las ejecuciones manuales por los Usuarios desde la App Reports,
	ya que acá siempre llegan en el TimeZone del Usuario.
--]]
local function GetTimezonesFromAndTo (userTimezone, hasToConvertToUTC, utils)
	local clientSideTimezone = utils.DeepCopy(userTimezone)

	local fromTimezone = userTimezone
	local endTimezone = utils.DeepCopy(fromTimezone)

	if hasToConvertToUTC then
		fromTimezone = m_utcTimezone
		endTimezone = clientSideTimezone
	end
	--LogDebug("[REPORTER.SLICES_HELPER:GetTimezonesFromAndTo] fromTimezone %s, endTimezone %s",
	--	json.encode(fromTimezone),
	--	json.encode(endTimezone)
	--)
	return fromTimezone, endTimezone
end

--=============================================================================
--[[
	Esta funcion valida si la fecha recibida por parámetro, se encuentra dentro de horario de verano,
	en el timezone recibido por parámetro.
	Busca dentro de la tabla historica "HistoricalDaylight".
--]]
function IsEnabledDateBetweenDaylightSavingTime (dateTime, timezone)
	assert(dateTime)
	if type(timezone) ~= "table" or #(timezone.HistoricalDaylight or {}) == 0 then
		--LogDebug("[REPORTER.SLICES_HELPER:IsEnabledDateBetweenDaylightSavingTime] ---> 1 NO Existe Informacion de horario de verano en el timezone.")
		return false
	end
	if type(dateTime) == "string" then
		dateTime = RPT.Date():timeFromString(dateTime)
	end
	--dateTime = ormDate(dateTime):time()
	for _, info in ipairs(timezone.HistoricalDaylight) do
		--LogDebug("[REPORTER.SLICES_HELPER:IsEnabledDateBetweenDaylightSavingTime] ---> 2 dateTime: %s, Timezone: %s, info %s", dateTime, timezone.id, json.encode(info))
		if dateTime >= info.StartDayLight and dateTime <= info.EndDayLight then
			--LogDebug("[REPORTER.SLICES_HELPER:IsEnabledDateBetweenDaylightSavingTime] ---> 3 %s esta la fecha en horario de Verano. Fecha %s, Timezone: %s", info.isdst and "SIIIIIIIIII" or "NO", stringDate, timezone.id)
			return info.isdst
		end
	end
	--LogDebug("[REPORTER.SLICES_HELPER:IsEnabledDateBetweenDaylightSavingTime] ---> 4 NO esta la fecha en horario de Verano. Fecha %s, Timezone: %s", stringDate, timezone.id)
	return false
end

--=============================================================================
--[[
	Esta función convierte a UTC la fecha que se encuentra en el timezone del Usuario.
	La misma invoca a la función "IsEnabledDateBetweenDaylightSavingTime" para determinar si debe o no ajustar la fecha resultante al Horario de Verano.
--]]
local function ConvertTimezoneDateToUTCDate (timeZoneDate, timezone, formatDate, utils)
	local utcDate
	local hasChangedToDst = false
	local objectDate

	if type(timezone) == "table" then
		local myTimeZone = timezone -- utils and utils.DeepCopy(timezone) or timezone
		if IsEnabledDateBetweenDaylightSavingTime(timeZoneDate, myTimeZone) then
			--LogDebug("[REPORTER.SLICES_HELPER:ConvertTimezoneDateToUTCDate] --> Change Timezone to Daylight Saving Time. Current date: '%s', Timezone: '%s'.", timeZoneDate, timezone.id)
			myTimeZone.dst = true
			hasChangedToDst = true
		else
			myTimeZone.dst = false
		end
		--TODO: remove it
		objectDate = ormDate:newFromDifferentTimezone(timeZoneDate, myTimeZone)
		if formatDate then
			utcDate = objectDate:toString(formatDate)
		else
			utcDate = tostring(objectDate)
		end
	else
		utcDate = timeZoneDate
	end

	-- LogDebug("[REPORTER.SLICES_HELPER:ConvertTimezoneDateToUTCDate] ---> Converted to UTC Date: timeZoneDate %s, utcDate: %s, timezone %s, formatDate: %s. %s",
	-- 	timeZoneDate,
	-- 	utcDate,
	-- 	(timezone or {}).id,
	-- 	formatDate,
	-- 	hasChangedToDst and "--> IT HAS BEEN CHANGED TO DAYLIGHT SAVING TIME" or ""
	-- )

	return {stringDate = utcDate, objectDate = objectDate, isDst = hasChangedToDst}
end

--=============================================================================
--[[
	Esta función convierte al Timezone del Usuario la fecha que se encuentra en UTC.
	La misma invoca a la función "IsEnabledDateBetweenDaylightSavingTime" para determinar si debe o no ajustar la fecha resultante al Horario de Verano.
--]]
local function ConvertUTCDateToTimezoneDate (utcDate, timezone, formatDate, utils, withoutObject)
	assert(type(timezone) == "table")

	local timeZoneDate
	local hasChangedToDst = false
	local objectDate
	if type(utcDate) == "string" then
		utcDate = RPT.Date():timeFromString(utcDate)
		--LogDebug("[REPORTER.SLICES_HELPER:ConvertUTCDateToTimezoneDate] --> utcDate: %s", utcDate)
	end

	local myTimeZone = timezone --utils and utils.DeepCopy(timezone) or timezone
	if IsEnabledDateBetweenDaylightSavingTime(utcDate, myTimeZone) then
		--LogDebug("[REPORTER.SLICES_HELPER:ConvertUTCDateToTimezoneDate] Change Timezone to Daylight Saving Time. Current date: '%s', Timezone: '%s'.", utcDate, timezone.id)
		myTimeZone.dst = true
		hasChangedToDst = true
	else
		myTimeZone.dst = false
	end
	local timeZoneDate,  timeZoneDateDouble = RPT.Date():toDifferentTimezoneString(utcDate, myTimeZone, formatDate)
	--local timeZoneDate = ormDate(utcDate):toDifferentTimezoneString(myTimeZone, formatDate)

	-- LogDebug("[REPORTER.SLICES_HELPER:ConvertUTCDateToTimezoneDate] ---> Converted to Timezone Date: utcDate %s, timeZoneDate: %s, timezone %s, formatDate: %s. %s",
	-- 	utcDate,
	-- 	timeZoneDate,
	-- 	(timezone or {}).id,
	-- 	formatDate,
	-- 	hasChangedToDst and "--> IT HAS BEEN CHANGED TO DAYLIGHT SAVING TIME" or ""
	-- )

	--TODO: summary_query AddMiniRow is using objectDate, remove it or migrate it into RPT Date
	local result = {stringDate = timeZoneDate, isDst = hasChangedToDst}
	if not withoutObject then
		result.objectDate = ormDate(timeZoneDate)
	end
	return result
end

return {
	getSlices                    = getSlices,
	formatTime                   = formatTime,
	GetTimezonesFromAndTo        = GetTimezonesFromAndTo,
	ConvertTimezoneDateToUTCDate = ConvertTimezoneDateToUTCDate,
	ConvertUTCDateToTimezoneDate = ConvertUTCDateToTimezoneDate
}
