/*
* Author:          Patrick-Christopher Mattulat
* Co-Author:       Claude Sonnet 4.6 (LLM)
* Company:         Lynar Studios
* E-Mail:          webmaster@lynarstudios.com
* Created:         2024-05-16
* Changed:         2026-06-23
*
* */

#include <ls-std/core/exception/EventNotHandledException.hpp>
#include <ls-std/event/EventListener.hpp>
#include <ls-std/event/EventManager.hpp>

using ls::standard::core::Class;
using ls::standard::core::EventNotHandledException;
using ls::standard::event::Event;
using ls::standard::event::EventListener;
using ls::standard::event::EventManager;
using ls::standard::event::EventParameter;
using ls::standard::event::type::event_action;
using ls::standard::event::type::event_listeners;
using ls::standard::event::type::listener_id;
using ::std::make_pair;
using ::std::move;
using ::std::shared_ptr;

EventManager::EventManager() : Class("EventManager")
{}

EventManager::~EventManager() noexcept = default;

listener_id EventManager::getNextProvisionId() const
{
  return this->provisionId;
}

bool EventManager::holdsListenerForEvent(const listener_id _id, const Event &_event)
{
  bool holdsListener{};

  if (this->_observesEvent(_event))
  {
    for (const auto &[listener, eventAction] : this->inventory[_event.getClassName()])
    {
      holdsListener = listener->getId() == _id;

      if (holdsListener)
      {
        break;
      }
    }
  }

  return holdsListener;
}

void EventManager::invoke(const Event &_event)
{
  if (!this->_observesEvent(_event))
  {
    throw EventNotHandledException("event " + _event.getClassName() + " is not known by event manager");
  }

  const auto &listeners = this->inventory[_event.getClassName()];
  EventManager::_notifyListeners(listeners);
}

void EventManager::invoke(const Event &_event, const EventParameter &_parameter)
{
  if (!this->_observesEvent(_event))
  {
    throw EventNotHandledException("event " + _event.getClassName() + " is not known by event manager");
  }

  const auto &listeners = this->inventory[_event.getClassName()];
  _giveListenersParameter(listeners, _parameter);
  _notifyListeners(listeners);
}

listener_id EventManager::requestListenerId()
{
  const listener_id providedId = this->provisionId;
  ++this->provisionId;

  return providedId;
}

void EventManager::subscribeListenerForEvent(shared_ptr<EventListener> _listener, const Event &_event, event_action _action)
{
  if (!this->_observesEvent(_event))
  {
    this->inventory[_event.getClassName()] = {};
  }

  const auto inventoryEntry = make_pair<shared_ptr<EventListener>, event_action>(::move(_listener), ::move(_action));
  this->inventory[_event.getClassName()].push_back(inventoryEntry);
}

void EventManager::unsubscribeListenerForEvent(const shared_ptr<EventListener> &_listener, const Event &_event)
{
  if (this->_observesEvent(_event))
  {
    for (auto iterator = this->inventory[_event.getClassName()].begin(); iterator != this->inventory[_event.getClassName()].end(); ++iterator)
    {
      if (iterator->first->getId() == _listener->getId())
      {
        this->inventory[_event.getClassName()].erase(iterator);
        break;
      }
    }
  }
}

void EventManager::_giveListenersParameter(const event_listeners &_listeners, const EventParameter &_parameter)
{
  for (const auto &[listener, action] : _listeners)
  {
    listener->setParameter(_parameter);
  }
}

void EventManager::_notifyListeners(const event_listeners &_listeners)
{
  for (const auto &[listener, eventAction] : _listeners)
  {
    eventAction();
  }
}

bool EventManager::_observesEvent(const Event &_event) const
{
  return this->inventory.find(_event.getClassName()) != this->inventory.end();
}
