lzr_ambience = {}

-- The background/ambience music mod for Lazarr!
-- Originally this mod only contained pure ambience sounds
-- but then was switched to use music files instead.
-- The name `lzr_ambience` is kept for historic reasons,
-- To the user, the sound files here must be called "music".
-- Internally, they are called "ambiences".

local S = minetest.get_translator("lzr_ambience")

-- Gain multiplayer for all sounds (between 0.0 and 1.0)
local MASTER_GAIN = 1.0

local ambiences = {}
local ambience_aliases = {}

local current_singleplayer_ambience = nil -- ambience sound handler. nil if no current ambience
local current_singleplayer_ambience_id = nil -- ambience type ID (e.g. "village"). nil if uninitialized
local ambience_active = true -- whether to play ambience
local ambience_reduced = false -- whether volume of ambience is currently reduced to to sound effect
local ambience_reduced_timer = nil
local ambience_play_times = {}

lzr_ambience.DEFAULT_AMBIENCE = "village"
local REDUCED_GAIN = 0.1
local REDUCED_GAIN_STEP_DOWN = 5
local REDUCED_GAIN_STEP_UP = 0.2

local REPEAT_WRAP = 60000

lzr_ambience.reduce_ambience = function(reduce_time)
	ambience_reduced = true
	if ambience_reduced_timer then
		ambience_reduced_timer = math.max(ambience_reduced_timer, reduce_time)
	else
		ambience_reduced_timer = reduce_time
	end
	if current_singleplayer_ambience then
		local ambience = ambiences[current_singleplayer_ambience_id]
		local gain = ambience.gain * REDUCED_GAIN * MASTER_GAIN
		minetest.sound_fade(current_singleplayer_ambience, REDUCED_GAIN_STEP_DOWN, gain)
	end
end

lzr_ambience.unreduce_ambience = function()
	ambience_reduced = false
	ambience_reduced_timer = nil
	if current_singleplayer_ambience then
		local ambience = ambiences[current_singleplayer_ambience_id]
		local gain = ambience.gain * MASTER_GAIN
		minetest.sound_fade(current_singleplayer_ambience, REDUCED_GAIN_STEP_UP, gain)
	end
end

lzr_ambience.register_ambience = function(id, soundname, gain, description)
	ambiences[id] = { soundname = soundname, gain = gain, description = description }
	ambience_play_times[id] = 0
end

lzr_ambience.register_ambience_alias = function(old_name, new_name)
	ambience_aliases[old_name] = new_name
end

local registered_on_ambience_changes = {}
lzr_ambience.register_on_ambience_change = function(func)
	table.insert(registered_on_ambience_changes, func)
end
local function report_ambience_change(new_state)
	for i=1, #registered_on_ambience_changes do
		registered_on_ambience_changes[i](new_state)
	end
end

local function stop_ambience()
	if current_singleplayer_ambience then
		minetest.sound_stop(current_singleplayer_ambience)
		current_singleplayer_ambience = nil
	end
end

function lzr_ambience.ambience_exists(id)
	if ambiences[id] then
		return true
	else
		return false
	end
end

function lzr_ambience.get_ambiences()
	local list = {}
	for id,_ in pairs(ambiences) do
		table.insert(list, id)
	end
	table.sort(list, function(t1, t2)
		if t1 == "none" and t2 ~= "none" then
			return true
		elseif t1 ~= "none" and t2 == "none" then
			return false
		else
			local d1 = lzr_ambience.get_ambience_description(t1)
			local d2 = lzr_ambience.get_ambience_description(t2)
			return d1 < d2
		end
	end)
	return list
end

function lzr_ambience.get_ambience_description(id)
	if ambience_aliases[id] then
		id = ambience_aliases[id]
	end
	return ambiences[id].description
end

function lzr_ambience.set_ambience(id, no_play)
	if ambience_aliases[id] then
		id = ambience_aliases[id]
	end
	local ambience = ambiences[id]
	if not ambience then
		minetest.log("error", "[lzr_ambience] set_ambience called with invalid ambience ID ("..tostring(id)..")")
		return false
	end
	minetest.log("info", "[lzr_ambience] Ambience set to: "..tostring(id))
	-- No-op if we already have this ambience active
	if current_singleplayer_ambience_id == id and current_singleplayer_ambience then
		return true
	end
	stop_ambience()
	if id ~= "none" and not no_play and ambience_active then
		local gain
		if ambience_reduced then
			gain = ambience.gain * REDUCED_GAIN * MASTER_GAIN
		else
			gain = ambience.gain * MASTER_GAIN
		end
		local start_time = ambience_play_times[id]
		current_singleplayer_ambience = minetest.sound_play({name=ambience.soundname}, {gain = gain, to_player="singleplayer", loop=true, start_time=start_time})
	end
	current_singleplayer_ambience_id = id
	return true
end

function lzr_ambience.toggle_ambience(save_setting)
	if current_singleplayer_ambience then
		ambience_active = false
		stop_ambience()
		report_ambience_change(ambience_active)
		if save_setting then
			minetest.settings:set_bool("lzr_ambience", false)
		end
		return false
	else
		ambience_active = true
		lzr_ambience.set_ambience(current_singleplayer_ambience_id)
		report_ambience_change(ambience_active)
		if save_setting then
			minetest.settings:set_bool("lzr_ambience", true)
		end
		return true
	end
end

function lzr_ambience.toggle_ambience_by_player(player, save_setting)
	local new_status = lzr_ambience.toggle_ambience(save_setting)
	if new_status then
		lzr_messages.show_message(player, S("Music enabled"), 2)
		return true
	else
		lzr_messages.show_message(player, S("Music disabled"), 2)
		return false
	end
end

function lzr_ambience.is_active()
	return ambience_active
end

minetest.register_globalstep(function(dtime)
	if not ambience_active then
		return
	end
	if ambience_reduced and ambience_reduced_timer then
		ambience_reduced_timer = ambience_reduced_timer - dtime
		if ambience_reduced_timer <= 0 then
			lzr_ambience.unreduce_ambience()
		end
	end
	if current_singleplayer_ambience_id then
		ambience_play_times[current_singleplayer_ambience_id] = ambience_play_times[current_singleplayer_ambience_id] + dtime
		if ambience_play_times[current_singleplayer_ambience_id] > REPEAT_WRAP then
			ambience_play_times[current_singleplayer_ambience_id] = 0
		end
	end
end)

local setting = minetest.settings:get_bool("lzr_ambience", true)

minetest.register_on_joinplayer(function(player)
	if lzr_gamestate.get_state() == lzr_gamestate.MENU then
		if setting then
			ambience_active = true
			report_ambience_change(ambience_active)
			lzr_ambience.set_ambience("village")
		else
			ambience_active = false
			report_ambience_change(ambience_active)
			lzr_ambience.set_ambience("village", true)
		end
	end
end)

minetest.register_chatcommand("music", {
	description = S("Toggle music"),
	func = function(name)
		local player = minetest.get_player_by_name(name)
		local new_status = lzr_ambience.toggle_ambience_by_player(player, true)
		if new_status then
			return true, S("Music enabled.")
		else
			return true, S("Music disabled.")
		end
	end,
})

-- Special built-in ambience that is completely silent
--~ Special entry in music list representing silence
lzr_ambience.register_ambience("none", nil, nil, S("None"))

dofile(minetest.get_modpath("lzr_ambience").."/register.lua")
