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


--[[
	Terrain control. Changes the ground

	Terrain.Create( sName )		Creates a new terrain type and stores it
	Terrain.Get( sName )		Returns the terrain.
	Terrain.Set( sName )		Sets the terrain. (This should only be done serverside)
	Terrain.GetCurrent()		Returns the terrain obj or nil.
	StormFox2.Terrain.Reset()	Resets the terrain to default.
	StormFox2.Terrain.HasMaterialChanged( iMaterial )	Returns true if the terrain has changed the material.

	Terrain Meta:
		:LockUntil( fFunc )										Makes the terrian
		:MakeFootprints( sndList, sndName )						Makes footprints. Allows to overwrite footstep sounds.
		:AddTextureSwap( material, basetexture, basetextire2 )	Changes a materials textures.
		:RenderWindow( width, height )							A function that renders a window-texure. (Weather will trump this)
		:RenderWindowRefract( width, height )					A function that renders a window-texure. (Weather will trump this)
		:RenderWindow64x64( width, height )						A function that renders a window-texure. (Weather will trump this)
		:RenderWindowRefract64x64( width, height )				A function that renders a window-texure. (Weather will trump this)
		:Apply													Applies the terrain (This won't reset old terrain)

	Hooks:
		StormFox2.terrain.footstep 	Entity 	foot[0 = left,1 = right] 	sTexture 	bTerrainTexture
]]

---@class SF2Terrain
local meta = {}
meta.__index = meta
meta.__tostring = function(self) return "SF_TerrainType[" .. (self.Name or "Unknwon") .. "]" end
meta.__eq = function(self, other)
	if type(other) ~= "table" then return false end
	if not other.Name then return false end
	return other.Name == self.Name
end
debug.getregistry()["SFTerrain"] = meta
local terrains = {}
StormFox2.Terrain = {}

--- Creates a new terrain type, stores and returns it.
---@param sName string
---@return SF2Terrain
---@shared
function StormFox2.Terrain.Create( sName )
	local t = {}
	t.Name = sName
	setmetatable(t, meta)
	terrains[sName] = t
	t.swap = {}
	return t
end

local CURRENT_TERRAIN
---Returns the current applies terrain.
---@return SF2Terrain|nil
---@shared
function StormFox2.Terrain.GetCurrent()
	return CURRENT_TERRAIN
end

---Returns a terrain by name.
---@param sName string
---@return SF2Terrain|nil
---@shared
function StormFox2.Terrain.Get( sName )
	if not sName then return end
	return terrains[sName]
end

-- Makes the terrain stay until this function returns true or another terrain overwrites.
---@param fFunc function
---@shared
function meta:LockUntil( fFunc )
	self.lock = fFunc
end

---Sets the ground texture. e.i; snow texture.
---@param iTexture string
---@param bOnlyGround boolean
---@shared
function meta:SetGroundTexture( iTexture, bOnlyGround )
	self.ground = iTexture
	self.only_ground = bOnlyGround
end

-- Adds a texture swap for when this terrain gets applied.
---@param mMaterial Material|string
---@param basetexture string
---@param basetextire2 string
---@shared
function meta:AddTextureSwap( mMaterial, basetexture, basetextire2 )
	if type(mMaterial) ~= "IMaterial" then
		mMaterial = Material(mMaterial)
	end
	if not basetexture and not basetextire2 then return end
	self.swap[mMaterial] = { basetexture, basetextire2 }
end

---Makes footprints and allows to overwrite default footstep sounds.
---@param bool boolean
---@param sndList? table
---@param sndName? string
---@param OnPrint? function
---@shared
function meta:MakeFootprints( bool, sndList, sndName, OnPrint )
	self.footprints = bool
	if sndList or sndName then
		self.footprintSnds = {sndList, sndName}
	end
	self.footstepFunc = OnPrint
	self.footstepLisen = bool or sndList or sndName or OnPrint
end

---A function that renders a window-texure. (Weather will trump this)
---@param fFunc function
---@shared
function meta:RenderWindow( fFunc )
	self.windRender = fFunc
end

---A function that renders a window-texure. (Weather will trump this)
---@param fFunc function
---@shared
function meta:RenderWindowRefract( fFunc )
	self.windRenderRef = fFunc
end

---A function that renders a window-texure. (Weather will trump this)
---@param fFunc function
---@shared
function meta:RenderWindow64x64( fFunc )
	self.windRender64 = fFunc
end

---A function that renders a window-texure. (Weather will trump this)
---@param fFunc function
---@shared
function meta:RenderWindowRefract64x64( fFunc )
	self.windRenderRef64 = fFunc
end

-- Texture handler
_STORMFOX_TEXCHANGES = _STORMFOX_TEXCHANGES or {} -- List of changed materials.
_STORMFOX_TEXORIGINAL = _STORMFOX_TEXORIGINAL or {} -- This is global, just in case.
local footStepLisen = false		-- If set to true, will enable footprints.

local function StringTex(iTex)
	if not iTex then return end
	if type(iTex) == "string" then return iTex end
	return iTex:GetName()
end

local function HasChanged( self, materialTexture )
	local mat = self:GetName() or "unknown"
	local b = materialTexture == "$basetexture2" and 2 or 1
	return _STORMFOX_TEXCHANGES[mat] and _STORMFOX_TEXCHANGES[mat][b] or false
end

---Returns true if the material has changed.
---@param iMaterial Material
---@return boolean
---@shared
function StormFox2.Terrain.HasMaterialChanged( iMaterial )
	local mat = iMaterial:GetName() or iMaterial
	return _STORMFOX_TEXCHANGES[mat] and true or false
end

---Returns the original texture for said material.
---@param iMaterial Material
---@return stirng
---@shared
function StormFox2.Terrain.GetOriginalTexture( iMaterial )
	local mat = iMaterial:GetName() or iMaterial
	return _STORMFOX_TEXORIGINAL[mat] and _STORMFOX_TEXORIGINAL[mat][1]
end

-- We're going to overwrite SetTexture. As some mods might change the default texture.
local mat_meta = FindMetaTable("IMaterial")
STORMFOX_TEX_APPLY = STORMFOX_TEX_APPLY or mat_meta.SetTexture
function mat_meta:SetTexture(materialTexture, texture)
	-- Check if it is basetexutre or basetexture2 we're changing.
	if materialTexture ~= "$basetexture" and materialTexture ~= "$basetexture2" then
		return STORMFOX_TEX_APPLY( self, materialTexture, texture )
	end
	-- Overwrite the original texture list.
	local mat = self:GetName() or "unknown"
	if not _STORMFOX_TEXORIGINAL[mat] then _STORMFOX_TEXORIGINAL[mat] = {} end
	if materialTexture == "$basetexture" then
		_STORMFOX_TEXORIGINAL[mat][1] = StringTex(texture)
	else
		_STORMFOX_TEXORIGINAL[mat][2] = StringTex(texture)
	end
	-- If we havn't changed the texture, allow change.
	if not HasChanged(self, materialTexture) then
		return STORMFOX_TEX_APPLY( self, materialTexture, texture )
	end
end

-- Resets the material. Returns false if unable to reset.
local function ResetMaterial( self )
	local mat = self:GetName() or "unknown"
	if not _STORMFOX_TEXCHANGES[mat] or not _STORMFOX_TEXORIGINAL[mat] then return false end
	if _STORMFOX_TEXCHANGES[mat][1] and _STORMFOX_TEXORIGINAL[mat][1] then
		STORMFOX_TEX_APPLY( self, "$basetexture", _STORMFOX_TEXORIGINAL[mat][1] )
	end
	if _STORMFOX_TEXCHANGES[mat][2] and _STORMFOX_TEXORIGINAL[mat][2] then
		STORMFOX_TEX_APPLY( self, "$basetexture2", _STORMFOX_TEXORIGINAL[mat][2] )
	end
	_STORMFOX_TEXCHANGES[mat] = nil
	return true
end

-- Set the material
local function SetMat(self, tex1, tex2)
	if not tex1 and not tex2 then return end
	local mat = self:GetName() or "unknown"
	-- Save the default texture
	if not _STORMFOX_TEXORIGINAL[mat] then _STORMFOX_TEXORIGINAL[mat] = {} end
	if tex1 and not _STORMFOX_TEXORIGINAL[mat][1] then
		_STORMFOX_TEXORIGINAL[mat][1] = StringTex(self:GetTexture("$basetexture"))
	end
	if tex2 and not _STORMFOX_TEXORIGINAL[mat][2] then
		_STORMFOX_TEXORIGINAL[mat][2] = StringTex(self:GetTexture("$basetexture2"))
	end
	-- Set texture
	if tex1 then
		if CLIENT then
			STORMFOX_TEX_APPLY( self, "$basetexture", tex1 )
		end
		if not _STORMFOX_TEXCHANGES[ mat ] then _STORMFOX_TEXCHANGES[ mat ] = {} end
		_STORMFOX_TEXCHANGES[ mat ][ 1 ] = true
	end
	if tex2 then
		if CLIENT then
			STORMFOX_TEX_APPLY( self, "$basetexture2", tex2 )
		end
		if not _STORMFOX_TEXCHANGES[ mat ] then _STORMFOX_TEXCHANGES[ mat ] = {} end
		_STORMFOX_TEXCHANGES[ mat ][ 2 ] = true
	end
end

-- Resets the terrain to default. Setting bNoUpdate to true on the server, will not notify the clients or relays.
---@param bNoUpdate boolean
---@shared
function StormFox2.Terrain.Reset( bNoUpdate )
	--print("Reset")
	if SERVER and not bNoUpdate then
		StormFox2.Map.CallLogicRelay("terrain_clear")
	end
	CURRENT_TERRAIN = nil
	if SERVER and not bNoUpdate then
		net.Start(StormFox2.Net.Terrain)
			net.WriteString( "" )
		net.Broadcast()
	end
	if next(_STORMFOX_TEXCHANGES) == nil then return end
	for tex,_ in pairs( _STORMFOX_TEXCHANGES ) do
		local mat = Material( tex )
		if not ResetMaterial( mat ) then
			StormFox2.Warning( "Can't reset [" .. tostring( mat ) .. "]." )
		end
	end
	_STORMFOX_TEXCHANGES = {}
end

--- Sets the terrain. (This should only be done serverside)
---@param sName string
---@return boolean
---@shared
function StormFox2.Terrain.Set( sName )
	-- Apply terrain.
	local t = StormFox2.Terrain.Get( sName )
	if not t then
		StormFox2.Terrain.Reset()
		return false
	end
	StormFox2.Terrain.Reset( true )
	t:Apply()
	if SERVER then
		StormFox2.Map.CallLogicRelay( "terrain_" .. string.lower(sName) )
	end
	return true
end

--- Reapplies the current terrain
---@shared
function StormFox2.Terrain.Update()
	local terrain = StormFox2.Terrain.GetCurrent()
	if not terrain then return end
	StormFox2.Terrain.Reset( true )
	terrain:Apply()
end

local NO_TYPE = -1
local DIRTGRASS_TYPE = 0
local ROOF_TYPE = 1
local ROAD_TYPE = 2
local PAVEMENT_TYPE = 3

local function checkType(n, bOnlkyG)
	if not n then return false end
	if n == 0 then return true end
	if n == 1 and not bOnlkyG then return true end
	return false
end

-- Applies the terrain (This won't reset old terrain)
function meta:Apply()
	CURRENT_TERRAIN = self
	if SERVER then
		net.Start(StormFox2.Net.Terrain)
			net.WriteString( CURRENT_TERRAIN.Name )
		net.Broadcast()
	end
	-- Swap materials
	if self.swap then
		for mat,tab in pairs( self.swap ) do
			SetMat( mat, tab[1], tab[2] )
		end
	end
	-- Set ground
	if self.ground then
		for materialName,tab in pairs( StormFox2.Map.GetTextureTree() ) do
			local mat = Material( materialName )
			local a,b = checkType(tab[1], self.only_ground), checkType(tab[2], self.only_ground)
			SetMat( mat, a and self.ground,b and self.ground)
		end
	end
	footStepLisen = self.footprints or self.footprintSnds
end

-- NET
if SERVER then
	net.Receive(StormFox2.Net.Terrain, function(len, ply) -- OI, what terrain?
		net.Start(StormFox2.Net.Terrain)
			net.WriteString( CURRENT_TERRAIN and CURRENT_TERRAIN.Name or "" )
		net.Send(ply)
	end)
else
	net.Receive(StormFox2.Net.Terrain, function(len)
		local sName = net.ReadString()
		local b = StormFox2.Terrain.Set( sName )
	--"Terrain Recived: ", sName, b)
		
	end)
	-- Ask the server
	hook.Add("StormFox2.InitPostEntity", "StormFox2.terrain.init", function()
		timer.Simple(1, function()
			net.Start(StormFox2.Net.Terrain)
				net.WriteBit(1)
			net.SendToServer()
		end)
	end)
end

-- A bit ballsy, but lets try.
hook.Add("ShutDown","StormFox2.Terrain.Clear",function()
	StormFox2.Msg("Reloading all changed materials ..")
	for k, _ in pairs( _STORMFOX_TEXORIGINAL ) do
		RunConsoleCommand("mat_reloadmaterial", k)
	end
end)