--[[ ----------------------------------------------------------------------------------------------
 File       : level_weathers.script
 Description: Weather manager
 Copyright  : 2011 © LA-team
 Author     : gr1ph00n & Cromm Cruac
 Editors    : gr1ph00n, Skyloader(23.02.2013,13.03.2017)
 Last edit  : 13.04.2017 [loxotron]
--]] ----------------------------------------------------------------------------------------------
---]] - fixed eternal weather transition bug
--/ -----------------------------------------------------------------
--/ VARIABLEs
--/ -----------------------------------------------------------------
local weather_mgr = nil
--/ -----------------------------------------------------------------
local sModule = script_name()
--/ -----------------------------------------------------------------
local blow_ini = ini_file("misc\\surge_manager.ltx")
local blow_parameters = la_utils.parse_ini_section_to_array(blow_ini,"la_atmosfear_default_paramaters")
local blowout_enabled = la_utils.str_to_bool(blow_parameters["opt_blowout_enabled"])
local bDynamicWeather = false

local iTimeIdle    = 10 *60*60  --/ 10 game hours
local sTimerName   = "•TM_BLOWOUT•" --/ unique blowout timer name for save
local sTimerFunc   = sModule..".start_preblow" --/ dynamic action function
STATIC_WEATHER  = 1
DYNAMIC_WEATHER = 2
local MIN_CHANGE_PERIOD = 3
local MAX_CHANGE_PERIOD = 6


local no_transition = {
	[ 4] = true,
	[ 5] = true,
	[20] = true,
	[21] = true
}
local all_weathers = {
	"af_clear",
	"af_partly",
	"af_foggy",
	"af_cloudy",
	"af_rain",
	"af_thunder",
	"alpha_r2",
}
local inverse_all_weathers = {}

local weather_periods = {
	"good",
	"neutral",
	"bad"
}
local inverse_weather_periods = {}

--/ -----------------------------------------------------------------
local log = function(fmt,...) _G.log(sModule..":"..tostring(fmt),...) end
--/ -----------------------------------------------------------------
--/ Initialize Module
--/ -----------------------------------------------------------------
function init()
	for idx,v in ipairs(all_weathers) do
		inverse_all_weathers[v] = idx
	end
	for idx,v in ipairs(weather_periods) do
		inverse_weather_periods[v] = idx
	end
	event("presets"):register(PreSets)
	log("initing level_weathers","")
end
--/ -----------------------------------------------------------------
--/ Process
--/ -----------------------------------------------------------------
function PreSets(e)
	log("presets level_weathers","")
	bDynamicWeather = device():is_r4_active() or device():is_r3_active() or device():is_r2_active()
	get_weather_manager()
	event("update_1sec"):register(update)
	event("update"):register(check)
	event("actor_save"):register(save)
	if HasVar("sav_"..sModule) then
		local tbl = GetVar("sav_"..sModule,{})
		weather_mgr:Load(tbl)
		DelVar("sav_"..sModule) --/ clear
		log("has sav","")
	else
		log("not has sav","")
		weather_mgr:Activate()
		weather_mgr:StartTimer()
		if bDynamicWeather then
			weather_mgr:SelectWeather()
		end
	end
end

function get_weather_manager()
	if not weather_mgr then
		if device():is_r4_active() then
			log("r4 active")
		elseif device():is_r3_active() then
			log("r3 active")
		elseif device():is_r2_active() then
			log("r2 active")
		else
			log("r1 active")
		end
		if bDynamicWeather then
			weather_mgr = DynamicWeatherManager()
		else
			weather_mgr = StaticWeatherManager()
		end
	end
	--log("getting weather manager","")
	return weather_mgr
end

function update(e)
	weather_mgr:Update()
end

function check(e)
	weather_mgr:Check()
end

function save(e)
	weather_mgr:Save()
end

--/ -------------------------
--/ CLASS CONSTRUCTOR
--/ -----------------------------------------------------------------------------------------------
class "WeatherManager"
--/ -----------------------------------------------------------------------------------------------
function WeatherManager:__init(w_type)
	self.set           = false
	self.default_indor = "indoor"
	self.weather_type  = w_type
	self.blowout_types   = {"wave", "psi_storm"}
	self.last_blow 		 = game.get_game_time()
	self.active = true
	log("WeatherManager:__init: dynamic = %s : %s", tostring(bDynamicWeather), Get_StrTime()) --/#~#
end

function WeatherManager:GetType()
	return self.weather_type
end

function WeatherManager:IsActive()
	return self.active == true
end

function WeatherManager:Activate()
	self.active = true
	self.blowout_active = false
	self.is_afterblow = false
	self.is_preblow = false
	self.current_period = weather_periods[math.random(#weather_periods)]
	self.last_period = weather_periods[math.random(#weather_periods)]
	if bDynamicWeather then
		self.current_weather = self:GetWeatherFromPeriod()
	else
		self.current_weather = all_weathers[math.random(#all_weathers)]
	end
	self.next_weather    = nil
	self.transition      = self.current_weather
	self.weather_update  = game.get_game_time()
	Set_RestSeconds(60*60 - level.get_time_minutes()*60, self.weather_update)
	self.period_update  = game.get_game_time()
	Set_RestSeconds(math.random(MIN_CHANGE_PERIOD, MAX_CHANGE_PERIOD)*60*60 - level.get_time_minutes()*60, self.period_update)
	log("activating","")
end

function WeatherManager:Active(bStatus)
	self.active = bStatus
	log("WeatherManager:Activate=(%s): %s", bStatus, Get_StrTime()) --/#~#
end

function WeatherManager:IsBlowActive()
	return self.blowout_active == true
end

function WeatherManager:IsBlowoutActive()
	return self.blowout_active
end

function WeatherManager:GetLastBlow()
	if self.last_blow and self.last_blow ~= nil then
		local g_time = game.get_game_time()
		local storm_start = g_time:diffSec(self.last_blow)
		if storm_start <= (iTimeIdle/1.2) then  -- check last blowout time tolerance
			return false
		end
	end
	return true
end

function WeatherManager:Load(...)
	--/ sturb
end

function WeatherManager:Save(...)
	--/ sturb
end

--/ -------------------------
--/ CLASS CONSTRUCTOR
--/ -----------------------------------------------------------------------------------------------
class "StaticWeatherManager" (WeatherManager)
--/ -----------------------------------------------------------------------------------------------
function StaticWeatherManager:__init() super(STATIC_WEATHER)
	self.default 		 = "default"
	self.set 			 = false
end

function StaticWeatherManager:StartPreblow()
	if self:GetLastBlow() == true and not self.blowout_active and can_start_blowout() then
		self.active = false
		local type = self.blowout_types[math.random(#self.blowout_types)]
		if type == "psi_storm" then
			--log("type = psi storm")
			psi_storm_manager.start_psi_storm()
		elseif type == "wave" then
			--log("type = wave")
			surge_manager.start_blowout()
		end
		self:ResetBlowTimer() --/ full restart
		self.blowout_active = true
	else
		self.blowout_active = false
		self.active = true
		self:ResetBlowTimer()
	end
end

function StaticWeatherManager:ResetBlowTimer()
	self.blowout_timer = get_timer(sTimerName)
	if self.blowout_timer then
		self.blowout_timer:restart(10*60*60,sTimerFunc)
	else
		self.blowout_timer = start_game_timer(10*60*60,sTimerFunc,{},sTimerName)
	end
end

function StaticWeatherManager:StartTimer()
	self.blowout_timer = start_game_timer(10*60*60,sTimerFunc,{},sTimerName)
end

function StaticWeatherManager:BlowEnd()
	self.active = true
	self.blowout_active = false
	self.last_blow = game.get_game_time()
end

function StaticWeatherManager:Update()
	if self:IsActive() then
		--log("level weathers static update active = true")
		if not self.set then
			self.set = true
			level.set_weather(if_then_else(IsUnderground(db.sMapNow), self.default_indor, self.default), true)
			--log("Update:set weather=[%s] for level=[%s]", level.get_weather(), level.name()) --/#~#
		end
	else
		--log("level weathers static update active = false")
		--log("time factor = [%s]",tostring(level.get_time_factor()))
		if not self.set then
			self.set = true
			level.set_weather(if_then_else(sr_weather.is_actor_inside(), sr_weather.sr_weather_type(), self.default), true)
		end
	end
end

function StaticWeatherManager:Check()
	--stub
end

function StaticWeatherManager:Load(tbl)
	if not (tbl.t and tbl.t == self.weather_type) then 
		self.active = tbl.a
		if self.active == true then
			self.set = true
			level.set_weather(if_then_else(IsUnderground(db.sMapNow), self.default_indor, self.default), true)
		else
			self.set = true
			level.set_weather(if_then_else(sr_weather.is_actor_inside(), sr_weather.sr_weather_type(), self.default), true)
		end
		return 
	end --/>
	self.active = tbl.a
	--log("static weather load active = [%s]",tostring(self.active))
	self.blowout_active = tbl.b
	--log("static weather load blowout active = [%s]",tostring(self.blowout_active))
	self.last_blow = Set_RestSeconds(tbl.l or 0)
end

function StaticWeatherManager:Save()
	--log("static weather save blowout active = [%s]",tostring(self.blowout_active))
	local tbl = {}
	tbl.t = weather_mgr.weather_type
	tbl.b = self.blowout_active
	--log("static weather save active = [%s]",tostring(self.active))
	tbl.a = self.active
	tbl.l = Get_RestSeconds(self.last_blow)
	SetVar("sav_"..sModule,tbl)
end


---------------------------------------------------------------------------------------------------

--/ -------------------------
--/ CLASS CONSTRUCTOR
--/ -----------------------------------------------------------------------------------------------
class "DynamicWeatherManager" (WeatherManager)
--/ -----------------------------------------------------------------------------------------------
function DynamicWeatherManager:__init() super(DYNAMIC_WEATHER)
	self.period_sequences = {}
	self:LoadConfig()
end

function DynamicWeatherManager:__finalize()
	--level.remove_pp_effector(999)
end

function DynamicWeatherManager:LoadConfig()
	self.period_sequences = parser.read_section(db.lost_alpha_cfg, "weather_manager_periods", "parse_str_num_list")	
end

function DynamicWeatherManager:StartTimer()
	self.blowout_timer = start_game_timer(iTimeIdle,sTimerFunc,{},sTimerName)
	log("starting timer","")
end

function DynamicWeatherManager:SetIdleTimer(hours)
	self.blowout_timer = get_timer(sTimerName)
	if self.blowout_timer then
		self.blowout_timer:restart(hours*60*60,sTimerFunc)
	else
		self.blowout_timer = start_game_timer(hours*60*60,sTimerFunc,{},sTimerName)
	end
end

function DynamicWeatherManager:ResetBlowTimer()
	self.blowout_timer = get_timer(sTimerName)
	if self.blowout_timer then
		self.blowout_timer:restart(10*60*60,sTimerFunc)
	else
		self.blowout_timer = start_game_timer(10*60*60,sTimerFunc,{},sTimerName)
	end
end

function DynamicWeatherManager:StartBlow()
	log("get last blow = [%s]",tostring(self:GetLastBlow()))
	if self:GetLastBlow() == true and not self.blowout_active and can_start_blowout() then
		level.set_time_factor(10)
		self.last_blow = game.get_game_time()
		self.blowout_active = true
		self.active  = false
		local type = self.blowout_types[math.random(#self.blowout_types)]
		if type == "psi_storm" then
			log("type = psi storm")
			start_real_timer(2.5,"psi_storm_manager.start_psi_storm")
		elseif type == "wave" then
			log("type = wave")
			start_real_timer(2.5,"surge_manager.start_blowout")
		end
	elseif self.is_preblow then
		self:SetIdleTimer(3)
		self.active = true
		self.blowout_active = false
		self.is_preblow = false
		self.is_afterblow = true
	end
end	

function DynamicWeatherManager:BlowEnd()
	self:ResetBlowTimer()
	self.active = true
	self.blowout_active = false
	self.is_preblow = false
	self.is_afterblow = true
end

function DynamicWeatherManager:StartPreblow()
	if self:GetLastBlow() == true and not self.blowout_active and can_start_blowout() then
		log("starting preblow")
		self.is_preblow = true
	else
		self:SetIdleTimer(1)
	end	
end

function DynamicWeatherManager:Check()
	self:HandleNoHours()
	self:HandleUnderground()
	self:HandleSrWeather()
end

function DynamicWeatherManager:Update()
	if game.get_game_time() >= self.period_update then
		self:ChangePeriod()
	end
	if game.get_game_time() >= self.weather_update then
		Set_RestSeconds(60*60 - level.get_time_minutes()*60, self.weather_update)
		if not self.active or self.is_underground or self.sr_weather or self.no_hours then
			--stub
		else		
			self:SelectWeather()
		end
	end
	if not self.blowout_active and not self.is_afterblow and self.transition and self.transition ~= level.get_weather() then
		log("transition = [%s]",tostring(self.transition),"")
		level.set_weather(self.transition, true)	
	end
end

function DynamicWeatherManager:ChangePeriod()
	if self.no_hours then
		Set_RestSeconds(60*60 - level.get_time_minutes()*60, self.period_update)
	else 
		local cp = inverse_weather_periods[self.current_period]
		local lp = inverse_weather_periods[self.last_period]
		if lp <= cp and cp < #weather_periods or cp == 1  then
			d = 1
		else
			d = -1
		end
		cp = cp + d
		self.last_period = self.current_period
		self.current_period = weather_periods[cp]
		log("CHANGING PERIOD FROM %s TO %s", self.last_period, self.current_period)
		Set_RestSeconds(math.random(MIN_CHANGE_PERIOD, MAX_CHANGE_PERIOD)*60*60 - level.get_time_minutes()*60, self.period_update)
	end
end

function DynamicWeatherManager:SelectWeather()
	local now_weather = self.current_weather		--/ for debug
	local hours = level.get_time_hours()
	if self.is_preblow then
		if self.current_weather == "af_preblowout" then
			log("current_weather == af_preblowout in preblow")
			self.active = false
			self.is_preblow = false
			self:StartBlow()
		else
			self.next_weather = "af_preblowout"
		end
	elseif self.is_afterblow then
		if self.current_weather == "af_preblowout" then
			log("current_weather == af_preblowout in afterblow")
			self.next_weather = "af_cloudy"
			self.is_afterblow = false
		end
	end
	if not self.current_weather then
		self.current_weather = all_weathers[math.random(#all_weathers)]
		self.transition = string.format("%s_to_%s_at_%02d_00", self.current_weather, self.current_weather, hours)
	else
		self.transition = string.format("%s_to_%s_at_%02d_00", self.current_weather, self.next_weather or self.current_weather, hours)
	end
	if self.next_weather then
		self.current_weather = self.next_weather
		--log("if next then current = [%s] and next = [%s]",tostring(now_weather),tostring(self.next_weather))
	end
	if not self.is_preblow and not self.is_afterblow then
		self.next_weather = self:GetWeatherFromPeriod()
	end
	--log("current transition: [%s]~>[%s] : %s ~> %s", now_weather, self.transition, Get_StrTime(), Get_StrTime(self.timer)) --/#~#
end

function DynamicWeatherManager:GetWeatherFromPeriod()
	local sum=0
	local seq = self.period_sequences[self.current_period]
	for st,prob in pairs(seq) do
		sum=sum+prob
	end
	local rnd=math.random(sum)
	local next_state
	for st,prob in pairs(seq) do
		next_state=st
		rnd=rnd-prob
		if (rnd<=0) then
			break
		end
	end
	--log("DRAWING %s FROM %s PERIOD", next_state, self.current_period)
	return next_state
end


function DynamicWeatherManager:HandleUnderground()
	if IsUnderground(db.sMapNow) then
		if not self.is_underground then
			self.is_underground = true
			self.transition = Get_Cfg_String(game_ini(), db.sMapNow, "weathers", "default")
			log("UG weather should be [%s] but is actually [%s]",self.transition,level.get_weather(),"")
		end
	elseif self.is_underground then
		if not IsUnderground(db.sMapNow) then
			self.is_underground = false
			local hours = level.get_time_hours()
			if no_transition[hours] then
				--hours = set_hours(hours)
				self.transition = self.previous_weather
			else
				self:SelectWeather()
			end
		end
	end
end

function DynamicWeatherManager:HandleSrWeather()
	if sr_weather.is_actor_inside() then
		if not self.sr_weather then
			self.sr_weather = true
			self.transition = sr_weather.sr_weather_type()
			log("SR weather should be [%s] but is actually [%s]",self.transition,level.get_weather(),"")
		end
	elseif self.sr_weather then
		if not sr_weather.is_actor_inside() then
			self.sr_weather = false
			local hours = level.get_time_hours()
			if no_transition[hours] then
				--hours = set_hours(hours)
				self.transition = self.previous_weather
			else
				self:SelectWeather()
			end
		end
	end
end

function DynamicWeatherManager:HandleNoHours()
	local hours = level.get_time_hours()
	if no_transition[hours] then
		if not self.no_hours then
			self.no_hours = true
			self.previous_weather = self.current_weather
			self.transition = self.current_weather
			log("no transition hours previous weather = [%s]",self.previous_weather,"")
		end
	elseif self.no_hours then
		self.no_hours = false
		--self:SelectWeather()
	end
end

function DynamicWeatherManager:Load(tbl)
	if not (tbl.t and tbl.t == self.weather_type) then 
		self:Activate()
		return --/>
	end
	self.active = tbl.w
	self.is_preblow = tbl.p
	self.is_afterblow = tbl.a
	self.blowout_active  = tbl.b
	self.last_blow = Set_RestSeconds(tbl.l or 0)
	self.current_period = (tbl.cp) or weather_periods[math.random(#weather_periods)]
	self.last_period 	= (tbl.lp) or self.current_period
	if tbl.c == "af_preblowout" then
		self.current_weather = tbl.c
		if tbl.n == "af_preblowout" then
			if this.can_start_blowout() then
				self.next_weather = tbl.n
				self.blowout_active = false
				--self.last_blow = nil
				self.is_afterblow = false
				self:StartBlow()
			else
				self.is_preblow = false
				self.current_weather = "af_cloudy"
			end
		end
	else
		if bDynamicWeather then
			self.current_weather = (tbl.c and all_weathers[tbl.c]) or self:GetWeatherFromPeriod()
		else
			self.current_weather = (tbl.c and all_weathers[tbl.c]) or all_weathers[math.random(#all_weathers)]
		end
		self.next_weather    = (tbl.n and all_weathers[tbl.n]) or self.current_weather
	end
	local hours = level.get_time_hours()
	if IsUnderground(db.sMapNow) then
		local trans = Get_Cfg_String(game_ini(), db.sMapNow, "weathers", nil)
		if self.transition and self.transition ~= trans then
			self.transition = trans
		end
	elseif no_transition[hours] then
		self.transition = self.current_weather
	else
		--self.transition = string.format("%s_to_%s_at_%02d_00", self.current_weather, self.next_weather or self.current_weather, hours)
		self.transition = self.current_weather
	end
	self.weather_update = Set_RestSeconds(tbl.s or 0)
	self.period_update = Set_RestSeconds(tbl.sp or 0)
	log("Load: period=[%s], curr=[%s], next=[%s]", self.current_period, self.current_weather, self.next_weather,"") --/#~#
end

function DynamicWeatherManager:Save()
	local tbl = {}
	if self.current_weather == "af_preblowout" then
		tbl.c = "af_preblowout"
		if self.is_afterblow then
			tbl.n = "af_cloudy"
		else
			tbl.n = "af_preblowout"
		end
	else		
		tbl.c = inverse_all_weathers[self.current_weather]
		tbl.n = inverse_all_weathers[self.next_weather]
	end
	tbl.w = self.active
	tbl.b = self.blowout_active
	tbl.p = self.is_preblow
	tbl.a = self.is_afterblow
	tbl.l = Get_RestSeconds(self.last_blow)
	tbl.cp = self.current_period
	tbl.lp = self.last_period
	tbl.s = Get_RestSeconds(self.weather_update)
	tbl.sp = Get_RestSeconds(self.period_update)
	tbl.t = weather_mgr.weather_type
	SetVar("sav_"..sModule,tbl)
	log("Save: period=[%s], curr=[%s], next=[%s]", self.current_period, self.current_weather, self.next_weather,"") --/#~#
end

--/ -----------------------------------------------------------------
--/ Helper Functions
--/ -----------------------------------------------------------------

function start_preblow(t)
	get_weather_manager():StartPreblow()
end

function can_start_blowout()
	--log("blowout_enabled = [%s]", blowout_enabled)
	if blowout_enabled == false then
		return false
	end
	if db.sMapNow == "la07_military" then
		if not has_info("mil_blowout_effects") then
			return false
		end
	end
	if is_level_blowout_allowed(db.sMapNow) == "false" then --/ level condition for blowout
		--log("level condition not met for blowout")
		return false --/>
	end
	log("level cond met for blowout")
	if IsUnderground(db.sMapNow) then  --/ underground level
		log("is underground")
		return false --/>
	end
	log("not underground")
	if sr_weather.is_actor_inside() then  --/ inside weather space restrictor
		log("is in weather restrictor")
		return false --/>
	end
	log("not in weather restrictor")
	if (la_actor_sleep) then
		if la_actor_sleep.is_sleep_active() or la_actor_sleep.is_tired() then --/ is sleeping or is tired
			log("actor sleeping or tired")
			return false --/>
		end
	end
	log("actor not sleeping or tired")
	return true
end

function is_level_blowout_allowed(lname)
	local cond,list = false,nil
	local sCondlist = Get_Cfg_String(blow_ini, lname, "condlist")
	if sCondlist then
		log("sCondlist = [%s]",sCondlist)
		list = xr_logic.parse_condlist(db.actor, lname, "condlist", sCondlist)
		log("list = [%s]",tostring(list))
	end
	if list then
		cond = xr_logic.pick_section_from_condlist(db.actor, db.actor, list)
	end
	log("level condition for [%s] = [%s] and type = [%s]",lname,tostring(cond),type(cond))
	return cond
end

function reset_blowout_timer()
	get_weather_manager():ResetBlowTimer()
end

function get_blowout_mgr()
	return surge_manager.get_surge_manager()
end

function get_psi_storm_mgr()
	return psi_storm_manager.get_psi_storm_manager()
end

function is_blowout_active()
	return get_weather_manager():IsBlowoutActive()
end

function set_hours(time)
	if time == 4 or time == 5 then
		time = 6
	elseif time == 20 or time == 21 then
		time = 22
	end
	return time
end

--/SkyLoader: calls from engine (if WEATHER_RELOAD define enabled) when NumPad5 button pressed
function restart_weather_manager()
	get_weather_manager():Save()
	weather_mgr = nil
	weather_mgr = get_weather_manager()
	local tbl = GetVar("sav_"..sModule,{})
	get_weather_manager():Load(tbl)
	DelVar("sav_"..sModule) --/ clear
end
--/ -----------------------------------------------------------------------------------------------