local BGM = {}              -- KEY: track_name; VALUE: {name, description, params = MT params}
local SFX = {}              -- ^
-- i nomi delle tracce sono le cose più gettonate, per questo salvate anche a parte:
-- così da non dover copiare tabelle su tabelle ogni volta
local now_playing = {}      -- KEY: p_name; VALUE: {BGM = name, -- SFX = {name1, name2}, whatever = {},
                                                    -- _data = {bgm = {name, description, handle, started_at, params}, prev_bgm = {name, description, started_at, params},
                                                    -- sfx = {[name1] = {params}, [name2] = {params}}}}
                                                     -- TODO registra categorie personalizzate, se già non esistono

-- TODO opzione accessibilità che mostra sx - centro - dx da dove viene il suono e intensità. SE BGM, al centro con notina

-- TODO
function audio_lib.play_sound(track_name, params) end
function audio_lib.stop_stound(track_name) end
function audio_lib.get_sounds_of_type(type) end
function audio_lib.get_player_sfx(p_name, in_detail) end
function audio_lib.get_player_sounds_of_type(type) end -- TODO o get_player_sounds e (sopra) get_sounds?



-- INTERNAL ONLY
function audio_lib.init_player(p_name)
  audio_lib.load_settings(p_name)
  now_playing[p_name] = {_data = {bgm = {}, prev_bgm = {}, sfx = {}}}
end



function audio_lib.dealloc_player(p_name)
  now_playing[p_name] = nil
end





----------------------------------------------
---------------------CORE---------------------
----------------------------------------------

-- PROMEMORIA: su arena_lib lancialo quando carica le varie arene
function audio_lib.register_sound(s_type, track_name, desc, def)
  assert(type(s_type) == "string", "[AUDIO_LIB] Error! The first parameter in `audio_lib.register_sound` must be a string!")
  assert(desc, "[AUDIO_LIB] Error! Description is mandatory for accessibility reasons (TODO). Let's everyone enjoy videogames!")
  -- TODO assert se traccia audio esiste; fattibile?

  def = def or {}
  def.to_player = nil
  def.object = nil
  def.pos = nil
  def.exclude_player = nil

  if s_type == "bgm" then
    def.loop = true

    BGM[track_name] = {
      name = track_name,
      description = desc,
      params = def
    }

  elseif s_type == "sfx" then
    def.loop = false

    SFX[track_name] = {
      name = track_name,
      description = desc,
      params = def
    }
  end -- TODO categorie personalizzate
end



function audio_lib.play_sfx(track_name, override_params)
  local audio = table.copy(SFX[track_name])
  local params = audio.params

  if override_params then
    for k, v in pairs(override_params) do
      params[k] = v
    end
  end

  params.loop = false

  -- tutto il server
  if not params.pos and not params.object and not params.to_player and not params.exclude_player then
    local gain = audio.params.gain or 1
    for _, pl in pairs(minetest.get_connected_players()) do
      local pl_name = pl:get_player_name()
      local pl_settings = audio_lib.get_settings(pl_name)

      params.gain = gain * (pl_settings.volume_sfx / 100) * (pl_settings.volume_main / 100)
      minetest.sound_play(track_name, params, true)

      now_playing[pl_name].sfx = track_name
      now_playing[pl_name]._data.sfx = audio
    end

  -- sennò...
  else
    -- pos
    if params.pos then
      -- TODO: per le posizioni, becca l3 giocatori nei dintorni

    -- object
    elseif params.object then
      -- TODO
    end

    -- to_player
    if params.to_player then
      local p_name = params.to_player

      if not now_playing[p_name] then return end

      local p_settings = audio_lib.get_settings(p_name)

      params.gain = (params.gain or 1) * (p_settings.volume_sfx / 100) * (p_settings.volume_main / 100)
      minetest.sound_play(track_name, params, true)

      now_playing[p_name].sfx = track_name
      now_playing[p_name]._data.sfx = audio

    -- exclude_player
    elseif params.exclude_player then
      local gain = audio.params.gain or 1

      for _, pl in pairs(minetest.get_connected_players()) do
        local pl_name = pl:get_player_name()

        if pl_name ~= params.exclude_player then
          local pl_settings = audio_lib.get_settings(pl_name)

          params.gain = gain * (pl_settings.volume_sfx / 100) * (pl_settings.volume_main / 100)
          minetest.sound_play(track_name, params, true)

          now_playing[pl_name].sfx = track_name
          now_playing[pl_name]._data.sfx = audio
        end
      end
    end
  end
end



-- TODO: passare tabella se si vogliono incolonnare più canzoni
function audio_lib.play_bgm(p_name, track_name, override_params)
  if not now_playing[p_name] then return end

  local audio = table.copy(BGM[track_name])
  local params = audio.params

  if override_params then
    for k, v in pairs(override_params) do
      params[k] = v
    end
  end

  audio.started_at = minetest.get_us_time()
  params.to_player = p_name

  -- se c'era già una canzone in corso
  if now_playing[p_name].bgm then
    -- se è la stessa, fermati
    if now_playing[p_name].bgm == track_name then return end

    local _data = now_playing[p_name]._data
    local curr_bgm = table.copy(_data.bgm)

    _data.prev_bgm = table.copy(_data.bgm)
    _data.prev_bgm.handle = nil
    _data.prev_bgm.params.start_time = (_data.prev_bgm.params.start_time or 0) + (minetest.get_us_time() - curr_bgm.started_at) / 1000000

    minetest.sound_stop(curr_bgm.handle)
  end

  local p_settings = audio_lib.get_settings(p_name)

  params.gain = (params.gain or 1) * (p_settings.volume_bgm / 100) * (p_settings.volume_main / 100)
  local handle = minetest.sound_play(track_name, params)

  audio.handle = handle
  now_playing[p_name].bgm = track_name
  now_playing[p_name]._data.bgm = audio
end



-- TODO: aspettando MT 5.8, start_time aggiunto con quella versione
function audio_lib.continue_bgm(p_name)
  if not now_playing[p_name] then return end

  local _data = now_playing[p_name]._data

  if not _data.prev_bgm then return end

  local prev_bgm = _data.prev_bgm

  audio_lib.play_bgm(p_name, prev_bgm.name, { start_time = prev_bgm.params.start_time })
end



function audio_lib.stop_bgm(p_name)
  local p_playing = now_playing[p_name]

  if not p_playing or not p_playing.bgm then return end

  local _data = p_playing._data

  p_playing.bgm = nil
  _data.prev_bgm = table.copy(_data.bgm)
  _data.bgm = {}

  minetest.sound_stop(_data.prev_bgm.handle)
end



function audio_lib.reload_music(p_name, old_settings)
  local p_playing = now_playing[p_name]
  if not p_playing then return end

  local p_settings = audio_lib.get_settings(p_name)
  local _data = p_playing._data

  if p_playing.bgm then
    local bgm = _data.bgm
    local params = bgm.params

    minetest.sound_stop(bgm.handle)

    local old_gain

    -- 1. Ottengo il vecchio volume invertendo il calcolo che lo determina (perché
    -- in caso di parametro sovrascritto tramite play_bgm(..), non mi è possibile
    -- ottenerlo da BGM[bgm])
    -- 2. tutti i parametri ~= 0 perché sennò se il volume di prima stava a 0, nella
    -- moltiplicazione in params.gain darebbe sempre 0
    if not params.gain or params.gain == 0 or old_settings.volume_main == 0 or old_settings.volume_bgm == 0 then
      old_gain = 1
    else
      old_gain = params.gain / (old_settings.volume_bgm / 100) / (old_settings.volume_main / 100)
    end

    params.gain = old_gain * (p_settings.volume_bgm / 100) * (p_settings.volume_main / 100)
    params.start_time = (params.start_time or 0) + (minetest.get_us_time() - bgm.started_at) / 1000000
    bgm.handle = minetest.sound_play(bgm.name, params)
  end
end





----------------------------------------------
-----------------GETTERS----------------------
----------------------------------------------

function audio_lib.get_player_bgm(p_name, in_detail)
  local p_playing = now_playing[p_name]
  if not p_playing then return end

  if not in_detail then
    return p_playing.bgm
  else
    return table.copy(p_playing._data.bgm)
  end
end
