// audio_output.cpp - Audio output implementation for Lua sandbox
// Milestone 13: Safe audio playback with volume limits and concurrent sound management

#include "audio_output.h"
#include <algorithm>
#include <cmath>

extern "C" {
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
}

namespace mosis {

// ============================================================================
// SoundPlayer Implementation
// ============================================================================

SoundPlayer::SoundPlayer(int id, const AudioData& audio, const PlaybackConfig& config)
    : m_id(id)
    , m_audio(audio)
    , m_volume(std::clamp(config.volume, 0.0f, 1.0f))
    , m_pitch(std::clamp(config.pitch, 0.5f, 2.0f))
    , m_pan(std::clamp(config.pan, -1.0f, 1.0f))
    , m_loop(config.loop)
    , m_position(0.0f)
{
    m_state = AudioState::Playing;
}

SoundPlayer::~SoundPlayer() {
    Stop();
}

void SoundPlayer::Play() {
    std::lock_guard<std::mutex> lock(m_mutex);
    if (m_state != AudioState::Playing) {
        m_state = AudioState::Playing;
    }
}

void SoundPlayer::Pause() {
    std::lock_guard<std::mutex> lock(m_mutex);
    if (m_state == AudioState::Playing) {
        m_state = AudioState::Paused;
    }
}

void SoundPlayer::Stop() {
    std::lock_guard<std::mutex> lock(m_mutex);
    m_state = AudioState::Stopped;
    m_position = 0.0f;
}

void SoundPlayer::SetVolume(float volume) {
    std::lock_guard<std::mutex> lock(m_mutex);
    m_volume = std::clamp(volume, 0.0f, 1.0f);
}

void SoundPlayer::SetPitch(float pitch) {
    std::lock_guard<std::mutex> lock(m_mutex);
    m_pitch = std::clamp(pitch, 0.5f, 2.0f);
}

void SoundPlayer::SetPan(float pan) {
    std::lock_guard<std::mutex> lock(m_mutex);
    m_pan = std::clamp(pan, -1.0f, 1.0f);
}

void SoundPlayer::SimulateEnd() {
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        if (m_loop) {
            m_position = 0.0f;
            return;
        }
        m_state = AudioState::Stopped;
        m_position = 1.0f;
    }
    if (m_on_end) {
        m_on_end();
    }
}

// ============================================================================
// AudioOutputInterface Implementation
// ============================================================================

AudioOutputInterface::AudioOutputInterface(const std::string& app_id)
    : m_app_id(app_id)
{
}

AudioOutputInterface::~AudioOutputInterface() {
    Shutdown();
}

std::shared_ptr<SoundPlayer> AudioOutputInterface::Play(const AudioData& audio, const PlaybackConfig& config, std::string& error) {
    std::lock_guard<std::mutex> lock(m_mutex);

    // Clean up stopped players first
    CleanupStoppedPlayers();

    // Check concurrent limit
    if (m_players.size() >= MAX_CONCURRENT_SOUNDS) {
        error = "Maximum concurrent sounds limit reached (10)";
        return nullptr;
    }

    // Validate audio data
    if (audio.data.empty()) {
        error = "Audio data is empty";
        return nullptr;
    }

    // Create player
    int id = m_next_player_id++;
    auto player = std::make_shared<SoundPlayer>(id, audio, config);

    // Set up cleanup callback
    std::weak_ptr<SoundPlayer> weak_player = player;
    player->SetOnEnd([this, id]() {
        // Player ended, it will be cleaned up on next Play() call
    });

    m_players[id] = player;
    return player;
}

void AudioOutputInterface::StopPlayer(int player_id) {
    std::lock_guard<std::mutex> lock(m_mutex);
    auto it = m_players.find(player_id);
    if (it != m_players.end()) {
        it->second->Stop();
        m_players.erase(it);
    }
}

void AudioOutputInterface::StopAll() {
    std::lock_guard<std::mutex> lock(m_mutex);
    for (auto& [id, player] : m_players) {
        player->Stop();
    }
    m_players.clear();
}

size_t AudioOutputInterface::GetActivePlayerCount() const {
    std::lock_guard<std::mutex> lock(m_mutex);
    size_t count = 0;
    for (const auto& [id, player] : m_players) {
        if (player->GetState() != AudioState::Stopped) {
            count++;
        }
    }
    return count;
}

void AudioOutputInterface::SetSystemVolume(float volume) {
    m_system_volume = std::clamp(volume, 0.0f, 1.0f);
}

void AudioOutputInterface::Shutdown() {
    StopAll();
}

std::shared_ptr<SoundPlayer> AudioOutputInterface::GetPlayer(int id) {
    std::lock_guard<std::mutex> lock(m_mutex);
    auto it = m_players.find(id);
    if (it != m_players.end()) {
        return it->second;
    }
    return nullptr;
}

void AudioOutputInterface::CleanupStoppedPlayers() {
    // Called with lock held
    for (auto it = m_players.begin(); it != m_players.end();) {
        if (it->second->GetState() == AudioState::Stopped) {
            it = m_players.erase(it);
        } else {
            ++it;
        }
    }
}

// ============================================================================
// Lua API Implementation
// ============================================================================

// Weak reference to player stored in userdata
struct LuaPlayerRef {
    std::weak_ptr<SoundPlayer> player;
    AudioOutputInterface* audio;
};

static const char* PLAYER_METATABLE = "mosis.SoundPlayer";

static LuaPlayerRef* GetPlayerRef(lua_State* L, int idx) {
    return static_cast<LuaPlayerRef*>(luaL_checkudata(L, idx, PLAYER_METATABLE));
}

static std::shared_ptr<SoundPlayer> GetPlayer(lua_State* L, int idx) {
    auto ref = GetPlayerRef(L, idx);
    return ref->player.lock();
}

// Player methods

static int LuaPlayer_pause(lua_State* L) {
    auto player = GetPlayer(L, 1);
    if (player) {
        player->Pause();
    }
    return 0;
}

static int LuaPlayer_resume(lua_State* L) {
    auto player = GetPlayer(L, 1);
    if (player) {
        player->Play();
    }
    return 0;
}

static int LuaPlayer_stop(lua_State* L) {
    auto ref = GetPlayerRef(L, 1);
    auto player = ref->player.lock();
    if (player && ref->audio) {
        ref->audio->StopPlayer(player->GetId());
    }
    return 0;
}

static int LuaPlayer_setVolume(lua_State* L) {
    auto player = GetPlayer(L, 1);
    if (player) {
        float volume = static_cast<float>(luaL_checknumber(L, 2));
        player->SetVolume(volume);
    }
    return 0;
}

static int LuaPlayer_setPitch(lua_State* L) {
    auto player = GetPlayer(L, 1);
    if (player) {
        float pitch = static_cast<float>(luaL_checknumber(L, 2));
        player->SetPitch(pitch);
    }
    return 0;
}

static int LuaPlayer_setPan(lua_State* L) {
    auto player = GetPlayer(L, 1);
    if (player) {
        float pan = static_cast<float>(luaL_checknumber(L, 2));
        player->SetPan(pan);
    }
    return 0;
}

static int LuaPlayer_getState(lua_State* L) {
    auto player = GetPlayer(L, 1);
    if (!player) {
        lua_pushstring(L, "stopped");
        return 1;
    }

    switch (player->GetState()) {
        case AudioState::Playing:
            lua_pushstring(L, "playing");
            break;
        case AudioState::Paused:
            lua_pushstring(L, "paused");
            break;
        case AudioState::Stopped:
        default:
            lua_pushstring(L, "stopped");
            break;
    }
    return 1;
}

static int LuaPlayer_getPosition(lua_State* L) {
    auto player = GetPlayer(L, 1);
    if (player) {
        lua_pushnumber(L, player->GetPosition());
    } else {
        lua_pushnumber(L, 0.0);
    }
    return 1;
}

static int LuaPlayer_gc(lua_State* L) {
    auto ref = GetPlayerRef(L, 1);
    ref->~LuaPlayerRef();
    return 0;
}

static const luaL_Reg player_methods[] = {
    {"pause", LuaPlayer_pause},
    {"resume", LuaPlayer_resume},
    {"stop", LuaPlayer_stop},
    {"setVolume", LuaPlayer_setVolume},
    {"setPitch", LuaPlayer_setPitch},
    {"setPan", LuaPlayer_setPan},
    {"getState", LuaPlayer_getState},
    {"getPosition", LuaPlayer_getPosition},
    {nullptr, nullptr}
};

static void CreatePlayerMetatable(lua_State* L) {
    luaL_newmetatable(L, PLAYER_METATABLE);

    // __index = methods table
    lua_newtable(L);
    luaL_setfuncs(L, player_methods, 0);
    lua_setfield(L, -2, "__index");

    // __gc
    lua_pushcfunction(L, LuaPlayer_gc);
    lua_setfield(L, -2, "__gc");

    lua_pop(L, 1);
}

static void PushPlayer(lua_State* L, std::shared_ptr<SoundPlayer> player, AudioOutputInterface* audio) {
    auto ref = static_cast<LuaPlayerRef*>(lua_newuserdata(L, sizeof(LuaPlayerRef)));
    new (ref) LuaPlayerRef{player, audio};
    luaL_setmetatable(L, PLAYER_METATABLE);
}

// audio.play(soundData, config)
static int LuaAudio_play(lua_State* L) {
    // Get audio interface from upvalue
    auto audio = static_cast<AudioOutputInterface*>(lua_touserdata(L, lua_upvalueindex(1)));
    if (!audio) {
        lua_pushnil(L);
        lua_pushstring(L, "Audio interface not available");
        return 2;
    }

    // Parse audio data from first argument (table)
    AudioData audioData;
    if (lua_istable(L, 1)) {
        lua_getfield(L, 1, "data");
        if (lua_isstring(L, -1)) {
            size_t len;
            const char* data = lua_tolstring(L, -1, &len);
            audioData.data.assign(data, data + len);
        }
        lua_pop(L, 1);

        lua_getfield(L, 1, "sampleRate");
        if (lua_isnumber(L, -1)) {
            audioData.sample_rate = static_cast<int>(lua_tointeger(L, -1));
        }
        lua_pop(L, 1);

        lua_getfield(L, 1, "channels");
        if (lua_isnumber(L, -1)) {
            audioData.channels = static_cast<int>(lua_tointeger(L, -1));
        }
        lua_pop(L, 1);

        lua_getfield(L, 1, "bitsPerSample");
        if (lua_isnumber(L, -1)) {
            audioData.bits_per_sample = static_cast<int>(lua_tointeger(L, -1));
        }
        lua_pop(L, 1);
    }

    // Parse playback config from second argument (optional table)
    PlaybackConfig config;
    if (lua_istable(L, 2)) {
        lua_getfield(L, 2, "volume");
        if (lua_isnumber(L, -1)) {
            config.volume = static_cast<float>(lua_tonumber(L, -1));
        }
        lua_pop(L, 1);

        lua_getfield(L, 2, "loop");
        if (lua_isboolean(L, -1)) {
            config.loop = lua_toboolean(L, -1);
        }
        lua_pop(L, 1);

        lua_getfield(L, 2, "pitch");
        if (lua_isnumber(L, -1)) {
            config.pitch = static_cast<float>(lua_tonumber(L, -1));
        }
        lua_pop(L, 1);

        lua_getfield(L, 2, "pan");
        if (lua_isnumber(L, -1)) {
            config.pan = static_cast<float>(lua_tonumber(L, -1));
        }
        lua_pop(L, 1);
    }

    std::string error;
    auto player = audio->Play(audioData, config, error);

    if (!player) {
        lua_pushnil(L);
        lua_pushstring(L, error.c_str());
        return 2;
    }

    PushPlayer(L, player, audio);
    return 1;
}

// audio.stopAll()
static int LuaAudio_stopAll(lua_State* L) {
    auto audio = static_cast<AudioOutputInterface*>(lua_touserdata(L, lua_upvalueindex(1)));
    if (audio) {
        audio->StopAll();
    }
    return 0;
}

// audio.getActiveCount()
static int LuaAudio_getActiveCount(lua_State* L) {
    auto audio = static_cast<AudioOutputInterface*>(lua_touserdata(L, lua_upvalueindex(1)));
    if (audio) {
        lua_pushinteger(L, static_cast<lua_Integer>(audio->GetActivePlayerCount()));
    } else {
        lua_pushinteger(L, 0);
    }
    return 1;
}

// Helper to set a global in the real _G (bypassing any proxy)
static void SetGlobalInRealG(lua_State* L, const char* name) {
    lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS);

    if (lua_getmetatable(L, -1)) {
        lua_getfield(L, -1, "__index");
        if (lua_istable(L, -1)) {
            lua_pushvalue(L, -4);
            lua_setfield(L, -2, name);
            lua_pop(L, 4);
            return;
        }
        lua_pop(L, 2);
    }

    lua_pushvalue(L, -2);
    lua_setfield(L, -2, name);
    lua_pop(L, 2);
}

void RegisterAudioAPI(lua_State* L, AudioOutputInterface* audio) {
    // Create player metatable
    CreatePlayerMetatable(L);

    // Create audio table
    lua_newtable(L);

    // audio.play
    lua_pushlightuserdata(L, audio);
    lua_pushcclosure(L, LuaAudio_play, 1);
    lua_setfield(L, -2, "play");

    // audio.stopAll
    lua_pushlightuserdata(L, audio);
    lua_pushcclosure(L, LuaAudio_stopAll, 1);
    lua_setfield(L, -2, "stopAll");

    // audio.getActiveCount
    lua_pushlightuserdata(L, audio);
    lua_pushcclosure(L, LuaAudio_getActiveCount, 1);
    lua_setfield(L, -2, "getActiveCount");

    // Set as global (bypasses sandbox proxy)
    SetGlobalInRealG(L, "audio");
}

} // namespace mosis
