//  BlockyFroggy
//  Copyright © 2017 John Ryland.
//  All rights reserved.
#pragma once
#ifndef EVENT_SERVICE_H
#define EVENT_SERVICE_H


#include "Common.h"
#include <queue>
#include <mutex>


// Manager to queue and dispatch events
// Uses globally mutable state! Ideally an EventService object is provided to avoid contention
class EventService
{
public:
  // Dispatch all queued events
  static void update(float a_elaspedTime);

  // Queues for dispatch an event
  template <typename ObjectType>
  static void enqueue(const ObjectType& a_obj)
  {
    AutoLock lock(mutex());
    auto obj = std::make_shared<ObjectType>(a_obj);
    queuedEvents().push([obj](){ obj->dispatch(); });
  }

private:
  static std::mutex& mutex();
  static std::queue<std::function<void()>>& queuedEvents();
};


// In GOF terminology, this is an 'Observer'
class EventListener : public Finalizable
{
public:
  template <typename ObjectType, typename ListenerType>
  void registerEventListener(void (ListenerType::*a_memberFunc)(const ObjectType&));
};


// In GOF terminology, this is a 'Subject'
template <typename ObjectType, bool DispatchAcrossThreads = false, bool DispatchAcrossNetworks = false>
class EventObject
{
public:
  bool hasListeners() const
  {
    return !getReceivers().empty();
  }

  // This will auto-deregister when a_obj goes out of scope - the registration has a lifetime linked to a_obj
  static void registerReceiver(EventListener* a_obj, const std::function<void(const ObjectType&)>& a_func)
  {
    auto matcher = [a_obj](ReceiverInfo& obj) -> bool{ return obj.m_object == a_obj; };
    if (std::find_if(getReceivers().begin(), getReceivers().end(), matcher) == std::end(getReceivers()))
    {
      a_obj->addCleanupCallback([a_obj]()
      {
        deregisterReceiver(a_obj);
      });
    }
    else
    {
      // This might happen if the listener is in a class hierachy and different levels of it all want these callbacks
      // This is intended to be supported, but we only need to set up deregistration once.
      Log(LL_Error, "Multiple registations for event receiving by same object for the same event type");
    }
    getReceivers().emplace_back(ReceiverInfo{a_obj, a_func});
  }
  
  // Sends immediately to all receivers in the current thread
  void dispatch() const
  {
    const ObjectType* ptr = static_cast<const ObjectType*>(this);
    for (const auto& receiver : ptr->getReceivers())
    {
      receiver.m_callback(*ptr);
    }
  }

  // TODO: deprecate this, need to remove the global state
  void enqueue() const
  {
    EventService::enqueue(*this);
  }

private:
  struct ReceiverInfo
  {
    EventListener*                         m_object;
    std::function<void(const ObjectType&)> m_callback;
  };

  // TODO: not thread-safe to add / remove receivers or dispatch from different threads, either needs a mutex or use concurrent_vector
  // Perhaps it is just a bad idea to have events across threads anyway, and just not allow it instead. Make all the event based logic
  // on a single thread.
  // Cross-thread events have a number of issues - 
  //     - dispatching an event with multiple listeners that should be on different threads
  //     - the pumping of the event system on the different threads
  //     - solution is very complex - map receivers on different threads with dispatch from different threads for different event types
  // Solution probably depends where it is best to put blocking/contention.
  // One way is at point of invoking the event to put on a central queue which contains a mutex
  // Then this central queue fans out the event to dispatchers on the threads
  // Another is on each thread to have a queue and on invoking the event to add it to this local queue
  // Then the local queues are collated by a central queue and fanned out.
  // But this adds extra latency to event dispatch. It is a trade off between latency and contention.
  // Possibly with lock-free techniques the required queue manipulations might be able to be done lock-free.
  // Perhaps two event systems, a thread-safe, multi-thread one, and a non-threaded one
  // Or perhaps a single-thread one which can pass the event on to a multi-thread dispatcher - a tunnel.
  // This tunnel idea could be extended to pass events from client<->server.

  // Adding template parameters for threaded-dispatch and for network-dispatch

  // Need to add concept of a Thread with a thread context
  // and need to add concept of a Network with Client(s) and Server, so some kind of network id, protocol, contexts

  static std::vector<ReceiverInfo>& getReceivers()
  {
    static std::vector<ReceiverInfo> s_receivers;
    return s_receivers;
  }

  static void deregisterReceiver(EventListener* a_obj)
  {
    auto matcher = [a_obj](ReceiverInfo& obj) -> bool{ return obj.m_object == a_obj; };
    getReceivers().erase(std::remove_if(getReceivers().begin(), getReceivers().end(), matcher), getReceivers().end());
  }
};


template <typename ObjectType, typename ListenerType>
void EventListener::registerEventListener(void (ListenerType::*a_memberFunc)(const ObjectType&))
{
  ListenerType* _this = static_cast<ListenerType*>(this);
  EventObject<ObjectType>::registerReceiver(_this, std::bind(a_memberFunc, _this, std::placeholders::_1));
}


// A single-threaded event, only to be used to send/recieve on the same thread
#define DECLARE_EVENT(EventName) \
  class EventName : public EventObject<EventName> \
  { \
  };


// TODO: Proposed - possibly macro name to change
// An event that can cross thread boundaries
#define DECLARE_EVENT_MT(EventName) \
  class EventName : public EventObject<EventName, true, false> \
  { \
  };


// TODO: Proposed - possibly macro name to change
// An event to be sent over a network socket 
#define DECLARE_ACTION(EventName) \
  class EventName : public EventObject<EventName, false, true> \
  { \
  };


#endif // EVENT_SERVICE_H

