#ifndef __GAMESWFCALLBACK_H__
#define __GAMESWFCALLBACK_H__

#include <CasualCore.h>

namespace gameswf
{
  void initCallbacks();
  void deinitCallbacks();

  class CallBackBase
  {
  protected:
    friend void initCallbacks();
    friend void deinitCallbacks();

    static RKHashTable<void*> m_natives;
  };

  // \brief     provides container for AS callbacks
  // \details   object that callback will be called on must exist at the time of the callback
  template <class T>
  class CallBackContainer final
  {
    class CallBack : CallBackBase
    {
    public:
      typedef void (T::*MemberFunction)(const gameswf::FunctionCall&);
      typedef void (T::*MemberFunctionWithUserData)(const gameswf::FunctionCall&, const gameswf::ASValue&);

    public:
      CallBack(T& host, MemberFunction p, const char* functionName)
        : m_host(host)
        , m_pMemberFunction(p)
        , m_pMemberFunctionWithUserData(nullptr)
        , m_handle()
        , m_functionName(functionName)
        , m_registeredNative(true) {
        m_natives.Insert(this, functionName);
        gameswf::registerNativeFunction(functionName, CallBackFunction);
      }

      CallBack(T& host, MemberFunction p, const char* functionName, const gameswf::CharacterHandle& handle)
        : m_host(host)
        , m_pMemberFunction(p)
        , m_pMemberFunctionWithUserData(nullptr)
        , m_handle(handle)
        , m_functionName(functionName)
        , m_registeredNative(false) {
        RKASSERT(sizeof(gameswf::ASCppFunctionPtr) == sizeof(T*), "Types are different, consider wrapping 'this' pointer to another type (string?)");

        m_handle.setMember(functionName, HandleCallbackFunction);
        RKString sCppID = RKString::MakeFormatted("cpp_this_%s", functionName);
        m_handle.setMember(sCppID.GetString(), reinterpret_cast<gameswf::ASCppFunctionPtr>(this));
      }

      CallBack(T& host, MemberFunctionWithUserData p, const char* functionName, const gameswf::CharacterHandle& handle, const gameswf::ASValue& userData)
        : m_host(host)
        , m_pMemberFunction(nullptr)
        , m_pMemberFunctionWithUserData(p)
        , m_handle(handle)
        , m_functionName(functionName)
        , m_registeredNative(false) {
        RKASSERT(sizeof(gameswf::ASCppFunctionPtr) == sizeof(T*), "Types are different, consider wrapping 'this' pointer to another type (string?)");

        m_handle.setMember(functionName, HandleCallbackFunctionWithUserData);
        RKString sCppID = RKString::MakeFormatted("cpp_this_%s", functionName);
        RKString sDataID = RKString::MakeFormatted("user_data_%s", functionName);
        m_handle.setMember(sCppID.GetString(), reinterpret_cast<gameswf::ASCppFunctionPtr>(this));
        m_handle.setMember(sDataID.GetString(), userData);
      }

      ~CallBack() {
        if (m_registeredNative)
        {
          if (m_natives.IsInited()){
            RKHashTable<void*>::ConstIterator iter = m_natives.ConstFindKey(m_functionName.GetString());
            RKASSERT(iter.IsValid(), "Removing native function more than once.");
            if (iter.IsValid())
            {
              m_natives.Erase(iter);
            }
          }
          gameswf::registerNativeFunction(m_functionName.GetString(), nullptr);
        }
        else
        {
          if (m_handle.isValid()) {
            gameswf::ASValue dummy;
            m_handle.setMember(m_functionName.GetString(), dummy);

            RKString sCppID = RKString::MakeFormatted("cpp_this_%s", m_functionName.GetString());
            RKString sDataID = RKString::MakeFormatted("user_data_%s", m_functionName.GetString());
            m_handle.setMember(sCppID.GetString(), dummy);
            m_handle.setMember(sDataID.GetString(), dummy);
          }
        }
      }

      void operator()(const gameswf::FunctionCall& fn) const {
        RKASSERT(m_pMemberFunction, "CallBack Function is nullptr");
        if (m_pMemberFunction) {
          (m_host.*m_pMemberFunction)(fn);
        }
      }

      void operator()(const gameswf::FunctionCall& fn, const gameswf::ASValue& userData) const {
        RKASSERT(m_pMemberFunctionWithUserData, "CallBack Function is nullptr");
        if (m_pMemberFunctionWithUserData) {
          (m_host.*m_pMemberFunctionWithUserData)(fn, userData);
        }
      }

      static void CallBackFunction(const gameswf::FunctionCall& fn) {
        RKHashTable<void*>::Iterator iter = m_natives.FindKey(fn.name);
        if (iter.IsValid()) {
          CallBack* callback = reinterpret_cast<CallBack*>(iter.Value());
          (*callback)(fn);
        }
      }

      static void HandleCallbackFunction(const gameswf::FunctionCall& fn) {
        RKString sCppID = RKString::MakeFormatted("cpp_this_%s", fn.name);
        gameswf::ASValue thisVal; fn.this_ptr->getMember(sCppID.GetString(), &thisVal);
        gameswf::ASCppFunctionPtr disguisedThisPtr = static_cast<gameswf::ASCppFunction*>(thisVal.toObject())->m_func;
        CallBack* pOwner = reinterpret_cast<CallBack*>(disguisedThisPtr);
        if (pOwner != nullptr) {
          (*pOwner)(fn);
        }
      }

      static void HandleCallbackFunctionWithUserData(const gameswf::FunctionCall& fn) {
        RKString sCppID = RKString::MakeFormatted("cpp_this_%s", fn.name);
        RKString sDataID = RKString::MakeFormatted("user_data_%s", fn.name);
        gameswf::ASValue thisVal;  fn.this_ptr->getMember(sCppID.GetString(), &thisVal);
        gameswf::ASValue userdata; fn.this_ptr->getMember(sDataID.GetString(), &userdata);
        gameswf::ASCppFunctionPtr  disguisedThisPtr = static_cast<gameswf::ASCppFunction*>(thisVal.toObject())->m_func;
        CallBack* pOwner = reinterpret_cast<CallBack*>(disguisedThisPtr);
        if (pOwner) {
          (*pOwner)(fn, userdata);
        }
      }

    private:
      T&                          m_host;
      gameswf::CharacterHandle    m_handle;
      const RKString              m_functionName;
      bool                        m_registeredNative;
      MemberFunction              m_pMemberFunction;
      MemberFunctionWithUserData  m_pMemberFunctionWithUserData;
    };

  public:
    inline CallBackContainer() {
      m_callbacks.Reserve(4);
    }

    inline ~CallBackContainer(){
      Clear();
    }

    inline void Clear() {
      for (size_t i = 0; i < m_callbacks.Size(); ++i) {
        RKDELETE(m_callbacks[i]);
      }
      m_callbacks.Clear();
    }

    // \brief adds a member function callback
    // \param t             (this) pointer to object, cannot be nullptr
    // \param p             address of a member function
    // \param functionName  exposed AS function name
    // \return true on success, false otherwise
    bool AddCallBack(T& t, void(T::*p)(const gameswf::FunctionCall&), const char* functionName) {
      if (p != nullptr) {
        m_callbacks.Append(RKNEW(CallBack)(t, p, functionName));
        return true;
      }
      return false;
    }

    // \brief adds a member function callback
    // \param t             (this) pointer to object, cannot be nullptr
    // \param p             address of a member function
    // \param functionName  exposed AS function name
    // \param userData      eser defined data to pass to the callback
    // \return true on success, false otherwise
    bool AddCallBack(T& t, void(T::*p)(const gameswf::FunctionCall&, const gameswf::ASValue&), const char* functionName, const gameswf::ASValue& userData) {
      if (p != nullptr) {
        m_callbacks.Append(RKNEW(CallBack)(t, p, functionName, userData));
        return true;
      }
      return false;
    }

    // \brief adds a member function callback
    // \param t             (this) pointer to object, cannot be nullptr
    // \param p             address of a member function
    // \param functionName  exposed AS function name
    // \param handle        AS object handle for the callback to be bound to, must be valid
    // \return true on success, false otherwise
    bool AddCallBack(T& t, void(T::*p)(const gameswf::FunctionCall&), const char* functionName, const gameswf::CharacterHandle& handle) {
      if (p != nullptr && handle.isValid()) {
        m_callbacks.Append(RKNEW(CallBack)(t, p, functionName, handle));
        return true;
      }
      return false;
    }

    // \brief adds a member function callback
    // \param t             (this) pointer to object, cannot be nullptr
    // \param p             address of a member function
    // \param functionName  exposed AS function name
    // \param handle        AS object handle for the callback to be bound to, must be valid
    // \param userData      eser defined data to pass to the callback
    // \return true on success, false otherwise
    bool AddCallBack(T& t, void(T::*p)(const gameswf::FunctionCall&, const gameswf::ASValue&), const char* functionName, const gameswf::CharacterHandle& handle, const gameswf::ASValue& userData) {
      if (p != nullptr && handle.isValid()) {
        m_callbacks.Append(RKNEW(CallBack)(t, p, functionName, handle, userData));
        return true;
      }
      return false;
    }

  private:
    RKList<CallBack*> m_callbacks;
  };

} // namespace gameswf

#endif
