-- Parameters

local settings=core.settings
local particle_mult=tonumber((settings:get("snowdrift_mul") or "1"))
local custom_sat=tonumber((settings:get("custom_sat") or "1"))
local fog_dist=(tonumber((settings:get("max_block_send_distance") or "12")))*15
-- fog distance should be send_distance*16 cause unit is (map) blocks.
-- But one needs to leave some in the background to limit the "popping" effect
-- that I don't like.

local YLIMIT = -10 -- Set to world's water level
-- Particles are timed to disappear at this y
-- Particles do not spawn when player's head is below this y
local PRECSPR = 5 -- Time scale for precipitation variation in minutes
local GSCYCLE = 2 -- Globalstep cycle (seconds)
local FLAKES = 8 -- Snowflakes per cycle
local DROPS = 16 -- 128 -- Raindrops per cycle
local RAINGAIN = 0.1 -- Rain sound volume

local np_prec = {
	offset = 0,
	scale = 1,
	spread = {x = PRECSPR, y = PRECSPR, z = PRECSPR},
	seed = 813,
	octaves = 1,
	persist = 0,
	lacunarity = 2.0,
	--flags = ""
}

local random=math.random

-- These 2 must match biome heat and humidity noise parameters for a world

--[[
local np_temp = {
	offset = 50,
	scale = 50,
	spread = {x = 1000, y = 1000, z = 1000},
	seed = 5349,
	octaves = 3,
	persist = 0.5,
	lacunarity = 2.0,
	--flags = ""
}
]]

--[[
local np_humid = {
	offset = 50,
	scale = 50,
	spread = {x = 1000, y = 1000, z = 1000},
	seed = 842,
	octaves = 3,
	persist = 0.5,
	lacunarity = 2.0,
	--flags = ""
}
]]


-- Stuff

local grad = 14 / 95
local yint = 1496 / 95


-- Initialise noise objects to nil

--local nobj_temp = nil
--local nobj_humid = nil
local nobj_prec = nil

minetest.after(0, function()
--	nobj_temp = minetest.get_perlin(np_temp)
--	nobj_humid =minetest.get_perlin(np_humid)
	nobj_prec = minetest.get_perlin(np_prec)
end)


-- Globalstep function

snowdrift_handles = {}
local timer = 0

--[[
snowdrift= {}
function snowdrift.get_temp(pos)
	return nobj_temp:get2d {x=pos.x, y=pos.z}
end
--]]

local function count_players_inside_radius(pos, radius)
	local count=0
	for playerName, _ in pairs(mfplayers) do
		local player = minetest.get_player_by_name(playerName)
		if vector.distance(pos, player:get_pos()) <= radius then
			count=count+1
		end
	end
	return count
end

minetest.register_globalstep(function(dtime)
	timer = timer + dtime
	if timer < GSCYCLE then return end
	timer = 0

	local time = minetest.get_timeofday()
	for _, player in ipairs(minetest.get_connected_players()) do
		local player_name = player:get_player_name()
		local ppos = player:get_pos()
		local pposy = math.floor(ppos.y) + 2 -- Precipitation when swimming
		local pposx = math.floor(ppos.x)
		local pposz = math.floor(ppos.z)
		local ppos = {x = pposx, y = pposy, z = pposz}

		--local nval_temp = nobj_temp:get2d({x = pposx, y = pposz})
		--local nval_humid = nobj_humid:get2d({x = pposx, y = pposz})
		local bd=minetest.get_biome_data(ppos)
		local nval_temp=bd.heat
		local moons=minetest.get_day_count()
		-- Since sin() has a 2xPI period, moons/10 gives a year of approx. 63 moons (20xPI)
		local nval_humid= 0.8*bd.humidity-0.2*100*math.sin(moons/10) -- the - makes the weather nicer the first few days (<=> 180° offset)
		local nval_prec = nobj_prec:get_2d({x = minetest.get_gametime() / 60, y = 0})

		-- Biome system: Frozen biomes below heat 35,
		-- deserts below line 14 * t - 95 * h = -1496
		-- h = (14 * t + 1496) / 95
		-- h = 14/95 * t + 1496/95
		-- where 14/95 is gradient and 1496/95 is y intersection
		-- h - 14/95 t = 1496/95 y intersection
		-- so area above line is
		-- h - 14/95 t > 1496/95
		local freeze = nval_temp < 35
		-- local precip = nval_prec < (nval_humid - 50) / 50 - 0.9 + 0.3*math.sin(minetest.get_day_count()/10) and
		local precip = nval_prec < (nval_humid - 50) / 50 - 0.9 and nval_humid - grad * nval_temp > yint
		-- Check if player is outside
		local outside = (minetest.get_node_light(ppos, 0.5) or 0) >= default.LIGHT_MAX

		local p=1-nval_prec
		local clouds=player:get_clouds()
		local d= math.floor(500*math.min(0.9, math.max(0.1,nval_humid*p/200)))/500
		clouds.density=d
		player:set_lighting{ 
			shadows={intensity=0.3*(1-d)}, 
			saturation=custom_sat, 
			volumetric_light={strength=0.1*d},
			bloom={intensity=0.2*d*d, radius=1, strength_factor=3}
		}
		clouds.height=120-math.floor(d*32)
		clouds.thickness=8+math.floor(d*64)
		clouds.speed={x=2*p*math.sin(moons/10), z= 2*p*math.cos(moons/10)}
		d=d*d
		local c=math.floor(255-d*128)
		clouds.color={r=c, g=c, b=0xC0, a=0xFD}
		player:set_clouds(clouds)
		local r=math.max(0.2, 0.5*(1-math.cos(time*2*math.pi)))
		player:override_day_night_ratio(r*(1-d))
		if precip and not freeze and random()*clouds.density>0.75 and minetest.check_player_privs(player, "interact") then
			-- divide the chance by the number of nearby players.
			-- Since guests do not count, the spawn area should have less lightnings. Not a bad thing.
			if random(1, count_players_inside_radius(ppos, 64))==1 then
				if outside then
					lightning.strike(ppos)
				else
					minetest.sound_play("lightning_thunder", { pos = ppos, gain = 0.2, max_hear_distance = 64 })
				end
			end
		end
		-- minetest.chat_send_all("CD= "..d.." L= "..(15*(r*(1-d))).." P= "..p.." H= "..nval_humid.." N= "..N)

		player:set_sky { fog={fog_distance=fog_dist*(1-clouds.density)}}
		if pposy >= YLIMIT - 2 and pposy <= clouds.height+8 then
			if not precip or freeze or not outside then
				if snowdrift_handles[player_name] then
					-- Stop sound if playing
					minetest.sound_stop(snowdrift_handles[player_name])
					snowdrift_handles[player_name] = nil
				end
			end

			if precip and outside then
				local N=math.floor(60*player:get_clouds().density)
				-- Precipitation
				local xyz0={x=0, y=0, z=0}
				if freeze then
					minetest.add_particlespawner
					{
						amount=math.ceil(500*particle_mult*d),
						time=GSCYCLE,
						attached=player,
						minpos={x=-10, y=10, z=-10}, -- {x=-20, y=10, z=-20},
						maxpos={x=10, y=10, z=10}, -- {x=20, y=10, z=20},
						minvel={x=0,y=-5,z=0},
						maxvel={x=0,y=-10,z=0},
						minacc=xyz0,
						maxacc=xyz0,
						minsize=1,
						maxsize=2,
						minexptime=5,
						maxexptime=10,
						collisiondetection=true,
						collision_removal=true,
						--texpool={ "snowdrift_snowflake1.png","snowdrift_snowflake2.png","snowdrift_snowflake3.png","snowdrift_snowflake4.png" },
						texture="dot.png";
						playername=player:get_player_name(),
						vertical=true,
						jitter={min={x=-50, y=-10, z=-50}, max={x=50,y=10,z=50}},
					}
				else
					-- Rainfall
					minetest.add_particlespawner
					{
						amount=math.ceil(200*particle_mult),
						time=GSCYCLE,
						attached=player,
						minpos={x=-3, y=5, z=-3},
						maxpos={x=3, y=5, z=3},
						minvel={x=1,y=-5,z=1},
						maxvel={x=1,y=-10,z=1},
						minacc=xyz0,
						maxacc=xyz0,
						minsize=0.25,
						maxsize=0.5,
						minexptime=GSCYCLE,
						maxexptime=GSCYCLE*2,
						collisiondetection=true,
						collision_removal=true,
						texture="snowdrift_raindrop.png",
						playername=player:get_player_name(),
						vertical=true,
					}
				end
			end
			if snowdrift_handles[player_name] then
				minetest.sound_stop(snowdrift_handles[player_name])
				snowdrift_handles[player_name] = nil
			end
			-- Start sound if not playing
			local handle=nil
			if precip and not freeze then
				if outside and pposy >= YLIMIT - 2 and pposy <= clouds.height+8 then
					handle = minetest.sound_play( "snowdrift_rain",
					{
						to_player = player_name,
						gain = RAINGAIN*(1+4*d),
						pitch= 1+3*d,
						loop = true,
					})
				else	
					handle = minetest.sound_play( "snowdrift_rain",
					{
						to_player = player_name,
						gain = 0.1,
						pitch= 1+3*d,
						loop = true,
					})
				end
			end
			if handle then
				snowdrift_handles[player_name] = handle
			end
		elseif snowdrift_handles[player_name] then
			-- Stop sound when player goes under y limit
			minetest.sound_stop(snowdrift_handles[player_name])
			snowdrift_handles[player_name] = nil
		end
	end
end)


-- Stop sound and remove player handle on leaveplayer

minetest.register_on_leaveplayer(function(player)
	local player_name = player:get_player_name()
	if snowdrift_handles[player_name] then
		minetest.sound_stop(snowdrift_handles[player_name])
		snowdrift_handles[player_name] = nil
	end
end)
