#pragma once


#include <vector>
#include <memory>
#include <atomic>
#include <cmath> // needed for floor() used in json headers below (iOS)
#include "rapidjson/rapidjson.h"
#include "rapidjson/writer.h"
#include "RKJson.h"
#include "Utils/Json.h"
#include "Utils/Serializer.h"
#include "EventSystem/EventSystem.h"
#include <type_traits>


#define GA_BEGIN(name) \
  class name : public GameActionBase<name> \
  { \
  private: \
    typedef name _this_type; \
  public: \
    name() : GameActionBase() {} \
    name(SupressActionIdIncrement dummy) : GameActionBase(dummy) {} \
    static const char* ActionType() { return #name; } \
    typedef struct { \
      template <typename V> \
      static void Visit(_this_type* o, V& v) \
      { \
        v.Visit("_i_", o->actionId); \
        v.Visit("_r_", o->responseToId); \
      }

// Internal macro not intended to be used directly
#define GA_MEMBER_BEGIN(name) \
    /* This macro magic to chain members together in this fashion I call "Ryland's Device" */ \
    } visit_ns_##name; \

// Internal macro not intended to be used directly
#define GA_MEMBER_END(name, func) \
    /* This macro magic to chain members together in this fashion I call "Ryland's Device" */ \
    typedef struct \
    { \
      template <typename V> \
      static void Visit(_this_type* o, V& v) \
      { \
        visit_ns_##name::Visit(o, v); \
        func \
      }

// Usage:  Without defaults:   GA_MEMBER(int, m_val)    or   With a default value:  GA_MEMBER(int, m_val, 5)
#define GA_MEMBER(type, name, ...) \
    GA_MEMBER_BEGIN(name) \
    type name = type(__VA_ARGS__); \
    GA_MEMBER_END(name, v.Visit(#name, o->name); )

// Note, arrays are just std::vectors. Intended usage:  GA_ARRAY_MEMBER(int, m_array)   creates:  std::vector<int> m_array;
#define GA_ARRAY_MEMBER(type, name) \
    GA_MEMBER_BEGIN(name) \
    std::vector<type> name; \
    GA_MEMBER_END(name, v.Visit(#name, o->name); )

// Usage:  GA_MEMBER_FUNCTION_BEGIN(int, myFunc, (int a, int b)) <function body> GA_MEMEBER_FUNCTION_END(myFunc)
#define GA_MEMBER_FUNCTION_BEGIN(returnType, name, parameters) \
    GA_MEMBER_BEGIN(name) \
    returnType name parameters \
    {

// Usage:  GA_MEMBER_FUNCTION_BEGIN(int, myFunc, (int a, int b)) <function body> GA_MEMEBER_FUNCTION_END(myFunc)
#define GA_MEMBER_FUNCTION_END(name) \
    } \
    GA_MEMBER_END(name, )

#define GA_END(name) \
    GA_MEMBER_BEGIN(end_of_members) \
    template <typename V> \
    void Visit(V& v) \
    { \
      v.Enter(ActionType()); \
      visit_ns_end_of_members::Visit(this, v); \
      v.Exit(ActionType()); \
    } \
  }; \
  \
  static struct register_##name \
  { \
    /* daisy chain during program initialization the GameAction types in to a linked list */ \
    register_##name() { static RegisterActionFactoryItem factory(#name, name::DeserializeAndDispatch); } \
  } s_register_##name##_obj;


struct RegisterActionFactoryItem
{
  // Iterate the action factories
  static void ForEach(std::function<void(const RegisterActionFactoryItem& a_actionFactory)> a_function)
  {
    const RegisterActionFactoryItem* item = GetHead();
    while (item)
    {
      a_function(*item);
      item = &item->m_next;
    }
  }

  RegisterActionFactoryItem(const char* a_name, void(*a_dispatch)(EventSink&, bne::JsonValueConstRef)) : m_name(a_name), m_dispatch(a_dispatch), m_next(*GetHead())
  {
    // Linked list
    GetHead() = this;
  }

  const char* m_name;
  void(*m_dispatch)(EventSink& a_eventSink, bne::JsonValueConstRef a_inData);

private:
  const RegisterActionFactoryItem& m_next;
  static RegisterActionFactoryItem*& GetHead();
};


inline RegisterActionFactoryItem*& RegisterActionFactoryItem::GetHead()
{
  static RegisterActionFactoryItem* s_head = nullptr; // initialized to null - will happen when the first RegisterActionFactoryItem object is created
  return s_head;
}


namespace details
{
  // Internal function for the generation of the unique action id in an action
  // action ids start at 1. An actionId of 0 is invalid
  // This id is unique per instance of an action, not per action type.
  uint32_t GetNextActionId();

  // For unit testing only
  void ResetGameActionIds();
}


class SupressActionIdIncrement
{
};


template <class ActionT>
struct GameActionBase : public EventBase<ActionT>
{
public:
  uint32_t actionId;
  uint32_t responseToId;

  GameActionBase()
  {
    actionId = details::GetNextActionId();
    responseToId = 0;
  }

  GameActionBase(SupressActionIdIncrement dummy)
  {
    actionId = 0;
    responseToId = 0;
  }

  template <typename T>
  void SetRespondingToAction(const GameActionBase<T>& a_otherAction)
  {
    responseToId = a_otherAction.actionId;
  }

  template <typename T>
  bool IsResponseToAction(const GameActionBase<T>& a_otherAction) const
  {
    return responseToId == a_otherAction.actionId;
  }

  static void DeserializeAndDispatch(EventSink& a_eventSink, bne::JsonValueConstRef a_inData);

  static bool SerializeToJson(ActionT& a_gameAction, std::vector<uint8_t>& a_outData);
};


// Use the new serialization system
class JsonSerializerVisitor
{
public:
  JsonSerializerVisitor(JsonSerializerBase& a_serializer) : m_serializer(a_serializer) {}
  inline void SetResult(bool okay)                           { if (!okay) m_error = true; }
  inline bool GetError() const                               { return m_error; }

  void Enter(const char* a_scopeName)                        { m_serializer.PushFrame(a_scopeName); }
  void Exit(const char* a_scopeName)                         { m_serializer.PopFrame(); }

  template <typename T, int>
  struct EnableIfActionOrIntrinsic // Intrinsic case
  {
    EnableIfActionOrIntrinsic(JsonSerializerVisitor& v, const char* a_memberName, T& a_value)
    {
      v.SetResult(SerializerDispatch<T>::Process(a_value, a_memberName, v.m_serializer));
    }
  };

  template <typename T>
  struct EnableIfActionOrIntrinsic<T, 1> // GameAction case
  {
    EnableIfActionOrIntrinsic(JsonSerializerVisitor& v, const char* a_memberName, T& a_value)
    {
      a_value.Visit(v);
    }
  };

  template <typename T>
  void Visit(const char* a_memberName, T& a_value)
  {
    // Select code to call depending if T is derived from a GameAction or not
    EnableIfActionOrIntrinsic<T, std::is_base_of< GameActionBase<T>, T >::value>(*this, a_memberName, a_value);
  }

private:
  JsonSerializerBase& m_serializer;
  bool m_error = false;
};


template <class ActionT>
void GameActionBase<ActionT>::DeserializeAndDispatch(EventSink& a_eventSink, bne::JsonValueConstRef a_inData)
{
  // TODO: Disabling this minor optimization for now. If event system can support this then we could put this back
  //if (EventBase<ActionT>::HasListeners())
  {
    SupressActionIdIncrement dummy;
    ActionT obj(dummy);
    JsonReader reader(a_inData, nullptr, false);  // #TODO OM: had to disable migration here to deal with non-standard use
                                                  // of the API. Should fix this properly later (although we probably still
                                                  // want to turn migration off for GA's)
    JsonSerializerVisitor visitor(reader);
    obj.Visit(visitor);
    if (!visitor.GetError())
    {
      a_eventSink.Enqueue(std::move(obj));
    }
  }
  return;
}


template <class ActionT>
bool GameActionBase<ActionT>::SerializeToJson(ActionT& a_gameAction, std::vector<uint8_t>& a_outData)
{
  bne::JsonValue result;
  JsonWriter writer(result);
  JsonSerializerVisitor visitor(writer);
  a_gameAction.Visit(visitor);
  if (!visitor.GetError())
  {
    std::string str = result.toCompactString();
    size_t oldSize = a_outData.size();
    a_outData.resize(oldSize + str.size());
    memcpy(a_outData.data() + oldSize, str.data(), str.size());
    return true;
  }
  return false;
}


/*

Old ZA code for adding a single GameAction (approximately what it was like)

// Add it to the enum (GameActions.h?)
enum GameActions
{
  GA_ProfileUpdate
};

// h   (Profile.h)
GA_BEGIN(PropfileUpdateAction, GA_ProfileUpdate)
  int m_blah;
GA_END()

//cpp  (Profile.cpp)
GA_BEGIN_SERIALIZE(PropfileUpdateAction)
GA_SERIALIZE(int, m_blah)
GA_END_SERIALIZE()

// Register it   (GameActions.cpp?)
void RegisterAction()
{
  REGISTER_GA(GA_ProfileUpdate);
}

// Creating one (which also queues it)
PropfileUpdateAction* action = GAM->CreateAction<GA_ProfileUpdate>();


The GAM (GameActionManager) would serialize and de-serialize this via the SystemNetworkService
and then dispatch via the event system as an event. This event has a void* data payload which
made the event dispatch not so type safe.

Various problems during development included:
- not doing all the steps required to add a new GameAction correctly
- When adding one, long compile times after changing the enum
- Compatility issues between the client and server when making changes to the game actions (enum sent as type as int over the wire)
- GameDB change actions invalidating in-flight/queued actions. Also GameDB refs/pointers in code.
- Type safety of the event dispatch
- The 2 ways to create game actions (CreateAction<GA_ProfileUpdate> or CreateAction<PropfileUpdateAction::VALUE>) which made searching for actions harder


// Example new way:
//   Only declare in one place (no need to put stuff in h and cpp files and keep in sync)
//   No need to register the action
//   There is no enum, based on string matching which can be optimized by use of hashing
//   although a std::map with RB-tree will probably be plenty good enough
//   Over the wire between client and server, use of a string instead of enum value as type
//   will be safer anyway.

GA_BEGIN(Inventory)
  GA_MEMBER(int32_t, m_coins) // specifying defaults is optional
  GA_MEMBER(int32_t, m_gems, 0)
  GA_MEMBER(float, m_blah, 9.0)
GA_END(Inventory)

GA_BEGIN(ProfileUpdate)
  GA_MEMBER(Inventory, m_inventory)
  GA_MEMBER(int32_t, m_val, 100)
GA_END(ProfileUpdate)


A new feature is setting an action as being in response to another action. This is useful
in that there can be explicit confirmation or building a chain of communication between
client and server where as in ZA we sometimes ran in to problems where we might wait for
a response, but not be sure if it was a response sent as a result of the action we sent
or something else that happened. Now it can be clear.

Example usage:

void GotActionHandler(const RequestActionType& a)
{
  ResponseActionType b;
  b.SetRespondingToAction(a);
  ...
}


Example usage of sending a GameAction:


GA_BEGIN(Ping)
  GA_MEMBER(int32_t, m_variable, 0)
GA_END(Ping)


void SendPing()
{
  Ping ping;
  ping.m_variable = 1234;
  GameServerInterface::Get()->QueueAction(ping);
}


*/

