-------------------------------------------------------------------------------
--Internal Data Structures
-------------------------------------------------------------------------------

simple_music = {}

local player_context = {}
local tracks = {}

-------------------------------------------------------------------------------
--Settingtypes
-------------------------------------------------------------------------------

local mod_name = core.get_current_modname()

local function get_setting(name, def)

    if type(def) == "boolean" then
        local inp = core.settings:get_bool(mod_name .. "_" .. name, def)
        return inp
    elseif type(def) == "string" or type(def) == "table" then
        local inp = core.settings:get(mod_name .. "_" .. name) or def
        return inp
    else
        local inp = core.settings:get(mod_name .. "_" .. name) or def
        return tonumber(inp)
    end

end

local time_interval = get_setting("time_interval", 60)
local cleanup_interval = get_setting("cleanup_interval", 5)
local global_gain = get_setting("global_gain", 0.5)
local add_random_delay = get_setting("add_random_delay", true, true)
local maximum_random_delay = get_setting("maximum_random_delay", 30)
local playback_msg = get_setting("playback_messages", true, true)

-------------------------------------------------------------------------------
--Internal Variables
-------------------------------------------------------------------------------

local random_delay = 0
local settingsfile = mod_name .. "_settings"

if add_random_delay then
    random_delay = math.random(maximum_random_delay)
end

-------------------------------------------------------------------------------
--Utilities
-------------------------------------------------------------------------------

local function msg(level, input)
    core.log(level, "[" .. mod_name .. "] " .. input)
end

local function load_player_settings(name)

    local file = io.open(core.get_worldpath() ..
    "/" ..
    settingsfile ..
    ".mt", "r")

    if file then
        local rawfile = file:read()
        io.close(file)
        if rawfile then
            local settings = core.deserialize(rawfile)
            if settings[name] then
                player_context[name].settings = settings[name]
            end
        else
            msg("error", "Unable to read volume settings!")
        end
    end

end

local function save_player_settings(name)

    local path = core.get_worldpath() ..
    "/" ..
    settingsfile ..
    ".mt"

    local file = io.open(path, "r")
    local settings = {}

    if file then
        local rawfile = file:read()
        io.close(file)
        if rawfile then
            settings = core.deserialize(rawfile) or {}
        end
    end

    settings[name] = player_context[name].settings

    file = io.open(path, "w")

    if file then
        local rawfile = core.serialize(settings)
        file:write(rawfile)
        io.close(file)
        msg("action", "saving volume settings for " .. name)
    else
        msg("error", "Unable to save volume settings!")
    end

end

local function play_track(name)

    local player = core.get_player_by_name(name)
    local player_pos = player:get_pos()
    local time = core.get_timeofday()
    local possible_tracks = {}

    for _,track in pairs(tracks) do
        if track.name ~= player_context[name].previous and
        ((track.day and time > 0.25 and time < 0.75) or
        (track.night and ((time < 0.25 and time >= 0) or
        (time > 0.75 and time <= 1)))) and
        player_pos.y >= track.ymin and player_pos.y < track.ymax then
            table.insert(possible_tracks, track)
        end
    end

    if #possible_tracks == 0 then
        player_context[name].previous = nil
        return
    end

    local track = possible_tracks[math.random(#possible_tracks)]

    if not player_context[name].playing then

        if playback_msg then
            msg("action", "Starting playblack for: " ..
            name ..
            " " ..
            track.name ..
            " Available tracks for user: " ..
            #possible_tracks ..
            " Random delay: " ..
            random_delay)
        end

        local total_gain = track.gain *
        global_gain *
        player_context[name].settings.gain

        local handle = core.sound_play(track.name,
        {
            to_player = name,
            gain = total_gain,
        })

        player_context[name].track_handle = handle
        player_context[name].playing = true
        player_context[name].previous = track.name
        player_context[name].playback_started = os.time()
        player_context[name].track_def = track

    end

end

local function stop_track(name)

    if player_context[name] and player_context[name].playing and
    player_context[name].track_handle then

        core.sound_stop(player_context[name].track_handle)
        player_context[name].playing = false
        player_context[name].track_handle = nil
        player_context[name].playback_started = nil
        player_context[name].track_def = nil

        if playback_msg then
            msg("action", "Stopped playback for: " .. name)
        end

    end
end

-------------------------------------------------------------------------------
--Formspecs
-------------------------------------------------------------------------------

local function display_music_settings(name)

    local user
    if type(name) ~= "string" and name:is_player() then
        user = name:get_player_name()
    else
        user = name
    end

    local volume = math.floor(player_context[user].settings.gain * 1000)

    local formspec = {}

    formspec[#formspec + 1] = "size[5,2]"
    formspec[#formspec + 1] = "textarea[0.3,0.06;2,1;;Volume:;]"
    formspec[#formspec + 1] = "scrollbar[0,0.6;4.8,0.25;horizontal;volume;"
    formspec[#formspec + 1] = tostring(volume)
    formspec[#formspec + 1] = "]"
    formspec[#formspec + 1] = "button[0,1.5;1,0.3;play;Play]"
    formspec[#formspec + 1] = "button[0.9,1.5;1,0.3;stop;Stop]"
    formspec[#formspec + 1] = "button_exit[3,1.5;2,0.3;accept;Accept]"

    core.show_formspec(user, mod_name .. "_settings", table.concat(formspec))

end

core.register_on_player_receive_fields(function(player, formname, fields)

    if formname ~= mod_name .. "_settings" then return end

    local name = player:get_player_name()

    if fields.volume then
        local params = core.explode_scrollbar_event(fields.volume)
        player_context[name].settings.gain = params.value / 1000
    end

    if fields.play then
        play_track(name)
    end

    if fields.stop then
        stop_track(name)
    end

    if fields.accept or fields.quit then

        save_player_settings(name)

    end

end)

-------------------------------------------------------------------------------
--API
-------------------------------------------------------------------------------

function simple_music.register_track(def)

    if not def.name or not def.length then
        msg("error", "Missing track definition parameters!")
        return
    end

    local track_def = {
        name = def.name,
        length = def.length,
        gain = def.gain or 1,
        day = def.day or false,
        night = def.night or false,
        ymin = def.ymin or -31000,
        ymax = def.ymax or 31000,
    }

    table.insert(tracks, track_def)
end

-------------------------------------------------------------------------------
--Registrations
-------------------------------------------------------------------------------

core.register_on_joinplayer(function(player)
    local name = player:get_player_name()
    player_context[name] = {
        playing = false,
        playback_started = nil,
        track_handle = nil,
        track_def = nil,
        previous = nil,
        settings = {
            gain = 1,
        },
    }
    load_player_settings(name)
end
)

core.register_on_leaveplayer(function(player)
    local name = player:get_player_name()
    player_context[name] = nil
end
)

core.register_chatcommand("musicsettings",{
    params = "",
    description = "Displays music settings menu",
    privs = {shout = true},
    func = display_music_settings
})

if core.get_modpath("sfinv_buttons") then
    sfinv_buttons.register_button(mod_name .. "_music_settings",
    {
        title = "Music Settings",
        action = display_music_settings,
        tooltip = "Show music settings",
        image = mod_name .. "_sfinv_buttons_icon.png",
    })
end

-------------------------------------------------------------------------------
--Globalstep
-------------------------------------------------------------------------------

local cleanup_timer = 0
core.register_globalstep(function(dtime)

    cleanup_timer = cleanup_timer + dtime
    if cleanup_timer < cleanup_interval then return end

    for k,v in pairs(player_context) do

        if v.playing and os.time() > v.playback_started +
        v.track_def.length then
            stop_track(k)
        end

    end

    cleanup_timer = 0
end)

local track_timer = 0
core.register_globalstep(function(dtime)

    track_timer = track_timer + dtime
    if track_timer < time_interval + random_delay then return end

    if next(tracks) == nil then return end

    for k,_ in pairs(player_context) do
        play_track(k)
    end

    if add_random_delay then
        random_delay = math.random(maximum_random_delay)
    end

    track_timer = 0
end)
