//
//  EventService.cpp
//  BlockyFroggy
//
//  Created by John Ryland on 26/5/17.
//  Copyright © 2017 John Ryland. All rights reserved.
//


////////////// INCLUDES ////////////////////////////
#include "EventService.h"
#include "Common.h"


/*!
////////////// DOCUMENTATION ///////////////////////
@page Topics
@subpage EventSystem
@page EventSystem Event System


The event system is made up of three main types of things
  - Subjects / Event Objects
  - Observers / Event Listeners
  - Event Service

Event Objects are a class type which can have associated with them data. The class type is the
type of event, and the optional data contains any associated information or parameters about
the event.

An example of an EventObject:

```
class ExampleEvent : public EventObject<ExampleEvent>
{
public:
  uint32_t m_val1 = 1;
  uint32_t m_val2 = 2;
  uint32_t m_val3 = 3;
};
```

Note the use of CRTP (The curiously recurring template pattern).
Having members is optional, so this is possible too:

```
class GameStartedEvent : public EventObject<GameStartedEvent>
{
};
```

Possibly a macro could simplify this such as:

```
DECLARE_EVENT(GameStartedEvent)
```

Then to recieve these events, a listener / observer is needed. To
do this, inherit from EventListener and register to receieve the
events. For example:

```
class ExampleObserver : public EventListener
{
public:
  ExampleObserver()
  {
    registerEventListener(&ExampleObserver::gameStarted);
  }

  void gameStarted(const GameStartedEvent& a_obj)
  {
    Log(LL_Debug, "game started");
  }
};
```

This works because the EventListener class can match the event type
from the parameter to the gameStarted function.

Finally we need a way to invoke these events. There are two ways to
do this. The first is directly which will invoke the listener
immediately in the same thread of execution potentially blocking while
it executes. The second is to enqueue the event to be handled later
during an update. For this second method to work, the event queue must
be pumped.

First example is using the immediate dispatch method:

```
void beginGame()
{
  GameStartedEvent startedEvent;
  startedEvent.dispatch();
}
```

The second example shows the enqueuing method and pumping the event loop.

```
void beginGame()
{
  GameStartedEvent startedEvent;
  // Add to the event queue
  EventService::enqueue(startedEvent);
}

void updateLoop()
{
  // pump the event queue
  EventService::update(100);
}
```
*/


////////////// IMPLEMENTATION //////////////////////
void EventService::update(float a_elaspedTime)
{
  AutoLock lock(mutex());
  while (!queuedEvents().empty())
  {
    queuedEvents().front()();
    queuedEvents().pop();
  }
}


std::mutex& EventService::mutex()
{
  static std::mutex s_mutex;
  return s_mutex;
}


std::queue<std::function<void()>>& EventService::queuedEvents()
{
  static std::queue<std::function<void()>> s_queuedEvents;
  return s_queuedEvents;
}


////////////// UNIT TESTS //////////////////////////
#if ENABLE_UNIT_TESTS


class ExampleEventObject : public EventObject<ExampleEventObject>
{
public:
  uint32_t m_val1 = 1;
  uint32_t m_val2 = 2;
  uint32_t m_val3 = 3;
};


class ExampleReceiver : public EventListener
{
public:
  ExampleReceiver() {
    registerEventListener(&ExampleReceiver::receiveExampleEvent);
  }

  void receiveExampleEvent(const ExampleEventObject& a_obj) {
    Log(LL_Debug, "receive test worked");
  }
};


class ExampleReceiverSubClass : public ExampleReceiver
{
public:
  ExampleReceiverSubClass() {
    registerEventListener(&ExampleReceiverSubClass::receiveExampleEventSubClass);
  }

  void receiveExampleEventSubClass(const ExampleEventObject& a_obj) {
    Log(LL_Debug, "receive test worked from sub-class");
  }
};


DECLARE_UNIT_TEST(unitTestEventService)
{
  ExampleEventObject obj;
  obj.m_val3 = 5;
  
  ExampleReceiverSubClass rec;
  
  obj.dispatch();

  EventService::enqueue(obj);
  obj.m_val1 = 2;
  
  EventService::update(100);

  CHECK(true);
}


#endif // ENABLE_UNIT_TESTS

