local map = ...
local game = map:get_game()

-- Calls sol.audio.<name>(...) prepending context if it is not false.
-- context is one of: false (no context argument), sol.main, game or map.
local function call_audio(name, context, ...)
  local args = {...}
  if context then
    table.insert(args, 1, context)
  end
  return sol.audio[name](unpack(args))
end

-- The 4 ways of calling sol.audio functions: omitting the context
-- (the common case, using the default sol.main context implicitly),
-- and passing each of the 3 possible explicit contexts.
local contexts = {
  { "default", false },
  { "sol.main", sol.main },
  { "game", game },
  { "map", map },
}

local function test_sound_volume()
  local initial_volume = sol.audio.get_sound_volume()
  assert(type(initial_volume) == "number")

  sol.audio.set_sound_volume(50)
  assert_equal(sol.audio.get_sound_volume(), 50)

  -- Volume is clamped to [0, 100].
  sol.audio.set_sound_volume(150)
  assert_equal(sol.audio.get_sound_volume(), 100)
  sol.audio.set_sound_volume(-10)
  assert_equal(sol.audio.get_sound_volume(), 0)

  sol.audio.set_sound_volume(initial_volume)
end

local function test_music_volume()
  local initial_volume = sol.audio.get_music_volume()
  assert(type(initial_volume) == "number")

  sol.audio.set_music_volume(60)
  assert_equal(sol.audio.get_music_volume(), 60)

  -- Volume is clamped to [0, 100].
  sol.audio.set_music_volume(150)
  assert_equal(sol.audio.get_music_volume(), 100)
  sol.audio.set_music_volume(-10)
  assert_equal(sol.audio.get_music_volume(), 0)

  sol.audio.set_music_volume(initial_volume)
end

local function test_preload_sounds()
  -- Deprecated function: should just do nothing (and not error).
  sol.audio.preload_sounds()
end

local function test_play_sound()
  for _, entry in ipairs(contexts) do
    local name, context = entry[1], entry[2]
    call_audio("play_sound", context, "cursor")
  end
end

-- Regression test for the crash reported against sol.audio.get_music():
-- calling any sol.audio function about the current music without an
-- explicit context argument (the normal, most common usage) used to
-- crash the engine. There were two separate bugs: the context-handling
-- code called lua_remove() on an empty Lua stack when no context
-- argument was passed at all, and the "no music playing" case
-- dereferenced a null music pointer. Neither must crash, in any context.
local function test_no_music_playing()
  for _, entry in ipairs(contexts) do
    local name, context = entry[1], entry[2]

    assert_equal(call_audio("get_music", context), nil, name)
    assert_equal(call_audio("get_music_format", context), nil, name)
    assert_equal(call_audio("get_music_num_channels", context), nil, name)
    assert_equal(call_audio("get_music_channel_volume", context, 0), nil, name)
    assert_equal(call_audio("get_music_tempo", context), nil, name)
    assert_equal(call_audio("set_music_channel_volume", context, 0, 50), false, name)
    assert_equal(call_audio("set_music_tempo", context, 120), false, name)
  end
end

-- "missing_music" and "missing_music_2" are music resources declared in
-- project_db.dat on purpose without a backing file: play_music() must
-- never actually need to load the file to do its id bookkeeping (and
-- test runs use -no-audio anyway, where no music is ever really loaded).
local function test_play_music_get_music_stop_music()
  for _, entry in ipairs(contexts) do
    local name, context = entry[1], entry[2]

    assert_equal(call_audio("get_music", context), nil, name)

    call_audio("play_music", context, "missing_music")
    assert_equal(call_audio("get_music", context), "missing_music", name)

    -- Playing the same music again is a no-op: it keeps playing,
    -- it does not restart (and does not error).
    call_audio("play_music", context, "missing_music")
    assert_equal(call_audio("get_music", context), "missing_music", name)

    -- The special value "same" keeps the current music unchanged.
    call_audio("play_music", context, "same")
    assert_equal(call_audio("get_music", context), "missing_music", name)

    -- Switching to a different music replaces the current one.
    call_audio("play_music", context, "missing_music_2")
    assert_equal(call_audio("get_music", context), "missing_music_2", name)

    -- stop_music() stops it.
    call_audio("stop_music", context)
    assert_equal(call_audio("get_music", context), nil, name)

    -- play_music("none") is equivalent to stop_music().
    call_audio("play_music", context, "missing_music")
    assert_equal(call_audio("get_music", context), "missing_music", name)
    call_audio("play_music", context, "none")
    assert_equal(call_audio("get_music", context), nil, name)

    -- "same" with no music playing keeps doing nothing.
    call_audio("play_music", context, "same")
    assert_equal(call_audio("get_music", context), nil, name)
  end
end

-- Passing nil explicitly as the music id also stops the music.
-- (Not routed through call_audio() because a literal nil in the middle
-- of a Lua argument list needs an explicit call per context.)
local function test_play_music_nil_stops()
  sol.audio.play_music("missing_music")
  assert_equal(sol.audio.get_music(), "missing_music")
  sol.audio.play_music(nil)
  assert_equal(sol.audio.get_music(), nil)

  sol.audio.play_music(sol.main, "missing_music")
  assert_equal(sol.audio.get_music(sol.main), "missing_music")
  sol.audio.play_music(sol.main, nil)
  assert_equal(sol.audio.get_music(sol.main), nil)

  sol.audio.play_music(game, "missing_music")
  assert_equal(sol.audio.get_music(game), "missing_music")
  sol.audio.play_music(game, nil)
  assert_equal(sol.audio.get_music(game), nil)

  sol.audio.play_music(map, "missing_music")
  assert_equal(sol.audio.get_music(map), "missing_music")
  sol.audio.play_music(map, nil)
  assert_equal(sol.audio.get_music(map), nil)
end

-- The action parameter (loop boolean or finish callback) must be
-- accepted without error, with and without an explicit context.
local function test_play_music_action_parameter()
  sol.audio.stop_music()
  sol.audio.stop_music(game)

  sol.audio.play_music("missing_music", true)
  assert_equal(sol.audio.get_music(), "missing_music")
  sol.audio.stop_music()

  sol.audio.play_music("missing_music", false)
  assert_equal(sol.audio.get_music(), "missing_music")
  sol.audio.stop_music()

  sol.audio.play_music("missing_music", function() end)
  assert_equal(sol.audio.get_music(), "missing_music")
  sol.audio.stop_music()

  sol.audio.play_music(game, "missing_music", false)
  assert_equal(sol.audio.get_music(game), "missing_music")
  sol.audio.stop_music(game)
end

-- The 3 audio contexts (sol.main, game, map) each track their own
-- current music independently.
local function test_context_independence()
  sol.audio.play_music(sol.main, "missing_music")
  sol.audio.play_music(game, "missing_music_2")
  sol.audio.stop_music(map)

  assert_equal(sol.audio.get_music(sol.main), "missing_music")
  assert_equal(sol.audio.get_music(game), "missing_music_2")
  assert_equal(sol.audio.get_music(map), nil)

  sol.audio.stop_music(sol.main)
  sol.audio.stop_music(game)
end

test_sound_volume()
test_music_volume()
test_preload_sounds()
test_play_sound()
test_no_music_playing()
test_play_music_get_music_stop_music()
test_play_music_nil_stops()
test_play_music_action_parameter()
test_context_independence()

sol.main.exit()
