--[[
| 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/
--]]

--[[-------------------------------------------------------------------------
Use the map-data to set a minimum and maximum fogdistance
---------------------------------------------------------------------------]]
StormFox2.Setting.AddSV("enable_svfog",true,nil, "Effects")
if CLIENT then StormFox2.Setting.AddCL("enable_fog",true, "sf_enable_fog") end
StormFox2.Setting.AddSV("enable_fogz",false,nil, "Effects")
StormFox2.Setting.AddSV("overwrite_fogdistance",-1,nil, "Effects", -1, 800000)
StormFox2.Setting.SetType("overwrite_fogdistance","special_float")
StormFox2.Setting.AddSV("allow_fog_change",engine.ActiveGamemode() == "sandbox",nil, "Effects")

StormFox2.Fog = {}
-- Local functions
	local function fogEnabledCheck()
		if not StormFox2.Setting.SFEnabled() then return false end
		if not StormFox2.Setting.GetCache("enable_svfog", true) then return false end
		if not StormFox2.Setting.GetCache("allow_fog_change", false) then return true end
		return StormFox2.Setting.GetCache("enable_fog", true)
	end
	local _fS, _fE, _fD = 0,400000,1
	local function fogStart( f )
		_fS = f
	end
	local function fogEnd( f )
		_fE = f
	end
	local function fogDensity( f )
		_fD = f
	end
	local function getFogFill()
		if _fS >= 0 then return 0 end
		return -_fS / (_fE - _fS) * _fD * 0.1
	end
	-- Makes it so fog isn't linear
	local e = 2.71828
	local function fogCalc(b, a, p)
		if a == b then return a end
		p = e^(-8.40871*p)
		local d = b - a
		return a + d * p
	end

---Returns the start of the fog.
---@return number
---@shared
function StormFox2.Fog.GetStart()
	return math.max(0, _fS)
end

---Returns the end of fog.
---@return number
---@shared
function StormFox2.Fog.GetEnd()
	return _fE
end
-- Locate / Calculate the default fog-data
	local map_distance, map_farZ = -1, -1
	local tab = StormFox2.Map.FindClass("env_fog_controller")
	if #tab < 1 then
		map_distance = 400000
	else
		-- Set a minimum
		map_distance = 6000
		for _, data in ipairs(tab) do
			map_farZ = math.max(map_farZ, data.farz)
			-- Calculate fog-brightness. We can use this to scale the map-distance up to match the fog.
				local col_brightness = 1
				local density = (tonumber( data.fogmaxdensity or "" ) or 1)
				if data.fogcolor then
					local fcol = string.Explode(" ", data.fogcolor)
					col_brightness = (0.2126 * fcol[1] + 0.7152 * fcol[2] + 0.0722 * fcol[3]) / 255
				end
				density = density * col_brightness
			map_distance = math.max(((data.fogend or 6000) / density), map_distance)
		end
		-- It is important we don't overshoot farZ
		if map_farZ > 0 then
			map_distance = math.min(map_distance, map_farZ)
		end
	end

---Returns the fog-amount. 0 - 1
---@return number
---@shared
function StormFox2.Fog.GetAmount()
	return 1 - _fE / map_distance
end

---Returns the fog-distance ( Same as StormFox2.Fog.GetEnd(), but uses the map as a fallback )
---@return number
---@shared
function StormFox2.Fog.GetDistance()
	return _fE or map_distance
end

-- Returns the default fog-distance for clear weather.
	local function getDefaultDistance()
		local ov = StormFox2.Setting.GetCache("overwrite_fogdistance",-1)
		if ov > -1 then
			return ov
		end
		return map_distance
	end
-- Returns the fog-distance.
	local function getAimDistance(bFinal)
		local cW = StormFox2.Weather.GetCurrent()
		local ov = getDefaultDistance()
		if cW.Name == "Clear" then return ov end
		local perc = bFinal and StormFox2.Weather.GetFinishPercent() or StormFox2.Weather.GetPercent()
		local a = math.min(cW:Get('fogDistance'), ov)
		if a == ov then return ov end -- This weathertype doesn't change the fog .. or is higer than default
		if not a or perc <= 0 then return ov end -- If weather percent is 0 or under. Return the "clear" distance.
		if perc >= 1 then return a end -- If weather is higer or equal to 1, return the base value.
		return fogCalc(ov, a, perc)
	end

if SERVER then
	local loaded, data, f_FogZ = true
	
	---Sets the fogZ distance. Seems buggy atm, use at own rist.
	---@param num number
	---@param nTimer number
	---@server
	function StormFox2.Fog.SetZ(num, nTimer)
		timer.Remove( "sf_fog_timer" )
		if nTimer then
			timer.Create("sf_fog_timer", nTimer, 1, function()
				StormFox2.Fog.SetZ(num)
			end)
			return
		end
		f_FogZ = num
		if not loaded then
			data = num
			return 
		end
		if not num then num = map_farZ end
		for k,v in ipairs( StormFox2.Ent.env_fog_controllers or {} ) do
			if not IsValid(v) then continue end
			v:SetKeyValue("farz", num)
		end
	end
	
	---Returns the fogz distance.
	---@return number
	---@server
	function StormFox2.Fog.GetZ()
		if not StormFox2.Setting.Get("enable_fogz", false) then return map_farZ end
		return f_FogZ or (StormFox2.Fog.GetDistance() + 100)
	end
	hook.Add("StormFox2.PostEntityScan", "StormFox2.Fog.Initz", function()
		loaded = true
		if data then
			StormFox2.Fog.SetZ(data)
			data = nil
		end
	end)
	hook.Add("StormFox2.weather.postchange", "StormFox2.Fog.Updater", function( sName ,nPercentage, nDelta )
		local old_fE = _fE or map_distance
		_fE = getAimDistance(true)
		if _fE > 3000 then
			_fS = 0
		else
			_fS = _fE - 3000
		end
		-- Check fogZ distance
		if not StormFox2.Setting.Get("enable_fogz", false) then return end
		if old_fE > _fE then 		-- The fog shriks
			StormFox2.Fog.SetZ(_fE * 2 + 100, nDelta)
		elseif old_fE < _fE then	-- The fog grows
			StormFox2.Fog.SetZ(_fE * 2 + 100)
		end
	end)
	timer.Create("StormFox2.Fog.SVUpdate", 2, 0, function()
		local cWD = StormFox2.Weather.GetCurrent().Dynamic or {}
		if cWD.fogDistance then return end
		_fE = getAimDistance(true)
	end)
	
	---Returns the fog-color.
	---@return Color
	---@server
	function StormFox2.Fog.GetColor()
		return StormFox2.Mixer.Get("fogColor", StormFox2.Mixer.Get("bottomColor",color_white) ) or color_white
	end
	return
end
----- Fog render and clientside -----
-- Fog logic and default render
	-- Returns the "distance" to outside
	local f_outside = 0
	local f_indoor = -1
	local f_lastDist = map_distance

	local function outSideVar()
		local env = StormFox2.Environment.Get()
		if env.outside then
			return f_outside
		end
		if not env.nearest_outside then
			return f_indoor
		end
		local dis = StormFox2.util.RenderPos():Distance(env.nearest_outside) / 300
		if dis > 1 then
			return f_indoor
		end
		return dis
	end
	hook.Add("Think", "StormFox2.Fog.Updater", function()
		-- Figure out the fogdistance we should have
			local f_envfar = outSideVar()
			local fog_dis = getAimDistance()
			local fog_indoor = StormFox2.Mixer.Get("fogIndoorDistance",3000)
			if f_envfar == f_indoor then -- Indoors
				fog_dis = math.max(fog_dis, fog_indoor)
			elseif f_envfar ~= f_outside then
				fog_dis = Lerp(f_envfar + 0.1, fog_dis, fog_indoor)
			end
		_fE = math.Approach(_fE, fog_dis, math.max(10, _fE) * FrameTime())
		if _fE > 3000 then
			_fS = 0
		else
			_fS = _fE - 3000
		end
	end)

	local f_Col = color_white
	local SkyFog = function(scale)
		if _fD <= 0 then return end
		if not scale then scale = 1 end
		if not fogEnabledCheck() then return end
		f_Col = StormFox2.Mixer.Get("fogColor", StormFox2.Mixer.Get("bottomColor") ) or color_white
		-- Apply fog
		local tD = StormFox2.Thunder.GetLight() / 2055
		render.FogMode( 1 )
		render.FogStart( StormFox2.Fog.GetStart() * scale )
		render.FogEnd( StormFox2.Fog.GetEnd() * scale )
		render.FogMaxDensity( (_fD - tD) * 0.999 )
		render.FogColor( f_Col.r,f_Col.g,f_Col.b )
		return true
	end
	hook.Add("SetupSkyboxFog","StormFox2.Sky.Fog",SkyFog)
	hook.Add("SetupWorldFog","StormFox2.Sky.WorldFog",SkyFog)
	-- Returns the fog-color.
	function StormFox2.Fog.GetColor()
		return f_Col or color_white
	end

-- Additional Fog render
local m_fog = Material('stormfox2/effects/fog_sphere')
local l_fogz = 0
hook.Add("StormFox2.2DSkybox.FogLayer", "StormFox2.Fog.RSky", function( viewPos )
	if not fogEnabledCheck() then return end
	local v = Vector(math.cos( viewPos.y ), math.sin( viewPos.y ), 0)
	m_fog:SetVector("$color", Vector(f_Col.r,f_Col.g,f_Col.b) / 255)
	m_fog:SetFloat("$alpha", math.Clamp(5000 / _fE, 0, 1))
	render.SetMaterial( m_fog )
	local tH = math.min(StormFox2.Environment.GetZHeight(), 2100)
	if tH ~= l_fogz then
		local delta = math.abs(l_fogz, tH) / 2
		l_fogz = math.Approach( l_fogz, tH, math.max(delta, 10) * 5 * FrameTime() )
	end
	local h = 2000 + 6000 * StormFox2.Fog.GetAmount()
	render.DrawSphere( Vector(0,0,h - l_fogz * 4) , -30000, 30, 30, color_white)
end)

local mat = Material("color")
local v1 = Vector(1,1,1)
hook.Add("PostDrawOpaqueRenderables", "StormFox2.Sky.FogPDE", function()
	if _fS >= 0 or _fD <= 0 then return end
	if not fogEnabledCheck() then return end
	local a = getFogFill()
	mat:SetVector("$color",Vector(f_Col.r / 255,f_Col.g / 255,f_Col.b / 255))
	mat:SetFloat("$alpha",a)
	render.SetMaterial(mat)
	render.DrawScreenQuad()
	mat:SetVector("$color",v1)
	mat:SetFloat("$alpha",1)
end)