// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Mobius Forensic Toolkit
// Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023 Eduardo Aguiar
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 2, or (at your option) any later
// version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
// Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
#include "mediator.h"
#include <cstdint>
#include <mutex>
#include <unordered_map>
#include <vector>

namespace mobius
{
namespace core
{
namespace
{
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Mediator data
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

//! \brief Unordered map entry
struct entry
{
  std::uint64_t uid;
  callback cb;
};

//! \brief Event ID -> entry map
static std::unordered_multimap <std::string, entry> entries_;

//! \brief Subscription ID -> (subscription UID -> event ID)
static std::unordered_map <std::uint64_t, std::string> subscriptions_;

static std::uint64_t next_uid_ = 1;

//! \brief Data mutex
static std::mutex mutex_;

} // namespace

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Subscribe to event
//! \param id Event ID
//! \param c Callback function
//! \return Subscription ID
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
std::uint64_t
subscribe (const std::string& id, const callback& c)
{
  std::lock_guard <std::mutex> lock (mutex_);

  // Add entry to entries map
  entries_.emplace (id, entry {next_uid_, c});

  // Add new subscription
  subscriptions_.emplace (next_uid_, id);

  return next_uid_++;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Unsubscribe from event
//! \param uid Subscription ID
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
unsubscribe (std::uint64_t uid)
{
  std::lock_guard <std::mutex> lock (mutex_);

  // if subscription not found, return
  auto subscription_iter = subscriptions_.find (uid);

  if (subscription_iter == subscriptions_.end ())
    return;
  
  // search for entry with the given uid
  auto id = subscription_iter->second;
  auto [iter, last] = entries_.equal_range (id);

  while (iter != last)
    {
      if (iter->second.uid == uid)
        {
          entries_.erase (iter);
          return;
        }

      ++iter;
    }
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Get callback functions for a given event ID
//! \param id Event ID
//! \return List of callbacks
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
std::vector <callback>
get_callbacks (const std::string& id)
{
  std::vector <callback> callbacks;
  std::lock_guard <std::mutex> lock (mutex_);

  auto [first, last] = entries_.equal_range (id);

  std::for_each (
    first,
    last,
    [&callbacks](decltype (entries_)::value_type& p) { callbacks.push_back (p.second.cb); }
  );

  return callbacks;
}

} // namespace core
} // namespace mobius
