-- 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 = -12 -- 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 = 1.511 -- Globalstep cycle (seconds)
local FLAKES = 8 -- Snowflakes per cycle
local DROPS = 16 -- 128 -- Raindrops per cycle
local RAINGAIN = 0.1 -- Rain sound volume

local playerSats={}
local playerSh={}

core.register_chatcommand("sat", {
	description="Adjusts saturation %. 100% is the default, 50% gives a grey world, 200% gives vivid colors.",
	params="<number> saturation in % (10-1000)",
	func=function(name, param)
		local sat=math.max(math.min(tonumber(param) or 100, 1000), 10)/100
		playerSats[name]=sat
	end
})

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

-- Stuff

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


-- Initialise noise objects to nil

local nobj_prec = nil

minetest.after(0, function()
	nobj_prec = minetest.get_perlin(np_prec)
end)


-- Globalstep function

snowdrift_handles = {}
local timer = 0

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()
	local gametime60=minetest.get_gametime()/60
	local moons=minetest.get_day_count()
	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 bd=minetest.get_biome_data(ppos)
		local nval_temp=bd.heat
		-- 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 = gametime60, y = 0})

		local freeze = nval_temp < 25
		local precip = nval_prec < (nval_humid - 50) / 50 - 1.1 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{ 
			saturation=(playerSats[player_name] or 1)*(player:get_hp()/20),
			shadows={intensity=0.3*(1-d)*(playerSh[player_name] or 1)}, 
			volumetric_light={strength=0.1*d},
			bloom={intensity=0.2*d*d, radius=1, strength_factor=3},
		}
		clouds.height=150-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 (pposy>= YLIMIT) 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 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 then
				-- Precipitation
				local xyz0={x=0, y=0, z=0}
				if freeze then
					minetest.add_particlespawner
					{
						amount=math.ceil(500*particle_mult*clouds.density),
						time=GSCYCLE,
						attached=player,
						minpos={x=-10, y=20, z=-10}, -- {x=-20, y=10, z=-20},
						maxpos={x=10, y=20, z=10}, -- {x=20, y=10, z=20},
						minvel={x=0,y=-5,z=0},
						maxvel={x=0,y=-10,z=0},
						minacc=xyz0, -- TODO maybe this is not needed
						maxacc=xyz0,
						minsize=1,
						maxsize=2,
						minexptime=5,
						maxexptime=10,
						collisiondetection=true,
						collision_removal=true,
						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(2000*particle_mult*clouds.density),
						time=GSCYCLE,
						attached=player,
						minpos={x=-9, y=20, z=-9},
						maxpos={x=9, y=20, z=9},
						minvel={x=0,y=-100,z=0},
						maxvel={x=0,y=-100,z=0},
						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
			if precip and not freeze then
				local gain=0.1
				if outside and pposy >= YLIMIT and pposy <= clouds.height+8 then
					gain = RAINGAIN*(1+4*d)
				end
				snowdrift_handles[player_name] = minetest.sound_play( "snowdrift_rain",
				{
					to_player = player_name,
					gain = gain,
					pitch= 1+3*d,
					loop = true,
				})
			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)

-- Ice and snow melt outside of freezing biomes

local schedule_melting= function(pos)
	-- 50 is tree_limit in mapgen and 25 is the temp. for snowy biomes.
	if pos.y>=50 or minetest.get_heat(pos)<=25 then return end
	minetest.get_node_timer(pos):start(math.random(600, 900))
end

minetest.override_item("default:ice",
{
	on_timer=minetest.remove_node,
	on_construct=schedule_melting,
})

minetest.override_item("default:snowblock",
{
	on_timer=minetest.remove_node,
	on_construct=schedule_melting,
})

minetest.register_chatcommand("sh", {
	description="Sets shadows intensity",
	params="<shadow intensity (multiplier)>",
	func = function(name, param)
		playerSh[name]=(tonumber(param) or 1)
		return true
	end
})


