--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| 	Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]


--[[
	Unlike SF1, this doesn't support networking.
	
	If timespeed changes, and some lerpvalues like temperature has been applied, we need to keep it syncronised.
	That is why we now use Time value instead of CurTime
	
	Data.Set( sKey, zVar, nDelta )	Sets the data. Supports lerping if given delta.
	Data.Get( sKey, zDefault )		Returns the data. Returns zDefault if nil.
	Data.GetFinal( zKey, zDefault)	Returns the data without calculating the lerp.
	Data.IsLerping( sKey )			Returns true if the data is currently lerping.

	Hooks:
		- StormFox2.data.change		sKey	zVar		Called when data changed or started lerping.
		- StormFox2.data.lerpstart	sKey	zVar		Called when data started lerping
		- StormFox2.data.lerpend		sKey	zVar		Called when data stopped lerping (This will only be called if we check for the variable)
]]
StormFox2.Data = {}

StormFox_DATA = {}		-- Var
StormFox_AIMDATA = {} 	-- Var, start, end

--[[TODO: There are still problems with nil varables.
]]

---Returns the final data, ignoring lerp. Will return zDefault as fallback.
---@param sKey string
---@param zDefault any
---@return any
---@shared
function StormFox2.Data.GetFinal( sKey, zDefault )
	if StormFox_AIMDATA[sKey] then
		if StormFox_AIMDATA[sKey][1] ~= nil then
			return StormFox_AIMDATA[sKey][1]
		else
			return zDefault
		end
	end
	if StormFox_DATA[sKey] ~= nil then
		return StormFox_DATA[sKey]
	end
	return zDefault
end

local lerpCache = {}
local function calcFraction(start_cur, end_cur)
	local n = CurTime()
	if n >= end_cur then return 1 end
	local d = end_cur - start_cur
	return (n - start_cur) / d
end
do
	local function isColor(t)
		if type(t) ~= "table" then return false end
		return t.r and t.g and t.b and true or false
	end
	local function LerpVar(fraction, from, to)
		local t = type(from)
		if t ~= type(to) then
			--StormFox2.Warning("Can't lerp " .. type(from) .. " to " .. type(to) .. "!")
			return to
		end
		if t == "number" then
			return Lerp(fraction, from, to)
		elseif t == "string" then
			return fraction > .5 and to or from
		elseif isColor(from) then
			local r = Lerp(fraction, from.r, to.r)
			local g = Lerp(fraction, from.g, to.g)
			local b = Lerp(fraction, from.b, to.b)
			local a = Lerp(fraction, from.a, to.a)
			return Color(r,g,b,a)
		elseif t == "vector" then
			return LerpVector(fraction, from, to)
		elseif t == "angle" then
			return LerpAngle(fraction, from, to)
		elseif t == "boolean" then
			if fraction > .5 then
				return to
			else
				return from
			end
		else
			--print("UNKNOWN", t,"TO",to)
		end
	end

	---Returns data. Will return zDefault as fallback.
	---@param sKey string
	---@param zDefault any
	---@return any
	---@shared
	function StormFox2.Data.Get( sKey, zDefault )
		-- Check if lerping
		local var1 = StormFox_DATA[sKey]
		if not StormFox_AIMDATA[sKey] then
			if var1 ~= nil then
				return var1
			else
				return zDefault
			end
		end
		-- Check cache and return
		if lerpCache[sKey] ~= nil then return lerpCache[sKey] end
		-- Calc
		local fraction = calcFraction(StormFox_AIMDATA[sKey][2],StormFox_AIMDATA[sKey][3])
		local var2 = StormFox_AIMDATA[sKey][1]
		if fraction <= 0 then
			return var1
		elseif fraction < 1 then
			lerpCache[sKey] = LerpVar( fraction, var1, var2 )
			if not lerpCache[sKey] then
				--print("DATA",sKey, zDefault)
				--print(debug.traceback())
			end
			return lerpCache[sKey] or zDefault
		else -- Fraction end
			StormFox_DATA[sKey] = var2
			StormFox_AIMDATA[sKey] = nil
			hook.Run("StormFox2.data.lerpend",sKey,var2)
			return var2 or zDefault
		end
	end
	local n = 0
	-- Reset cache after 4 frames
	hook.Add("Think", "StormFox2.resetdatalerp", function()
		n = n + 1
		if n < 4 then return end
		n = 0
		lerpCache = {}
	end)
end

---Sets data. Will lerp if given delta time. Use StormFox2.Network.Set if you want want to network it.
---@param sKey string
---@param zVar any
---@param nDelta any
---@shared
function StormFox2.Data.Set( sKey, zVar, nDelta )
	-- Check if vars are the same
	if StormFox_DATA[sKey] ~= nil and not StormFox_AIMDATA[sKey] then
		if StormFox_DATA[sKey] == zVar then return end
	end
	-- If time is paused, there shouldn't be any lerping
	if StormFox2.Time and StormFox2.Time.IsPaused and StormFox2.Time.IsPaused() then
		nDelta = 0
	end
	-- Delete old cache
	lerpCache[sKey] = nil
	-- Set to nil
	if not zVar and zVar == nil then
		StormFox_DATA[sKey] = nil
		StormFox_AIMDATA[sKey] = nil
		return
	end
	-- If delta is 0 or below. (Or no prev data). Set it.
	if not nDelta or nDelta <= 0 or StormFox_DATA[sKey] == nil or StormFox2.Time.GetSpeed_RAW() <= 0 then
		StormFox_AIMDATA[sKey] = nil
		StormFox_DATA[sKey] = zVar
		hook.Run("StormFox2.data.change",sKey,zVar)
		return
	end
	-- Get the current lerping value and set that as a start
	if StormFox_AIMDATA[sKey] then
		StormFox_DATA[sKey] = StormFox2.Data.Get( sKey )
	end
	StormFox_AIMDATA[sKey] = {zVar, CurTime(), CurTime() + nDelta, StormFox2.Time.GetSpeed_RAW()}
	hook.Run("StormFox2.data.lerpstart",sKey,zVar, nDelta)
	hook.Run("StormFox2.data.change", sKey, zVar, nDelta)
end

---Returns true if the value is currently lerping.
---@param sKey string
---@return boolean
---@shared
function StormFox2.Data.IsLerping( sKey )
	if not StormFox_AIMDATA[sKey] then return false end
	-- Check and see if we're done lerping
	local fraction = calcFraction(StormFox_AIMDATA[sKey][2],StormFox_AIMDATA[sKey][3])
	if fraction < 1 then
		return true
	end
	-- We're done lerping.
	StormFox_DATA[sKey] = StormFox2.Data.GetFinal( sKey )
	StormFox_AIMDATA[sKey] = nil
	lerpCache[sKey] = nil
	hook.Run("StormFox2.data.lerpend",sKey,zVar)
	return true
end

---Returns a CurTime for when the data is done lerping.
---@param sKey string
---@return number
---@shared
function StormFox2.Data.GetLerpEnd( sKey )
	if not StormFox_AIMDATA[sKey] then return 0 end
	return StormFox_AIMDATA[sKey][3]
end

-- If time changes, we need to update the lerp values
hook.Add("StormFox2.Time.Changed", "StormFox2.datatimefix", function()
	local nT = StormFox2.Time.GetSpeed_RAW()
	local c = CurTime()
	if nT <= 0.001 then return end
	for k,v in pairs( StormFox_AIMDATA ) do
		if not v[4] or v[4] == nT then continue end
		local now_value = StormFox2.Data.Get( k )
		if not StormFox_AIMDATA[k] then continue end -- After checking the value, it is now gone.
		if now_value then
			StormFox_DATA[k] = now_value
		end
		local delta_timeamount = (v[3] - c) -- Time left
		local delta_time = v[4] / nT				-- Time multiplication
		StormFox_AIMDATA[k][2] = c
		StormFox_AIMDATA[k][3] = c + delta_timeamount * delta_time
		StormFox_AIMDATA[k][4] = nT
	end
end)