#include "SelectionController.h"

#include <algorithm>
#include <memory>

#include "DebrisSelectionChangedEvent.h"
#include "EntitySelectionChangedEvent.h"
#include "EventManager.h"
#include "SelectionChangedEvent.h"

namespace
{

template <typename T>
bool contains(const std::vector<T>& items, const T& item)
{
    return std::find(items.begin(), items.end(), item) != items.end();
}

// Applies `hits` to `selection` per `mode`. Replace is handled by the caller so
// that an empty hit list can mean "clear this category" there but "leave this
// category alone" here.
template <typename T>
void combine(std::vector<T>& selection, const std::vector<T>& hits, SelectionMode mode)
{
    for (const T& hit : hits)
    {
        const typename std::vector<T>::iterator it =
            std::find(selection.begin(), selection.end(), hit);
        if (it == selection.end())
        {
            selection.push_back(hit);
        }
        else if (mode == SelectionMode::Toggle)
        {
            // Only a toggle removes; an additive box drag never deselects.
            selection.erase(it);
        }
    }
}

}  // namespace

const std::vector<BuildingId>& SelectionController::getSelectedBuildings() const
{
    return m_buildings;
}

const std::vector<entt::entity>& SelectionController::getSelectedActors() const
{
    return m_actors;
}

const std::vector<entt::entity>& SelectionController::getSelectedDebris() const
{
    return m_debris;
}

bool SelectionController::isActorSelected(entt::entity actor) const
{
    return contains(m_actors, actor);
}

bool SelectionController::isDebrisSelected(entt::entity debris) const
{
    return contains(m_debris, debris);
}

void SelectionController::selectBuildings(const std::vector<BuildingId>& ids,
                                          SelectionMode mode)
{
    // Buildings win over field objects (REQ-UI-SELECTION-CATEGORIES).
    if (clearActorsQuietly()) { publishActors(); }
    if (clearDebrisQuietly()) { publishDebris(); }

    if (mode == SelectionMode::Replace)
    {
        m_buildings = ids;
    }
    else
    {
        combine(m_buildings, ids, mode);
    }
    publishBuildings();
}

void SelectionController::selectFieldObjects(const std::vector<entt::entity>& actors,
                                             const std::vector<entt::entity>& debris,
                                             SelectionMode mode)
{
    if (clearBuildingsQuietly()) { publishBuildings(); }

    if (mode == SelectionMode::Replace)
    {
        m_actors = actors;
        m_debris = debris;
    }
    else
    {
        combine(m_actors, actors, mode);
        combine(m_debris, debris, mode);
    }
    publishActors();
    publishDebris();
}

void SelectionController::clearAll()
{
    if (clearBuildingsQuietly()) { publishBuildings(); }
    if (clearActorsQuietly())    { publishActors(); }
    if (clearDebrisQuietly())    { publishDebris(); }
}

void SelectionController::setSelectedActors(std::vector<entt::entity> actors)
{
    if (actors == m_actors) { return; }
    m_actors = std::move(actors);
    publishActors();
}

void SelectionController::setSelectedDebris(std::vector<entt::entity> debris)
{
    if (debris == m_debris) { return; }
    m_debris = std::move(debris);
    publishDebris();
}

bool SelectionController::clearBuildingsQuietly()
{
    if (m_buildings.empty()) { return false; }
    m_buildings.clear();
    return true;
}

bool SelectionController::clearActorsQuietly()
{
    if (m_actors.empty()) { return false; }
    m_actors.clear();
    return true;
}

bool SelectionController::clearDebrisQuietly()
{
    if (m_debris.empty()) { return false; }
    m_debris.clear();
    return true;
}

void SelectionController::publishBuildings() const
{
    EventManager::getInstance()->sendEventImmediately(
        std::make_shared<SelectionChangedEvent>(m_buildings));
}

void SelectionController::publishActors() const
{
    EventManager::getInstance()->sendEventImmediately(
        std::make_shared<EntitySelectionChangedEvent>(m_actors));
}

void SelectionController::publishDebris() const
{
    EventManager::getInstance()->sendEventImmediately(
        std::make_shared<DebrisSelectionChangedEvent>(m_debris));
}
