#pragma once
#include <memory>
#include <functional>

//This header simply declares the signature for the callback expected when a NetMsg arrives after a user
//registers via:
//template< typename NetMsgT >
//void GLView::subscribe_NetMsg_to_callback( callback_on_NetMsgT_arrived<NetMsgT> func );
namespace Aftr
{
   class NetMsg;

   template< class NetMsgT >
   using callback_on_NetMsgT_arrived = std::function< void( std::unique_ptr< NetMsgT > ) >;

   struct callback_NetMsgT_erased
   {
      template< typename NetMsgT >
      callback_NetMsgT_erased( callback_on_NetMsgT_arrived<NetMsgT> func, unsigned int NetMsgID ) :
         NetMsgID( NetMsgID )
      {
         this->pimpl = std::make_shared< callback_concrete<NetMsgT> >( std::forward<callback_on_NetMsgT_arrived<NetMsgT> >( func ) );
      }

      //Always returns true if the functor/callback was invoked, returns false if the functor did not exist / was not called
      bool operator()( NetMsg* msg )
      {
         if( pimpl )
         {
            //we know that msg is actually of subclass NetMsgID-type, so cast and invoke callback
            this->pimpl->invoke( msg );
            return true;
         }
         return false;
      }
      unsigned int NetMsgID = -1;

   private:
      //Use type erasure to hide the templated type and only retrieve that type
      //at the instant the callback needs to be invoked.

      struct callback_abstract
      {
         virtual ~callback_abstract() = default;
         virtual void invoke( NetMsg* msg ) = 0;
      };

      template< typename NetMsgT >
      struct callback_concrete : callback_abstract
      {
         explicit callback_concrete( callback_on_NetMsgT_arrived<NetMsgT> func ) : functor(func)
         {
         }

         virtual void invoke( NetMsg* msg ) override
         {
            //Right here we know the TEMPLATED type AND has a pointer to the NetMsg.
            //So we can do the magic right here, right now!
            NetMsgT* subclass = static_cast<NetMsgT*>(msg);
            std::unique_ptr< NetMsgT > ptr( subclass ); //ptr takes full ownership of NetMsg
            this->functor( std::move(ptr) );
         }
         callback_on_NetMsgT_arrived<NetMsgT> functor;

      };

      std::shared_ptr< callback_abstract > pimpl;
   };
}