#pragma once
#include "AftrConfig.h"
#ifdef AFTR_CONFIG_USE_BOOST

#include "NetMessengerMessageQueue.h"
#include <iosfwd>
#include <cstdint>

#include <boost/asio.hpp>

namespace Aftr
{
   //SLN, this class is DEPRECATED and only left for older modules that may still use it... It will be removed in the future.
   // PREFER TO USE NetMessengerServerUDPConn or NetMessengerServerUDPConnT instead

/// This can only send messages UP TO the maximum size of a UDP payload. No fragmentation occurs. If the payload
/// is larger than UDP payload, a boost error (typically system: 1784) will result.
class NetMessengerServerSessionUDP
{
public:

   //This constructor is used by the NetMessengerServerListener to establish a new session between an initiating
   //remote client and this server.
   NetMessengerServerSessionUDP( boost::asio::io_context& io_context, NetMessengerMessageQueue* servListenerRecvQueue );
   
   //This constructor can be used by a user to establish a session to a remote NetMessengerServerListener (a remote
   //instance of the AftrBurner engine running.
   NetMessengerServerSessionUDP( const std::string& host, const std::string& port, boost::asio::io_context& io_context,
                              NetMessengerMessageQueue* recvQueue );
   
   ~NetMessengerServerSessionUDP();
   boost::asio::ip::udp::socket& getSocket();
   bool isStarted() { return this->IsStarted; }

   //Ownership is transferred to the NetMessengerServerSessionUDP, the message will be destroyed by this instance.
   bool enqueueMsgToSend( std::shared_ptr<NetMsg> msg );

   void start();
   bool connectSessionToHost( const std::string& host, const std::string& port );
   bool reConnectSessionToHost();

   std::string toString() const;

   //When doing async sending and receiving, notifying the user of a send/recv error is not possible
   //as the time the user queues the request to send or recv. What we do instead, if an send or recv
   //error occurs, we set these values to non-null. The user can then check these flags to see if an
   //error has occurred and choose to take action at that point, such as by trying to reconnect to the
   //endpoint.
   //These two get Error msgs will return the latest error and THEN reset the error_code back to none.
   boost::system::error_code getLastSendError();
   boost::system::error_code getLastRecvError();

private:

   bool isValidHeader( unsigned int headerCode ) const;

   //host and port are only used for the second constructor when the session is used to initiate a new connection (SYN)
   //versus serving as receiver of a new connection (the remote endpoint (SYN ACK)).
   std::string host{ "127.0.0.1" };
   std::string port{ "12683" };

   boost::asio::ip::udp::socket sock;
   static const unsigned int HEADER_LEN_BYTES = 8; ///< Header length of a message in bytes (32-bits)
   std::vector< char > recvBuff;
   std::array<char, HEADER_LEN_BYTES> recvHeader{ 0 };

   void onRecvHeader( const boost::system::error_code& error, size_t bytesReceived );
   void onRecvBody( const boost::system::error_code& error, size_t bytesReceived );
   
   //This method is only execute inside asio handler via this->strand executor.
   //This method adds msg to sendQueue. If no existing messages are being sent, it will
   //begin an writing within the strand's asio context.
   void enqueueMsgHandler( std::shared_ptr<NetMsg> msg );
   
   //This method is only execute inside asio handler via this->strand executor.
   //This method is called by enqueueMsgHandler only to begin writing queued messages within
   //the strand's asio context. This will repeatedly invoke until the send queue is empty.
   void sendMsgHandler();
   void onSentMsg( char* binDat, const boost::system::error_code& error, size_t bytes_transferred );

   bool IsStarted = false; ///< Initially set to false, gets set to True once this->start() has been invoked and async operations have begun

   unsigned int currHeaderCode = 0; ///< Set by onRecvHeader, read by onRecvBody, indicates NetMsg subclass to instantiate
   unsigned int currMsgLenBytes = 0; ///< Set by onRecvHeader, read by onRecvBody, indicates length of binary data for the current message

   //Thread safe queue of pending messages to send to endpoint associated with this session
   //Only thread safe when the user calls lock() prior to using it and unlock after using it.
   NetMessengerMessageQueueT< std::shared_ptr<NetMsg> > sendQueue;

   /**
      Pointer to corresponding NetMessengerServerListener's queue, which holds all newly
      received (and fully instantiated) messages until the engine processes their 
      callbacks (NetMsg::onMessageArrived()). The callback is invoked when then engine
      calls NetMessengerServer::onUpdate(), which is typically in the GLView::onUpdateWorld().
      This recvQueue is NOT owned by this instance, do not delete. This queue is actually
      owned by the corresponding NetMessengerServerListener which spawned this session's existence.
   */
   NetMessengerMessageQueue* recvQueue = nullptr;

   boost::asio::io_context::strand writeStrand; // A strand guarantees that no two handlers execute concurrently.

   boost::system::error_code lastSendError;
   boost::system::error_code lastRecvError;
};

} //namespace Aftr

#endif
