vg_weather = {}
vg_weather.min_y = -200 -- no rain in caves/nether
vg_weather.max_y = 140 -- no rain above clouds (realism :P)

local math, os, minetest = math, os, minetest

local ENABLE_WEATHER = minetest.settings:get_bool("vg.enable_weather", true)

local PRECTIM = 15 -- Time scale for precipitation variation

-- Precipitation noise threshold
local PRECTHR = tonumber(minetest.settings:get("vg.precipitation_threshold")) or 0.3
-- -1.0 to 1.0:
--	-1.0 = precipitation all the time
--	 0.0 = precipitation half the time
--	+1.0 = no precipitation

local DROPLPOS  = tonumber(minetest.settings:get("vg.rain_amount")) or 100
local RAINGAIN  = tonumber(minetest.settings:get("vg.rain_volume")) or 0.8
local DROPRAD   = tonumber(minetest.settings:get("vg.rain_radius")) or 24
local SDENSITY  = tonumber(minetest.settings:get("vg.precipitation_density")) or 6
local FREEZETHR = 21 -- Used to figure out what biomes are snowy

-- NoiseParams for the precipitation noise
-- Used to figure out when it should rain/snow based on PRECTHR
local np_prec = {
	offset = 0,
	scale = 1,
	spread = {x = PRECTIM, y = PRECTIM, z = PRECTIM},
	seed = 72105,
	octaves = 1,
	persist = 0,
	lacunarity = 2.0,
	flags = "defaults"
}
-- Initialize noise object
local nobj_prec

-- Stuff
local SDSQUARED = SDENSITY * SDENSITY
local SRANGE = DROPRAD / SDENSITY
local grad = 14 / 95
local yint = 1496 / 95

-- mgvalleys support
local valleys = minetest.get_mapgen_setting("mg_name") == "valleys"
local alt_chill = valleys and minetest.get_mapgen_setting("mgvalleys_spflags"):find("altitude_chill")
local alt_dry = valleys and minetest.get_mapgen_setting("mgvalleys_spflags"):find("altitude_dry")
local alt_chill_dist =  valleys and minetest.get_mapgen_setting("mgvalleys_altitude_chill") or 90

-- Whether any weather can possibly occur at `pos`.
function voxelgarden.has_weather(pos)
	return pos and ENABLE_WEATHER and
		(vg_weather.min_y < pos.y) and (pos.y < vg_weather.max_y)
end

-- Gametime when we start
local gametime_0 = minetest.get_gametime()
-- Random noise offset (Y)
local RNDOF = math.random(-300000, 300000)

-- What weather is there currently at `pos`?
-- Possible return values:
-- * "none"
-- * "rain"
-- * "snow"
--
-- IMPORTANT: this function calls `voxelgarden.has_weather(pos)`, don't check
-- for it twice!
function voxelgarden.get_weather(pos)
	if not voxelgarden.has_weather(pos) then
		return "none"
	end
	pos = {
		x = math.floor(pos.x + 0.5),
		y = math.floor(pos.y + 0.5),
		z = math.floor(pos.z + 0.5)
	}

	nobj_prec = nobj_prec or minetest.get_perlin(np_prec)

	local time = os.difftime(minetest.get_gametime(), gametime_0)

	local nval_temp  = minetest.get_heat(pos)
	local nval_humid = minetest.get_humidity(pos)
	local nval_prec  = nobj_prec:get_2d({x = time, y = RNDOF})
	-- ^ Scroll the noise with time + random offset

	-- mgvalleys adjustments to temp and humidity based on elevation
	if valleys then
		local elev = pos.y - 2
		while elev > 0 and minetest.get_node({x = pos.x, y = elev, z = pos.z}).name == "air" do
			elev = elev - 1
		end
		if alt_chill then
		nval_temp = nval_temp - (20 * elev / alt_chill_dist)
		end
		if alt_dry then
			nval_humid = nval_humid - (10 * elev / alt_chill_dist)
		end
	end

	local freeze = nval_temp < FREEZETHR
	local precip = nval_prec > PRECTHR and
		nval_humid - grad * nval_temp > yint

	return (precip and (freeze and "snow" or "rain") or "none")
end

if ENABLE_WEATHER then

-- Globalstep function

local handles = {}
local volumes = {}
local timer = 0

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

	timer = 0

	for _, player in ipairs(minetest.get_connected_players()) do
		local player_name = player:get_player_name()
		local pos = player:get_pos()
		if not pos then return end
		local ppos = {
			x = math.floor(pos.x + 0.5),
			y = math.floor(pos.y + 0.5),
			z = math.floor(pos.z + 0.5)
		}

		local weather = voxelgarden.get_weather(ppos)
		if weather == "none" then
			if handles[player_name] then
				-- Stop sound
				minetest.sound_stop(handles[player_name])
				handles[player_name] = nil
				volumes[player_name] = nil
			end
			return
		end

		local time = os.difftime(minetest.get_gametime(), gametime_0)

		nobj_prec = nobj_prec or minetest.get_perlin(np_prec)
		local nval_prec = nobj_prec:get_2d({x = time, y = RNDOF})

		local prec_abs = nval_prec + 1
		local thr_scale = math.max(1 - PRECTHR, 0.05) -- Don't divide by zero
		local thr_abs = PRECTHR + 1
		local rainfall = (prec_abs - thr_abs) / thr_scale
		-- How heavily is it raining (on a scale from 0 to 1 over the bounds we've set)?
		rainfall =  math.max(rainfall, 0.05) -- If we passed the threshhold, at least rain a little

		if weather ~= "rain" then
			if handles[player_name] then
				-- Stop sound if playing
				minetest.sound_stop(handles[player_name])
				handles[player_name] = nil
				volumes[player_name] = nil
			end
		else
			-- get_node_light can return nil @_@
			local light1 = minetest.get_node_light(pos, 0.5) or 0
			local light2 = minetest.get_node_light(pos, 0) or 0
			local skylit = light1 - light2
			-- Fade out
			local vol = RAINGAIN * (skylit / 15) ^ 2 * rainfall

			if handles[player_name] then
				if volumes[player_name] ~= vol then
					minetest.sound_fade(handles[player_name],
						(vol - volumes[player_name])/2, vol)
					volumes[player_name] = vol
				end
			else
				-- Start sound if not playing
				local handle = minetest.sound_play("snowdrift_rain", {
					to_player = player_name,
					gain = vol, -- Fade as we get out of skylit area
					loop = true,
				})
				handles[player_name] = handle
				volumes[player_name] = vol
			end
		end

		local node = minetest.get_node_or_nil({
			x = ppos.x,
			y = math.floor(pos.y+1.9),
			z = ppos.z
		})
		if node and minetest.registered_nodes[node.name] then -- Don't show particles when underwater
			if minetest.registered_nodes[node.name].liquidtype ~= "none" then
				return
			end
		end

		-- Create ParticleSpawners
		local lposx, lposz, spos
		local ndrops = math.ceil((DROPLPOS / 16) * rainfall)
		for lpos = 1, SDSQUARED do
			lposx = ppos.x - DROPRAD + ((math.floor(lpos/SDENSITY) + 0.5) * 2 * SRANGE)
			lposz = ppos.z - DROPRAD + (((lpos % SDENSITY) + 0.5) * 2 * SRANGE)

			spos = {x = lposx, y = ppos.y + 10, z = lposz}

			if minetest.get_node_light(spos, 0.5) == 15 then
				if weather == "rain" then
					minetest.add_particlespawner({
						amount = ndrops,
						time = 5,
						collisiondetection = true,
						collision_removal = true,
						vertical = true,
						texture = "snowdrift_raindrop.png",
						playername = player_name,
						pos = {
							min = {x = spos.x - SRANGE, y = spos.y, z = spos.z - SRANGE},
							max = {x = spos.x + SRANGE, y = spos.y, z = spos.z + SRANGE},
						},
						vel = {
							min = {x = 0.0, y = -14.0, z = 0.0},
							max = {x = 0.0, y = -12.0, z = 0.0},
						},
						acc = {
							min = {x = 0, y = 0, z = 0},
							max = {x = 0, y = 0, z = 0},
						},
						size = {min = 1.0, max = 1.2},
						exptime = {min = 2, max = 2},
					})
				elseif weather == "snow" then
					minetest.add_particlespawner({
						amount = ndrops,
						time = 5,
						collisiondetection = true,
						collision_removal = true,
						vertical = false,
						texture = "snowdrift_snowflake" ..
							math.random(1, 12) .. ".png",
						playername = player_name,
						pos = {
							min = {x = spos.x - SRANGE, y = spos.y, z = spos.z - SRANGE},
							max = {x = spos.x + SRANGE, y = spos.y, z = spos.z + SRANGE},
						},
						vel = {
							min = {x = -0.5, y = -2.5, z = -0.5},
							max = {x = 0.5,  y = -2.0, z = 0.5},
						},
						acc = {
							min = {x = 0, y = 0, z = 0},
							max = {x = 0, y = 0, z = 0},
						},
						size = {min = 0.9, max = 3.0, bias = 0.5},
						exptime = {min = 24, max = 24},
					})
				end
			end
		end
	end
end)

-- Weather-dependent ABMs
dofile(minetest.get_modpath("vg_weather").."/abms.lua")

-- Stop sound and remove player handle on leaveplayer
minetest.register_on_leaveplayer(function(player)
	local player_name = player:get_player_name()
	if handles[player_name] then
		minetest.sound_stop(handles[player_name])
		handles[player_name] = nil
		volumes[player_name] = nil
	end
end)

end
