#include "manager.hpp"
#include "menu.hpp"
#include "rules.hpp"
#include "editor.hpp"
#include "game.hpp"
#include <cmath>

/**
 * Définition des variables et fonctions globales internes
 * (accessibles uniquement dans ce fichier)
 */
namespace {
    /**
     * Récupère le nom du niveau dont le fichier est donné
     */
    sf::String getLevelName(Manager& manager, std::string path) {
        // pour ce faire, on crée une instance temporaire de
        // Game que l'on charge avec le chemin donné puis on
        // extrait son nom
        Game temporary_game(manager);

        temporary_game.setPath(path);
        temporary_game.load();

        return temporary_game.getName();
    }
}

Menu::Menu(Manager& manager) : State(manager) {
    background.setTexture(getResourceManager().getTexture("bg_menu.tga"));
    loadMainMenu();
}

Menu::~Menu() {}

void Menu::enable() {
    // attributs de la fenêtre
    getManager().setTitle("");
    getManager().setFramerate(Manager::FPS);

    // joue la musique du menu
    getResourceManager().playMusic("menu.ogg");
}

void Menu::processEvent(const sf::Event& event) {
    // gestion des touches
    if (event.type == sf::Event::KeyPressed) {
        // touche flèche haut : on passe au choix précédent
        if (event.key.code == sf::Keyboard::Up) {
            if (selection == 0) {
                selection = choices.size() - 1;
            } else {
                selection--;
            }
        }

        // touche flèche bas : on passe au choix suivant
        if (event.key.code == sf::Keyboard::Down) {
            if (selection == choices.size() - 1) {
                selection = 0;
            } else {
                selection++;
            }
        }

        // touche entrée ou espace : on exécute le choix sélectionné
        if (event.key.code == sf::Keyboard::Return ||
            event.key.code == sf::Keyboard::Space) {
            actions[selection]();
        }

        // touche retour ou échap : on revient au menu principal
        if (event.key.code == sf::Keyboard::BackSpace ||
            event.key.code == sf::Keyboard::Escape) {
            loadMainMenu();
        }
    }

    // au clic, on exécute le choix pointé s'il y a lieu
    if (event.type == sf::Event::MouseButtonPressed) {
        sf::Vector2f position(event.mouseButton.x, event.mouseButton.y);

        for (unsigned int i = 0; i < labels.size(); i++) {
            if (labels[i].getGlobalBounds().contains(position)) {
                actions[i]();
                return;
            }
        }
    }

    // au déplacement de souris, on sélectionne le choix pointé s'il y a lieu
    if (event.type == sf::Event::MouseMoved) {
        sf::Vector2f position(event.mouseMove.x, event.mouseMove.y);

        for (unsigned int i = 0; i < labels.size(); i++) {
            if (labels[i].getGlobalBounds().contains(position)) {
                selection = i;
            }
        }
    }
}

void Menu::frame() {
    // affichage du menu
    sf::RenderWindow& window = getWindow();
    sf::Vector2f size = (sf::Vector2f) window.getSize();
    sf::Font font = getResourceManager().getFont("raleway.ttf");

    // on s'assure d'être dans la vue par défaut (pas de zoom, 0x0 en haut gauche)
    getManager().useGUIView();

    // dessin du fond
    window.clear(sf::Color::White);

    // on met à l'échelle le fond de telle sorte qu'il soit toujours
    // entièrement visible en largeur
    float bg_scale = size.x / background.getLocalBounds().width;
    background.setScale(bg_scale, bg_scale);

    background.setPosition(sf::Vector2f(
        0, size.y - background.getGlobalBounds().height
    ));

    window.draw(background);

    // on crée les textes pour chaque choix et on les dessine
    float step = size.y / (choices.size() + 1);
    int font_size = std::max((int) step / 3, 12);

    // d'abord un fond pour éviter de mélanger texte et image
    sf::Vector2f mask_position(size.x - (font_size * 10), 0);
    sf::RectangleShape mask(sf::Vector2f(font_size * 10, size.y));
    mask.setPosition(mask_position);
    mask.setFillColor(sf::Color(0, 0, 0, 127));

    window.draw(mask);
    labels.clear();

    for (unsigned int i = 0; i < choices.size(); i++) {
        sf::Text label(choices[i], font, font_size);
        sf::FloatRect text_size = label.getLocalBounds();

        sf::Vector2f base_position(
            size.x - (font_size * 8),
            step * (i + 1)
        );

        label.setColor(sf::Color::White);
        label.setPosition(base_position - sf::Vector2f(
            text_size.left, text_size.top + text_size.height / 2
        ));

        // si c'est le choix sélectionné, on le souligne
        if (selection == i) {
            sf::RectangleShape underline(sf::Vector2f(text_size.width, 2.f));
            underline.setFillColor(sf::Color::White);
            underline.setPosition(base_position + sf::Vector2f(
                0, text_size.height / 2 + 8
            ));

            window.draw(underline);
        }

        labels.push_back(label);
        window.draw(label);
    }

    window.display();
}

void Menu::loadMainMenu() {
    choices.clear();
    actions.clear();
    selection = 0;

    sf::Texture& texture = getResourceManager().getTexture("bg_menu.tga");
    texture.setSmooth(true);
    background.setTexture(texture);

    choices.push_back(sf::String(L"Jouer"));
    actions.push_back(std::bind(&Menu::loadLevelMenu, this));

    choices.push_back(sf::String(L"Règles du jeu"));
    actions.push_back(std::bind(&Menu::launchRules, this));

    choices.push_back(sf::String(L"Éditeur"));
    actions.push_back(std::bind(&Menu::loadEditorMenu, this));

    choices.push_back(sf::String(L"Quitter"));
    actions.push_back(std::bind(&Menu::quit, this));
}

void Menu::loadLevelMenu() {
    choices.clear();
    actions.clear();
    selection = 0;

    std::vector<std::string> path_list = getResourceManager().getLevelList();
    std::vector<std::string> name_list;

    for (auto it = path_list.begin(); it != path_list.end(); it++) {
        choices.push_back(getLevelName(getManager(), *it));
        actions.push_back(std::bind(&Menu::launchGame, this, *it));
    }

    choices.push_back(sf::String(L"Retour"));
    actions.push_back(std::bind(&Menu::loadMainMenu, this));
}

void Menu::loadEditorMenu() {
    choices.clear();
    actions.clear();
    selection = 0;

    std::vector<std::string> path_list = getResourceManager().getLevelList();
    std::vector<std::string> name_list;

    choices.push_back(L"Créer un nouveau niveau");
    actions.push_back(std::bind(&Menu::launchEditor, this, ""));

    for (auto it = path_list.begin(); it != path_list.end(); it++) {
        choices.push_back(getLevelName(getManager(), *it));
        actions.push_back(std::bind(&Menu::launchEditor, this, *it));
    }

    choices.push_back("Retour");
    actions.push_back(std::bind(&Menu::loadMainMenu, this));
}

void Menu::launchGame(std::string path) {
    auto game = std::unique_ptr<Game>(new Game(getManager()));
    game->setPath(path);
    game->load();
    getManager().pushState(std::move(game));
}

void Menu::launchRules() {
    auto rules = std::unique_ptr<Rules>(new Rules(getManager()));
    getManager().pushState(std::move(rules));
}

void Menu::launchEditor(std::string path) {
    auto editor = std::unique_ptr<Editor>(new Editor(getManager()));

    if (!path.empty()) {
        editor->setPath(path);
        editor->load();
    }

    getManager().pushState(std::move(editor));
}

void Menu::quit() {
    getManager().popState();
}
