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

local INVALID_VERTS = 0
local WATER_VERTS = 1

StormFox2.Setting.AddSV("enable_ice",not game.IsDedicated())
StormFox2.Setting.AddSV("enable_wateroverlay",true, nil, "Effects")

--[[-------------------------------------------------------------------------
Localize
---------------------------------------------------------------------------]]
local round = math.Round
local util_TraceLine = util.TraceLine
local table_sort = table.sort
local LocalPlayer = LocalPlayer
--[[-------------------------------------------------------------------------
Make a few SurfaceInfo functions.
---------------------------------------------------------------------------]]
	local meta = FindMetaTable("SurfaceInfo")
	-- Support caching stuff
	local surf_caching = {}
	meta.__index = function(a,b)
		return meta[b] or surf_caching[a] and surf_caching[a][b]
	end
	meta.__newindex = function(a,b,c)
		if not surf_caching[a] then surf_caching[a] = {} end
		surf_caching[a][b] = c
	end
	hook.Add("EntityRemoved", "ClearSurfaceInfo", function(ent)
		for _,surf in ipairs(ent:GetBrushSurfaces() or {}) do
			surf_caching[surf] = nil
		end
	end)
	function meta:IsValid( )
		if self.b_invalid ~= nil then return self.b_invalid end
		local b = #(self:GetVertices()) > 0
		self.b_invalid = b
		return self.b_invalid
	end
	function meta:GetVerticesNoParallel( )
		if not self:IsValid() then return {} end
		if self.v_vertNP then return table.Copy(self.v_vertNP) end
		self.v_vertNP = {}
		local verts = self:GetVertices()
		for i,cv in ipairs(verts) do
			local pv,nv = verts[i - 1] or verts[#verts], verts[i + 1] or verts[1]
			local cP = ( cv - pv ):Cross( nv - pv )
			if cP.x == 0 and cP.y == 0 and cP.z == 0 then continue end -- parallel vector.
			table.insert(self.v_vertNP, cv)
		end
		return table.Copy(self.v_vertNP)
	end
	function meta:GetCenter( )
		if not self:IsValid() then return end
		if self.v_cent then return self.v_cent end
		local verts = self:GetVertices()
		if #verts < 2 then
			self.v_cent = verts[1]
			return self.v_cent
		end
		local vmax,vmin = verts[1],verts[1]
		for i = 2,#verts do
			vmax[1] = math.max(vmax[1],verts[i][1])
			vmax[2] = math.max(vmax[2],verts[i][2])
			vmax[3] = math.max(vmax[3],verts[i][3])
			vmin[1] = math.min(vmin[1],verts[i][1])
			vmin[2] = math.min(vmin[2],verts[i][2])
			vmin[3] = math.min(vmin[3],verts[i][3])
		end
		self.v_cent = vmin + (vmax - vmin) / 2
		return self.v_cent
	end
	function meta:GetNormal( )
		if not self:IsValid() then return end
		if self.v_norm then return self.v_norm end
		local p = self:GetVertices()
		if #p < 3 then return end -- Invalid brush. (Yes this happens)
		local c = p[1]
		local s = Vector(0,0,0)
		for i = 2,#p do
			s = s + ( p[i] - c ):Cross( (p[i + 1] or p[1]) - c )
			if s.x ~= 0 and s.y ~= 0 and s.z ~= 0 then -- Check if this isn't a parallel vector.
				break -- Got a valid norm
			end
		end
		self.v_norm = s:GetNormalized()
		return self.v_norm
	end
	function meta:GetAngles( )
		if not self:IsValid() then return end
		if self.a_ang then return self.a_ang end
		self.a_ang = self:GetNormal():Angle()
		return self.a_ang
	end
	function meta:GetPerimeter( )
		if not self:IsValid() then return end
		if self.n_peri then return self.n_peri end
		local p = self:GetVertices()
		local n = 0
		for i = 1,#p do
			n = n + p[i]:Distance(p[i + 1] or p[1])
		end
		self.n_peri = n
		return self.n_peri
	end
	function meta:GetArea( )
		if not self:IsValid() then return end
		if self.n_area then return self.n_area end
		local p = self:GetVertices()
		local n = #p
		if n < 3 then -- Invalid shape
			self.n_area = 0
			return 0
		--elseif n == 3 then -- Triangle, but cost more?
		--	local a,b,c = p[1]:Distance(p[2]),p[2]:Distance(p[3]),p[3]:Distance(p[1])
		--	local s = (a + b + c) / 2
		--	t_t[self] = sqrt( s * (s - a) * (s - b) * (s - c) )
		--	return t_t[self]
		else -- Any shape
			local a = Vector(0,0,0)
			for i,pc in ipairs(p) do
				local pn = p[i + 1] or p[1]
				a = a + pc:Cross(pn)
			end
			a = a / 2
			self.n_area = a:Distance(Vector(0,0,0))
			return self.n_area
		end
	end
--[[-------------------------------------------------------------------------
Make some adv SurfaceInfo functions.
---------------------------------------------------------------------------]]
	function meta:GetUVVerts() 	-- Creates UV-data out from the shape.
		if not self:IsValid() then return end
		if self.t_uv then return table.Copy(self.t_uv) end
		local t = self:GetVerticesNoParallel()
		local a = self:GetNormal():Angle()
		local c = self:GetCenter()
		local vmin,vmax
		for i,v in ipairs(t) do
			t[i] = (t[i] - c)
			t[i]:Rotate(a)
			if not vmin then
				vmin = Vector(t[i].x,t[i].y,t[i].z)
				vmax = Vector(t[i].x,t[i].y,t[i].z)
			else
				for ii = 1,3 do
					vmin[ii] = math.min(vmin[ii],t[i][ii])
					vmax[ii] = math.max(vmax[ii],t[i][ii])
				end
			end
		end
		local y_r = vmax.z - vmin.z
		local x_r,x_r2 = vmax.x - vmin.x,vmax.y - vmin.y
		local min_x = vmin.x
		local i2 = 1
		if x_r2 > x_r then
			x_r = x_r2
			i2 = 2
			min_x = vmin.y
		end
		local new_t = {}
		for i = 1,#t do
			table.insert(new_t, {u = (t[i][i2] - min_x) / x_r,v = (t[i].z - vmin.z) / y_r})
		end
		self.t_uv = new_t
		return table.Copy(self.t_uv)
	end
	function meta:GetMesh() 	-- Generates a mesh-table for the surfaceinfo.
		if not self:IsValid() then return end
		if self.t_mesh then return table.Copy(self.t_mesh) end
		local verts = self:GetVerticesNoParallel()
		local n = self:GetNormal()
		-- Calc the height and width
		local h_max,h_min = verts[1].z,verts[1].z
		for i = 2,#verts do
			local h = verts[i].z
			h_max = math.max(h_max,h)
			h_min = math.min(h_min,h)
		end
		local uvt = self:GetUVVerts()
		local t = {}
		for i = 1,3 do
			table.insert(t, {pos = verts[i],		u = uvt[i].u,v = uvt[i].v, 			normal = n})
		end
		for i = 4,#verts do
			table.insert(t, {pos = verts[1],		u = uvt[1].u,v = uvt[1].v, 			normal = n})
			table.insert(t, {pos = verts[i - 1],	u = uvt[i - 1].u,v = uvt[i - 1].v, 	normal = n})
			table.insert(t, {pos = verts[i],		u = uvt[i].u,v = uvt[i].v, 			normal = n})
		end
		self.t_mesh = t
		return table.Copy(self.t_mesh)
	end
	function meta:GetMinSide()
		if not self:IsValid() then return end
		if self.n_midi then return self.n_midi end
		local mi,ma
		local p = self:GetVertices()
		for i = 1,#p do
			if not mi then
				mi = p[i]:Distance(p[i + 1] or p[1])
				ma = mi
			else
				mi = math.min(mi,p[i]:Distance(p[i + 1] or p[1]))
				ma = math.max(ma,p[i]:Distance(p[i + 1] or p[1]))
			end
		end
		self.n_midi = mi
		self.n_madi = ma
		return mi
	end
	function meta:GetMaxSide()
		if not self:IsValid() then return end
		if self.n_madi then return self.n_madi end
		self:GetMinSide()
		return self.n_madi
	end
--[[-------------------------------------------------------------------------
Generate meshes and env-points out from the map-data.
---------------------------------------------------------------------------]]
-- New surface functions
	local function SurfaceInfo_GetType( eEnt, SurfaceInfo )
		if #SurfaceInfo:GetVertices() < 3 then return INVALID_VERTS end
		if SurfaceInfo:IsWater() then -- Water
			return WATER_VERTS
		end
		return INVALID_VERTS
	end


local ice = Material("stormfox2/effects/ice_water")
local ice_size = 500
local vec_ex = Vector(0,0,1)

STORMFOX_WATERMESHCOLLISON = {}

local scan = function() -- Locates all surfaceinfos we need.
	StormFox2.Msg("Scanning surfaces ..")
	surfaceinfos = {}
	-- Scan all brushsurfaces and grab the glass/windows, water and metal. Put them in a table with matching normal.
		for i,v in ipairs( game.GetWorld():GetBrushSurfaces() ) do
			if not v then continue end
			if not v:IsValid() then continue end
			if not v:IsWater() then continue end
			local v_type = SurfaceInfo_GetType( game.GetWorld(), v )
			if v_type == INVALID_VERTS then continue end -- Invalid or doesn't have a type.
			if not surfaceinfos[v_type] then surfaceinfos[v_type] = {} end
			table.insert(surfaceinfos[v_type], {v, v:GetCenter()} )
		end
		coroutine.yield()
	-- Generate water mesh
		if surfaceinfos[WATER_VERTS] then
			StormFox2.Msg("Generating ice-mesh [" .. #surfaceinfos[WATER_VERTS] .. "] ")
			local mesh = {}
			for i,v in ipairs(surfaceinfos[WATER_VERTS]) do
				if StormFox2.Map.IsInside( v[2] ) then
					local t = v[1]:GetVertices()
					if #t >= 3 then
						local t2 = {}
						if #t == 3 then
							for i = 1,3 do
								table.insert(t2, t[i] + vec_ex)
								table.insert(t2, t[i] - vec_ex)
							end
							table.insert(t2, t[3] + vec_ex)
							table.insert(t2, t[3] - vec_ex)
							table.insert(STORMFOX_WATERMESHCOLLISON, t2)
						elseif #t == 4 then
							for i = 1,4 do
								table.insert(t2, t[i] + vec_ex)
								table.insert(t2, t[i] - vec_ex)
							end
							table.insert(STORMFOX_WATERMESHCOLLISON, t2)
						else
							for i = 1,#t do
								table.insert(t2, t[i] + vec_ex)
								table.insert(t2, t[i] - vec_ex)
							end
							table.insert(STORMFOX_WATERMESHCOLLISON, t2)
						end
					end
				end
			end
		end
	coroutine.yield(true)
end

local cor_scan = coroutine.wrap(scan)
local function StartGenerating()
	timer.Create("SF_ENV_SCAN", 0.2, 0, function()
		if cor_scan() then
			cor_scan = nil
			timer.Remove("SF_ENV_SCAN")
			StormFox2.Msg("Meshes completed.")
		end
	end)
	hook.Remove("StormFox2.InitPostEntity", "StormFox_ENV_SCAN")
end
hook.Add("StormFox2.InitPostEntity", "StormFox_ENV_SCAN", StartGenerating)

local bIce = false
local function SpawnIce()
	for k,v in ipairs(ents.FindByClass("stormfox_mapice")) do
		v:Remove()
	end
	local e = ents.Create("stormfox_mapice")
	e:SetPos(Vector(0,0,0))
	e:Spawn()
	bIce = true
end

local function RemoveIce()
	bIce = false
	for k,v in ipairs(ents.FindByClass("stormfox_mapice")) do
		v:Remove()
	end
end

timer.Create("stormfox2.spawnice", 8, 0, function()
	if not StormFox2.Setting.GetCache("enable_ice") then
		if bIce then
			RemoveIce()
		end
		return
	end
	if bIce and StormFox2.Temperature.Get() > -1 then
		RemoveIce()
	elseif not bIce and StormFox2.Temperature.Get() <= -8 then
		SpawnIce()
	end
end)