#pragma once


#include "Network/GameActionsChannel.h"
#include "EventSystem/NetworkEvents.h"


/*
  Everything in GameSession.h is intended to be symetrically usable on both the client and server,
  so there shouldn't be anything client or server specific in here.

  It is therefore designed to be subclassed in to a ServerSession and ClientSession and
  each instantiated as appropriate in the client and server. This base class to these two cases
  provides a consistent way for binding together things like event, actions and the simulation
  in to a whole context. The server can then have multiple of these contexts for each client
  connection. The client is expected to typically only need to have one of these contexts, however
  it is good practise to avoid singletons anyway and it's not that hard to do.
*/


// Forward declared so it can be defined in a game specific way in the game code.
class CoreGameData;


class BaseTrackingEvent
{
public:
  // I believe this is the same information required to make either a GLOT or GList tracking event, but otherwise this may need extending
  uint32_t m_type;
  uint32_t m_eventId;
  rapidjson::Document m_eventData;
};


class SessionTrackingInterface
{
public:
  virtual void SendTrackingEvent(const BaseTrackingEvent& a_event) = 0;
};


#define SessionLog(lvl, ...)                 GetGameContext()->Log(lvl, GetTag(), __FILE__, __LINE__, __VA_ARGS__)
#define SessionQueueAction(a)                GetGameContext()->QueueAction(a)
#define SessionQueueEvent(e)                 GetGameContext()->QueueEvent(e)
#define SessionRegisterListener(f)           GetGameContext()->RegisterListener(this, &std::remove_reference<decltype(*this)>::type::f)
#define SessionGetGameData()                 GetGameContext()->GetGameData()
#define SessionSendTrackingEvent(e)          GetGameContext()->SendTrackingEvent()
#define SessionIsConnected()                 GetGameContext()->IsConnected()

// Only needed for debugging/testing
#define SessionDisconnect()                  GetGameContext()->Disconnect()


// Client or Server session base class
//  - just one instance of this should exist on the client, potentially multiple instances in the server, one for each client connected
//  - contains the state needed per client instance in the server or on device
//  - includes isolated logging per client
//  - includes event and action sending and receiving (which will stay in the sandbox of this session instance, won't dispatch to other client sessions in the server)
//  - possibly could contain other state (GameData) per client
//  - thread safety? Some systems like the event system provide their own with mutexes, others may be not. Possibly need to add mutexes around those.
class GameSession : public EventReceiverID
{
public:
  GameSession(std::shared_ptr<EventSink> a_eventSink, std::shared_ptr<SessionTrackingInterface> a_trackingSession)
    : m_channel(a_eventSink)
  {
    m_trackingSession = a_trackingSession;
    m_eventSink = a_eventSink;
  }

  virtual ~GameSession()
  {
  }

  // Remote delivery
  template <typename ActionType>
  void QueueAction(ActionType& a_action)
  {
    m_channel.QueueAction(a_action);
  }

  // Local delivery
  template <typename EventType>
  void QueueEvent(EventType a_event)
  {
    m_eventSink->Enqueue<EventType>(a_event);
  }

  // Listening for both local events and remote actions
  template <typename T, typename ActionType>
  void RegisterListener(T* a_obj, void (T::*func)(const ActionType&))
  {
    m_eventSink->RegisterListener<ActionType>(a_obj, std::bind(func, a_obj, std::placeholders::_1));
  }

  // TODO: do we need to expose a disconnect function like in ZA to initiate a disconnect from the client code?
  // Perhaps the destructor can hide this if the only reason is to handle clean shutdown or doing a full restart
  //bool Disconnect(bool raiseEvent = false, const char* disconnectReportMessage = 0);
  void Disconnect() // This is currently only exposed for debugging purposes to test the server reconnection logic
  {
    m_channel.Disconnect();
  }

  virtual bool IsConnected() = 0;

  virtual bool Update(std::chrono::milliseconds a_elapsedTime, std::chrono::milliseconds a_timeOut = std::chrono::milliseconds(0))
  {
    m_eventSink->DispatchQueued();
    return m_channel.Update(a_elapsedTime, a_timeOut);
  }

  enum class LogLevel
  {
    LL_Debug,
    LL_Warning,
    LL_Error,
  };

  template <typename ...Args>
  void Log(LogLevel a_level, const char* a_tag, const char* file, int line, const char* fmt, Args ...args)
  {
    // On server we might want to tunnel this logging to the client for mixed debugging of client and server logging being echoed out in the client debug console
    // We therefore probably want to have a virtual here so we can do different things by installing different ways the logging can be handled
  }

  virtual bool IsGameDataReadyToInitialize() const = 0;

  virtual bool InitializeGameData() = 0;

  virtual CoreGameData& GetGameData() = 0;

  // When game data needs to be refreshed or changes, need a way to handle this
  // I believe we may need the old data for a while, and when it is okay to switch, do the DB switch. A game action needs to then be
  // sent to say which DB we are now using. The server will know that all actions after this specific action will be against the new DB.
  void SwitchGameData()
  {
    // TODO: Need to queue it for when it is okay, then send a GameAction that we have made the swtich (is that higher level logic or can it be handled at this level?)
  }

  void SendTrackingEvent(const BaseTrackingEvent& a_event)
  {
    m_trackingSession->SendTrackingEvent(a_event);
  }

protected:
  GameActionsChannel m_channel;
  std::shared_ptr<EventSink> m_eventSink;
  std::shared_ptr<SessionTrackingInterface> m_trackingSession;
};


// There could be multiple instances of these, but they all point to a shared session
// object which provides the context in which things are operating.
class GameClass : public EventReceiverID
{
public:
  // Inherit context from parent
  explicit GameClass(GameClass& a_parent)
  {
    m_session = a_parent.m_session;
  }

  // Explicitly give the context to this object
  explicit GameClass(std::shared_ptr<GameSession> a_session)
  {
    m_session = a_session;
  }

protected:
  // Doing this means you aren't forced to inherit from GameClass. You can instead use composition if you prefer,
  // creating a GameSession shared_ptr member variable and returning it in a GetGameContext function in your class.
  std::shared_ptr<GameSession> GetGameContext()
  {
    return m_session;
  }

private:
  std::shared_ptr<GameSession> m_session;
};


template <typename... T>
struct dependent_false { static const bool value = false; };


template <typename... T>
std::shared_ptr<GameSession> GetGameContext(T...)
{
  // This is to prevent the macros being used in a global scope not within a context. A context can be created simply by
  // defining a function called GetGameContext() in a given scope, such as file scope, namespace or as a member of a class
  // however beware that defining in file scope won't work for compilation unit builds, so a recommended approach that will
  // be most convienent for client code would be establishing a OT2Client namespace and a GetGameContext() function in this
  // namespace so that any client code will be using this common context. Server code will however require more fine grained
  // contexts per instances of classes instead, so any code used in both the Client and Server or only in the Server which
  // requires a context will need to implement a non static GetGameContext() function as member or inherit from GameClass.
  // The usage is the same in both cases however, making it consistent/searchable/rememberable etc. Usage is via the
  // convienence macros:   SessionLog  SessionQueueAction  SessionQueueEvent   SessionRegisterListener
  static_assert(dependent_false<T...>::value, "Either inherit from GameClass or provide a function called GetGameContext() in namespace or class to return a context");
}


template <typename... T>
const char* GetTag(T...)
{
  static_assert(dependent_false<T...>::value, "Create GetTag() functions in namespaces and classes to give the logging a specific tag in that context");
}

