#include "LobbyState.hpp"

#include "ClientArgs.hpp"
#include "imgui.h"

#include <cstdio>

namespace tw::app {

namespace {

const ImVec4 FAVOURITES_COLOUR{1.0f, 0.8f, 0.2f, 1.0f};
const ImVec4 RECENT_COLOUR{0.7f, 0.7f, 0.7f, 1.0f};

}

LobbyState::LobbyState(std::optional<tw::net::Address> auto_connect) {
    m_recent.load();
    m_favourites.load();

    set_address_input(m_recent.entries().empty()
        ? "127.0.0.1:8080"
        : m_recent.entries().front());

    if(auto_connect) {
        begin_connect(*auto_connect);
    }
}

void LobbyState::set_address_input(const std::string& address) {
    std::snprintf(m_address_input, sizeof(m_address_input), "%s", address.c_str());
}

void LobbyState::begin_connect(tw::net::Address address) {
    m_error.clear();
    m_connection = std::make_unique<tw::net::ServerConnection>(address);

    auto started = m_connection->start();
    if(!started) {
        m_error = started.error().message();
        m_connection.reset();
    }
}

void LobbyState::draw_form() {
    const float input_width = 200.0f;

    ImGui::SetNextItemWidth(input_width);
    bool submitted = ImGui::InputText("##address", m_address_input, sizeof(m_address_input),
                                      ImGuiInputTextFlags_EnterReturnsTrue);

    ImGui::SameLine();
    submitted |= ImGui::Button("Connect");

    if(!submitted) {
        return;
    }

    auto parsed = parse_address(m_address_input);
    if(parsed) {
        begin_connect(*parsed);
    } else {
        m_error = parsed.error();
    }
}

void LobbyState::draw_favourite_toggle(const std::string& entry) {
    const char* label = m_favourites.contains(entry) ? "[*]" : "[ ]";

    // The label alone would collide between the rows drawn in one frame, so the
    // entry it acts on is what identifies the button.
    std::string button_id = std::string(label) + "##fav_" + entry;

    if(ImGui::SmallButton(button_id.c_str())) {
        toggle_favourite(entry);
    }
}

void LobbyState::toggle_favourite(const std::string& entry) {
    if(m_favourites.contains(entry)) {
        m_favourites.remove(entry);
    } else {
        m_favourites.add(entry);
    }
    m_favourites.save();
}

void LobbyState::draw_provider(ServerAddressProvider& provider, const ImVec4& header_colour) {
    ImGui::PushStyleColor(ImGuiCol_Text, header_colour);
    ImGui::SeparatorText(provider.name());
    ImGui::PopStyleColor();

    // BeginChild is one of the two calls whose End must run even when it
    // returns false, so the result only decides whether rows are submitted.
    if(ImGui::BeginChild(provider.name(), ImVec2(0, 120), ImGuiChildFlags_Borders)) {
        for(const auto& entry : provider.entries()) {
            draw_favourite_toggle(entry);
            ImGui::SameLine();

            if(ImGui::Selectable(entry.c_str(), false)) {
                set_address_input(entry);
            }

            if(ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) {
                auto parsed = parse_address(entry);
                if(parsed) {
                    begin_connect(*parsed);
                }
            }
        }
    }

    ImGui::EndChild();
}

void LobbyState::draw_status() {
    if(m_connection) {
        switch(m_connection->status()) {
            case tw::net::ConnectionStatus::Idle:
                break;
            case tw::net::ConnectionStatus::Connecting: {
                ImGui::TextUnformatted("Connecting...");
                ImGui::SameLine();
                draw_favourite_toggle(m_connection->address().to_string());
                ImGui::SameLine();
                if(ImGui::Button("Cancel")) {
                    m_connection.reset();
                }
                break;
            }
            case tw::net::ConnectionStatus::Connected:
                ImGui::TextColored(ImVec4(0, 1, 0, 1), "Connected!");
                m_recent.add(m_connection->address().to_string());
                m_recent.save();
                m_result = LobbyResult{std::move(m_connection)};
                break;
            case tw::net::ConnectionStatus::Failed: {
                std::string failed_msg = "Connection failed: " + m_connection->error();
                ImGui::TextColored(ImVec4(1, 0, 0, 1), "%s", failed_msg.c_str());
                if(ImGui::Button("Dismiss")) {
                    m_connection.reset();
                }
                break;
            }
        }
    } else if(!m_error.empty()) {
        std::string error_msg = "Error: " + m_error;
        ImGui::TextColored(ImVec4(1, 0, 0, 1), "%s", error_msg.c_str());
    }
}

void LobbyState::update(double delta_time) {
    if(m_connection) {
        m_connection->update();
    }

    // Center the window
    ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_FirstUseEver,
                            ImVec2(0.5f, 0.5f));
    ImGui::SetNextWindowSize(ImVec2(400, 0), ImGuiCond_FirstUseEver);

    ImGui::Begin("Lobby", nullptr, ImGuiWindowFlags_NoMove);

    draw_form();
    draw_provider(m_favourites, FAVOURITES_COLOUR);
    draw_provider(m_recent, RECENT_COLOUR);
    ImGui::Separator();
    draw_status();

    ImGui::End();
}

std::optional<LobbyResult> LobbyState::take_result() {
    // Moving out of an optional leaves it engaged, which would hand the caller
    // a second, empty result on the next frame.
    auto result = std::move(m_result);
    m_result.reset();

    return result;
}

} // namespace tw::app
