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

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

#include <boost/asio.hpp>

namespace Aftr
{
   // Encapsulates a single TCP Session (aka NetMessengerServerSession) between this 
   // Aftr instance and another end of the TCP session. Typically,
   // all data that flows over these TCP sessions are NetMsgs.
   // Updated Aug 2023 by Nykl to remove raw pointers and manual memory mgmt.

class NetMessengerServerSession : public std::enable_shared_from_this<NetMessengerServerSession>
{
public:

   //After the Engine has initialized, sometimes a user wants a TCP session between itself and another endpoint. This
   //factory method is the *simplest* way to create a TCP session that hooks in to the asynchronous IO (really fast) 
   //for sending and receiving NetMsgs... Internally, this wraps up the details of creating a NetMessengerServerSession
   //and connects the io_context and internal message queues with the singler NetMessengerServer already running.
   //If the GLView hasn't yet initialized or created the main NetMessengerServer, this method will return nullptr.
   //If a TCP connection to host:port is not able to be established, this method will return nullptr.
   //This method is identical / does the exact same thing as NetMessengerServer::createNew_TCP_Session.
   //After creating a TCP Session using this function, the user can connect by calling session->connectSessionToHost( ip, port );
   static std::shared_ptr< NetMessengerServerSession > createNew_TCP_Session();

   //This constructor is used by the NetMessengerServerListener to establish a new session between an initiating
   //remote client and this server.
   NetMessengerServerSession( boost::asio::io_context& io_context,
                              NetMessengerMessageQueue* servListenerRecvQueue );

   //This constructor is used by the NetMessengerServerListener to establish a new session between an initiating
   //remote client and this server. This c'tor is typically invoked from tcp_acceptor which has just created a 
   //new tcp socket. The io_context is used by the handler's write strand.
   NetMessengerServerSession( boost::asio::ip::tcp::socket&& session_socket, 
                              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.
   NetMessengerServerSession( const std::string& host, const std::string& port, boost::asio::io_context& io_context,
                              NetMessengerMessageQueue* recvQueue );
   
   ~NetMessengerServerSession();
   boost::asio::ip::tcp::socket& getSocket();
   bool isStarted() { return this->IsStarted; }
   bool isConnected() const; //Returns true iff the socket is open -- does not necessarily change if an error occurs

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

   void start();
   void close(); ///< Closes this TCP connection
   ///host and port are stored internally as a std::string, so we make the copy occur at the call site
   bool connectSessionToHost( std::string host, std::string port );
   bool connectSessionToHost( std::string_view host, std::string_view port );
   bool reConnectSessionToHost();

   std::string toString() const;

   //When doing async sending and receiving, notifying the user of a send/recv error is not possible
   //at 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 getLastSendErrorAndClearErrState() noexcept;
   boost::system::error_code getLastRecvErrorAndClearErrState() noexcept;
   std::optional< std::string > getLastErrorMsgs() const noexcept;
   std::optional< std::string > getLastErrorMsgsAndClearErrState() noexcept;

   //Normally, the engine instance creates a NetMessengerServer which has its *own* NetMessengerServerSession.
   //The NetMessengerServer ensures the NetMessengerServerSession is *processed* (checks for recv & send of
   //message queues) each frame. If a user creates their *own* instances of a NetMessengerServerSession (TCP Conn)
   //The sending will work properly, but to receive, the user must supply a servListenerRecvQueue. After supplying
   //a receive queue, the user needs to look in that queue to actually process the contained NetMsgs and invoke
   //the desired callbacks.


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::tcp::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; //Not owned by this instance, don't delete

   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

