//====== Copyright Valve Corporation, All rights reserved. ====================

#ifndef ISTEAMNETWORKINGMESSAGES
#define ISTEAMNETWORKINGMESSAGES
#pragma once

#include "steamnetworkingtypes.h"
#include "steam_api_common.h"

//-----------------------------------------------------------------------------
/// The non-connection-oriented interface to send and receive messages
/// (whether they be "clients" or "servers").
///
/// ISteamNetworkingSockets is connection-oriented (like TCP), meaning you
/// need to listen and connect, and then you send messages using a connection
/// handle.  ISteamNetworkingMessages is more like UDP, in that you can just send
/// messages to arbitrary peers at any time.  The underlying connections are
/// established implicitly.
///
/// Under the hood ISteamNetworkingMessages works on top of the ISteamNetworkingSockets
/// code, so you get the same routing and messaging efficiency.  The difference is
/// mainly in your responsibility to explicitly establish a connection and
/// the type of feedback you get about the state of the connection.  Both
/// interfaces can do "P2P" communications, and both support both unreliable
/// and reliable messages, fragmentation and reassembly.
///
/// The primary purpose of this interface is to be "like UDP", so that UDP-based code
/// can be ported easily to take advantage of relayed connections.  If you find
/// yourself needing more low level information or control, or to be able to better
/// handle failure, then you probably need to use ISteamNetworkingSockets directly.
/// Also, note that if your main goal is to obtain a connection between two peers
/// without concerning yourself with assigning roles of "client" and "server",
/// you may find the symmetric connection mode of ISteamNetworkingSockets useful.
/// (See k_ESteamNetworkingConfig_SymmetricConnect.)
///
class ISteamNetworkingMessages
{
public:
	/// Sends a message to the specified host.  If we don't already have a session with that user,
	/// a session is implicitly created.  There might be some handshaking that needs to happen
	/// before we can actually begin sending message data.  If this handshaking fails and we can't
	/// get through, an error will be posted via the callback SteamNetworkingMessagesSessionFailed_t.
	/// There is no notification when the operation succeeds.  (You should have the peer send a reply
	/// for this purpose.)
	///
	/// Sending a message to a host will also implicitly accept any incoming connection from that host.
	///
	/// nSendFlags is a bitmask of k_nSteamNetworkingSend_xxx options
	///
	/// nRemoteChannel is a routing number you can use to help route message to different systems.
	/// You'll have to call ReceiveMessagesOnChannel() with the same channel number in order to retrieve
	/// the data on the other end.
	///
	/// Using different channels to talk to the same user will still use the same underlying
	/// connection, saving on resources.  If you don't need this feature, use 0.
	/// Otherwise, small integers are the most efficient.
	///
	/// It is guaranteed that reliable messages to the same host on the same channel
	/// will be be received by the remote host (if they are received at all) exactly once,
	/// and in the same order that they were sent.
	///
	/// NO other order guarantees exist!  In particular, unreliable messages may be dropped,
	/// received out of order with respect to each other and with respect to reliable data,
	/// or may be received multiple times.  Messages on different channels are *not* guaranteed
	/// to be received in the order they were sent.
	///
	/// A note for those familiar with TCP/IP ports, or converting an existing codebase that
	/// opened multiple sockets:  You might notice that there is only one channel, and with
	/// TCP/IP each endpoint has a port number.  You can think of the channel number as the
	/// *destination* port.  If you need each message to also include a "source port" (so the
	/// recipient can route the reply), then just put that in your message.  That is essentially
	/// how UDP works!
	///
	/// Returns:
	/// - k_EREsultOK on success.
	/// - k_EResultNoConnection will be returned if the session has failed or was closed by the peer,
	///   and k_nSteamNetworkingSend_AutoRestartBrokenSession is not used.  (You can use
	///   GetSessionConnectionInfo to get the details.)  In order to acknowledge the broken session
	///   and start a new one, you must call CloseSessionWithUser
	/// - See ISteamNetworkingSockets::SendMessageToConnection for more possible return values
	virtual EResult SendMessageToUser( const SteamNetworkingIdentity &identityRemote, const void *pubData, uint32 cubData, int nSendFlags, int nRemoteChannel ) = 0;

	/// Reads the next message that has been sent from another user via SendMessageToUser() on the given channel.
	/// Returns number of messages returned into your list.  (0 if no message are available on that channel.)
	///
	/// When you're done with the message object(s), make sure and call SteamNetworkingMessage_t::Release()!
	virtual int ReceiveMessagesOnChannel( int nLocalChannel, SteamNetworkingMessage_t **ppOutMessages, int nMaxMessages ) = 0;

	/// Call this in response to a SteamNetworkingMessagesSessionRequest_t callback.
	/// SteamNetworkingMessagesSessionRequest_t are posted when a user tries to send you a message,
	/// and you haven't tried to talk to them first.  If you don't want to talk to them, just ignore
	/// the request.  If the user continues to send you messages, SteamNetworkingMessagesSessionRequest_t
	/// callbacks will continue to be posted periodically.
	///
	/// Returns false if there is no session with the user pending or otherwise.  If there is an
	/// existing active session, this function will return true, even if it is not pending.
	///
	/// Calling SendMessageToUser() will implicitly accepts any pending session request to that user.
	virtual bool AcceptSessionWithUser( const SteamNetworkingIdentity &identityRemote ) = 0;

	/// Call this when you're done talking to a user to immediately free up resources under-the-hood.
	/// If the remote user tries to send data to you again, another SteamNetworkingMessagesSessionRequest_t
	/// callback will be posted.
	///
	/// Note that sessions that go unused for a few minutes are automatically timed out.
	virtual bool CloseSessionWithUser( const SteamNetworkingIdentity &identityRemote ) = 0;

	/// Call this  when you're done talking to a user on a specific channel.  Once all
	/// open channels to a user have been closed, the open session to the user will be
	/// closed, and any new data from this user will trigger a
	/// SteamSteamNetworkingMessagesSessionRequest_t callback
	virtual bool CloseChannelWithUser( const SteamNetworkingIdentity &identityRemote, int nLocalChannel ) = 0;

	/// Returns information about the latest state of a connection, if any, with the given peer.
	/// Primarily intended for debugging purposes, but can also be used to get more detailed
	/// failure information.  (See SendMessageToUser and k_nSteamNetworkingSend_AutoRestartBrokenSession.)
	///
	/// Returns the value of SteamNetConnectionInfo_t::m_eState, or k_ESteamNetworkingConnectionState_None
	/// if no connection exists with specified peer.  You may pass nullptr for either parameter if
	/// you do not need the corresponding details.  Note that sessions time out after a while,
	/// so if a connection fails, or SendMessageToUser returns k_EResultNoConnection, you cannot wait
	/// indefinitely to obtain the reason for failure.
	virtual ESteamNetworkingConnectionState GetSessionConnectionInfo( const SteamNetworkingIdentity &identityRemote, SteamNetConnectionInfo_t *pConnectionInfo, SteamNetworkingQuickConnectionStatus *pQuickStatus ) = 0;
};
#define STEAMNETWORKINGMESSAGES_INTERFACE_VERSION "SteamNetworkingMessages002"

//
// Callbacks
//

#pragma pack( push, 1 )

/// Posted when a remote host is sending us a message, and we do not already have a session with them
struct SteamNetworkingMessagesSessionRequest_t
{ 
	enum { k_iCallback = k_iSteamNetworkingMessagesCallbacks + 1 };
	SteamNetworkingIdentity m_identityRemote;			// user who wants to talk to us
};

/// Posted when we fail to establish a connection, or we detect that communications
/// have been disrupted it an unusual way.  There is no notification when a peer proactively
/// closes the session.  ("Closed by peer" is not a concept of UDP-style communications, and
/// SteamNetworkingMessages is primarily intended to make porting UDP code easy.)
///
/// Remember: callbacks are asynchronous.   See notes on SendMessageToUser,
/// and k_nSteamNetworkingSend_AutoRestartBrokenSession in particular.
///
/// Also, if a session times out due to inactivity, no callbacks will be posted.  The only
/// way to detect that this is happening is that querying the session state may return
/// none, connecting, and findingroute again.
struct SteamNetworkingMessagesSessionFailed_t
{ 
	enum { k_iCallback = k_iSteamNetworkingMessagesCallbacks + 2 };

	/// Detailed info about the session that failed.
	/// SteamNetConnectionInfo_t::m_identityRemote indicates who this session
	/// was with.
	SteamNetConnectionInfo_t m_info;
};

#pragma pack(pop)

// Global accessors
// Using standalone lib
#ifdef STEAMNETWORKINGSOCKETS_STANDALONELIB

	// Standalone lib.
	static_assert( STEAMNETWORKINGMESSAGES_INTERFACE_VERSION[25] == '2', "Version mismatch" );
	STEAMNETWORKINGSOCKETS_INTERFACE ISteamNetworkingMessages *SteamNetworkingMessages_LibV2();
	inline ISteamNetworkingMessages *SteamNetworkingMessages_Lib() { return SteamNetworkingMessages_LibV2(); }

	// If running in context of steam, we also define a gameserver instance.
	#ifdef STEAMNETWORKINGSOCKETS_STEAM
		STEAMNETWORKINGSOCKETS_INTERFACE ISteamNetworkingMessages *SteamGameServerNetworkingMessages_LibV2();
		inline ISteamNetworkingMessages *SteamGameServerNetworkingMessages_Lib() { return SteamGameServerNetworkingMessages_LibV2(); }
	#endif

	#ifndef STEAMNETWORKINGSOCKETS_STEAMAPI
		inline ISteamNetworkingMessages *SteamNetworkingMessages() { return SteamNetworkingMessages_LibV2(); }
		#ifdef STEAMNETWORKINGSOCKETS_STEAM
			inline ISteamNetworkingMessages *SteamGameServerNetworkingMessages() { return SteamGameServerNetworkingMessages_LibV2(); }
		#endif
	#endif
#endif

// Using Steamworks SDK
#ifdef STEAMNETWORKINGSOCKETS_STEAMAPI

	// Steamworks SDK
	STEAM_DEFINE_USER_INTERFACE_ACCESSOR( ISteamNetworkingMessages *, SteamNetworkingMessages_SteamAPI, STEAMNETWORKINGMESSAGES_INTERFACE_VERSION );
	STEAM_DEFINE_GAMESERVER_INTERFACE_ACCESSOR( ISteamNetworkingMessages *, SteamGameServerNetworkingMessages_SteamAPI, STEAMNETWORKINGMESSAGES_INTERFACE_VERSION );

	#ifndef STEAMNETWORKINGSOCKETS_STANDALONELIB
		inline ISteamNetworkingMessages *SteamNetworkingMessages() { return SteamNetworkingMessages_SteamAPI(); }
		inline ISteamNetworkingMessages *SteamGameServerNetworkingMessages() { return SteamGameServerNetworkingMessages_SteamAPI(); }
	#endif
#endif

#endif // ISTEAMNETWORKINGMESSAGES
