

//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/
//_/_/  This file is part of CMSDK version 2.1.1.260726 (26 July 2026)
//_/_/  
//_/_/  developed by
//_/_/  
//_/_/  Communicative Machines Ltd. 
//_/_/    http://cmlabs.com/
//_/_/  Copyright (c) 2026
//_/_/  
//_/_/  in collaboration with 
//_/_/  
//_/_/  Icelandic Institute for Intelligent Machines ses
//_/_/    Menntavegur 1
//_/_/    101 Reykjavik
//_/_/    ICELAND
//_/_/    http://www.iiim.is/
//_/_/
//_/_/  and is released under the following
//_/_/  
//_/_/  EXTENDED BSD License
//_/_/
//_/_/  Redistribution and use in source and binary forms, with or without
//_/_/  modification, is permitted provided that the following conditions 
//_/_/  are met:
//_/_/
//_/_/  - Redistributions of source code must retain the above copyright 
//_/_/    and collaboration notice, this list of conditions and the following 
//_/_/    disclaimer.
//_/_/
//_/_/  - Redistributions in binary form must reproduce the above copyright 
//_/_/    notice, this list of conditions and the following disclaimer in the 
//_/_/    documentation and/or other materials provided with the distribution.
//_/_/
//_/_/  - Neither the name of its copyright holders nor the names of its 
//_/_/    contributors may be used to endorse or promote products derived from 
//_/_/    this software without specific prior written permission.
//_/_/
//_/_/  - CADIA Clause: The license granted in and to the software under this 
//_/_/    agreement is a limited-use license. The software may not be used in 
//_/_/    furtherance of: (i) intentionally causing bodily injury or severe 
//_/_/    emotional harm to any person; (ii) invading the personal privacy or 
//_/_/    violating the human rights of any person; or (iii) committing or 
//_/_/    preparing for any act of war. 
//_/_/
//_/_/  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
//_/_/  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
//_/_/  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
//_/_/  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 
//_/_/  OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
//_/_/  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
//_/_/  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
//_/_/  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
//_/_/  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
//_/_/  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
//_/_/  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//_/_/
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
/*! @file RequestClient.h
	@brief Request-system client side: RequestReply futures, RequestQueue, gateway connection records and the RequestClient itself.

	The CMSDK @b request @b system is a distributed request/response fabric
	built on the message protocol of NetworkManager. Three roles cooperate:

	- @b RequestClient (this file): application-facing. Wraps each outgoing
	  DataMessage in a RequestReply "future", sends it to a RequestGateway
	  and lets the caller either block on waitForResult() or register a
	  callback.
	- @b RequestGateway (RequestGateway.h): the router/load balancer. Accepts
	  requests from many clients (binary, HTTP or WebSocket), queues them,
	  picks the best RequestExecutor (short vs long requests, queue sizes,
	  heartbeats) and routes replies back to the originating client.
	- @b RequestExecutor (RequestExecutor.h): the worker/server. Pulls
	  requests from its gateway connection, processes them and replies.

	A request round-trip looks like this:
	@code
	RequestClient client;
	client.addGateway(1, "gateway.host", 9000);       // connect to a gateway
	client.waitForConnection(5000);

	DataMessage* msg = RequestClient::CreateRequestMessage(
		HTTP_GET, "status", "detail", "full");          // build the request
	RequestReply* reply = client.postRequest(msg);     // async send
	if (reply->waitForResult(3000) == SUCCESS) {       // block for the answer
		DataMessage* answer = reply->peekReplyMessage(); // owned by reply
		// ... use answer ...
	}
	client.finishRequest(reply);                       // release the slot
	@endcode

	Clients keep a list of gateways (RequestGatewayConnection) and reconnect
	automatically; requests survive brief gateway outages by re-queuing.

	@defgroup Requests Requests
	@brief The distributed request/response fabric: RequestClient posts
	requests, RequestGateway routes and load-balances them, RequestExecutor
	processes them and replies.

	@par Why a request fabric
	Plain @ref Messaging is fire-and-forget publish/subscribe; many interactions
	instead need an @e answer: "run this query", "fetch this status", "execute
	this command and tell me the result". The request system adds exactly that
	on top of the CMSDK network layer: correlation of each reply with its
	request, per-request timeouts, load balancing across multiple workers, and
	automatic reconnection/re-queuing when a gateway briefly disappears — all
	things you would otherwise hand-roll per application with raw sockets or an
	external RPC stack. Because payloads are ordinary DataMessage objects,
	the same request fabric also serves HTTP and WebSocket clients through the
	gateway without a separate web stack.

	@par The three roles
	- @b RequestClient (this header) — application-facing sender. Wraps each
	  outgoing DataMessage in a RequestReply "future"; the caller blocks on
	  RequestReply::waitForResult() or registers a RequestCallbackFunction.
	- @b RequestGateway (RequestGateway.h) — router/load balancer. Accepts
	  requests from many clients (binary, HTTP or WebSocket), queues them,
	  picks the best executor (short vs long requests, queue sizes, heartbeats)
	  and routes replies back to the originating client.
	- @b RequestExecutor (RequestExecutor.h) — the worker. Pulls requests from
	  its gateway connection, processes them and replies.

	@par A complete round-trip
	@code
	// Client side
	RequestClient client;
	client.addGateway(1, "gateway.host", 9000);        // register a gateway
	client.waitForConnection(5000);                    // wait up to 5 s

	DataMessage* msg = RequestClient::CreateRequestMessage(
		HTTP_GET, "status", "detail", "full");         // build the request payload
	RequestReply* reply = client.postRequest(msg);     // async send, returns a future
	if (reply->waitForResult(3000) == SUCCESS) {       // block up to 3 s
		DataMessage* answer = reply->peekReplyMessage(); // still owned by reply
		// ... read fields from answer ...
	}
	client.finishRequest(reply);                       // release slot + reply message
	@endcode
	On the worker side a RequestExecutor connects to the same gateway, receives
	the DataMessage, and answers with a reply message that the gateway routes
	back to this client, completing the RequestReply.

	@note Ownership: the reply DataMessage returned by peekReplyMessage() is
	owned by the RequestReply — do not delete it; it is released by
	finishRequest(). Take a copy if you need the data afterwards.

	@warning Always pair postRequest() with finishRequest(), even on timeout or
	failure, or the client's request slots leak. Choose timeouts that cover
	gateway queuing plus executor processing time, not just network latency.

	@see RequestClient, RequestReply, RequestGateway, RequestExecutor, @ref Messaging, @ref Networking
*/
#if !defined(_REQUESTCLIENT_H_)
#define _REQUESTCLIENT_H_

#pragma once

#include "NetworkManager.h"
#include "MovingAverage.h"
#include "jsmn.h"

namespace	cmlabs{

/////////////////////////////////////////////////////////////
// Request Reply
/////////////////////////////////////////////////////////////

class RequestReply;
/*! Signature for asynchronous completion callbacks registered on a RequestReply.
	Invoked on an internal client thread when the request completes (any status).
	@ingroup Requests */
typedef void (* RequestCallbackFunction)(RequestReply& reply);

/*! Lifecycle states of a request as it moves through client, gateway and executor.
	Terminal states (SUCCESS and the failure states) make RequestReply::isComplete() true.
	@ingroup Requests */
enum RequestStatus { NONE = 0, IDLE = 1, QUEUED = 2, PROCESSING = 3, SENT = 4, SUCCESS = 5, FAILED = 6, TIMEOUT = 7, TOOBUSY = 8, LOCALERROR = 9, NETWORKERROR = 10, SERVERERROR = 11};

/*! Future/handle for one in-flight request: holds the request message, the
	eventual reply, status, timing and routing references.

	Created by RequestClient::postRequest() (or internally by the gateway).
	The caller waits with waitForResult()/waitForMessage() or registers a
	RequestCallbackFunction, then returns the object to the client with
	RequestClient::finishRequest() — do not delete it directly when it came
	from a client, as objects are pooled/reused (::isInUse).
	@par Thread-safety
	Internally synchronised (mutex + semaphore); the completing network
	thread calls replyToRequest() while the application thread blocks in
	waitForResult().
	@ingroup Requests */
class RequestReply {
protected:
	// stats?
	RequestCallbackFunction callback;
	RequestStatus status;
	DataMessage* replyMsg;
	DataMessage* requestMsg;
	utils::Mutex mutex;
	utils::Semaphore semaphore;

public:
	RequestReply() { requestMsg = replyMsg = NULL; callback = NULL; status = IDLE; systemID = 0; startTime = GetTimeNow(); origin = processor = clientRef = gatewayRef = execRef = finishTime = customRef = 0; isLongReq = false; isInUse = true; }
	virtual ~RequestReply() { delete(requestMsg); delete(replyMsg); callback = NULL; status = IDLE; }

	uint64 startTime;	/*!< When the request was created (ms epoch). */
	uint64 finishTime;	/*!< When a terminal status was set (ms epoch; 0 while pending). */
	uint64 origin;		/*!< Originating endpoint/connection (packed uint64), for reply routing. */
	uint64 processor;	/*!< Executor id that processed (or is processing) the request. */
	uint64 clientRef;	/*!< Client-side reference id correlating request and reply. */
	uint64 execRef;		/*!< Executor-side reference id. */
	uint64 gatewayRef;	/*!< Gateway-side reference id. */
	uint64 customRef;	/*!< Free-form reference for application use. */
	uint32 systemID;	/*!< Id of the system/gateway this request belongs to. */
	bool isLongReq;		/*!< True when classified as a long-running request (separate queue/limits). */
	bool isInUse;		/*!< Pooling flag: false once finishRequest() releases the object for reuse. */

	/*! @return true once a terminal status (SUCCESS or any failure) has been set. */
	bool isComplete();
	/*! Set the current lifecycle status (wakes waiters when terminal).
		@param status New status. @return true on success. */
	bool setStatus(RequestStatus status);
	/*! @return The current lifecycle status. */
	RequestStatus getStatus();
	/*! @return Human-readable name of the current status (e.g. "Success"). */
	std::string getStatusText();
	/*! Register a completion callback (alternative to blocking waits).
		@param callback Invoked on an internal thread when the request completes.
		@return true on success. */
	bool setCallback(RequestCallbackFunction callback);
	/*! @return The registered callback, or NULL. */
	RequestCallbackFunction getCallback();

	/*! Complete this request with a reply (called by the network layer).
		@param msg Reply message (ownership transfers to this object; may be NULL on failure).
		@param status Terminal status to set.
		@return true on success. Wakes any waiter and fires the callback. */
	bool replyToRequest(DataMessage* msg, RequestStatus status);

	/*! Attach the outgoing request message, transferring ownership.
		@param msg Request message (owned by this object afterwards). @return true. */
	bool giveRequestMessage(DataMessage* msg);
	/*! Attach a copy of the outgoing request message (caller keeps @p msg).
		@param msg Message to copy. @return true on success. */
	bool setRequestMessageCopy(DataMessage* msg);
	/*! @return A new copy of the request message (caller owns it), or NULL. */
	DataMessage* getRequestMessageCopy();
	/*! @return The internal request message without transferring ownership (may be NULL). */
	DataMessage* peekRequestMessage();
	/*! Attach the reply message, transferring ownership. @param msg Reply. @return true. */
	bool giveReplyMessage(DataMessage* msg);
	/*! Attach a copy of the reply message (caller keeps @p msg). @return true on success. */
	bool setReplyMessageCopy(DataMessage* msg);
	/*! @return A new copy of the reply message (caller owns it), or NULL. */
	DataMessage* getReplyMessageCopy();
	/*! @return The internal reply message without transferring ownership (may be NULL).
		@warning The pointer remains owned by this RequestReply; it becomes
		dangling as soon as RequestClient::finishRequest() is called on the
		reply. Copy any data you need first (or use getReplyMessageCopy()). */
	DataMessage* peekReplyMessage();

	/*! Block until the request reaches a terminal status or the timeout expires.
		@param timeoutMS Max wait in milliseconds.
		@return The status at return time (TIMEOUT if still pending). Blocking. */
	RequestStatus waitForResult(uint32 timeoutMS);
	/*! Block for the reply message itself.
		@param timeoutMS Max wait in milliseconds.
		@param takeMessage Transfer ownership of the reply to the caller when true.
		@return The reply message, or NULL on timeout/failure. Blocking.
		@warning Ownership is the classic footgun here. With the default
		@p takeMessage = false the returned pointer is still owned by this
		RequestReply and is deleted by RequestClient::finishRequest() (which
		deletes the whole reply object) — never delete it yourself and never
		use it after finishRequest(). With @p takeMessage = true ownership
		transfers to the caller, who must @c delete the message exactly once;
		the reply's internal pointer is cleared, so a second call returns NULL. */
	DataMessage* waitForMessage(uint32 timeoutMS, bool takeMessage = false);

	/*! @return Total request duration in microseconds (finish - start). */
	uint64 getRequestDuration();
	/*! @return Total request duration in milliseconds. */
	uint32 getRequestDurationMS();
};


/////////////////////////////////////////////////////////////
// Request Queue  (implemented in RequestGateway.cpp)
/////////////////////////////////////////////////////////////

/*! Priority-ordered queue of pending RequestReply objects (per executor connection).

	Used by RequestGateway to hold requests destined for one executor,
	separately for short and long requests. Supports priority insertion,
	timeout scanning and bulk take-over when an executor dies (its queue is
	redistributed to the survivors). Implemented in RequestGateway.cpp.
	@par Thread-safety
	All operations are guarded by an internal mutex.
	@ingroup Requests */
class RequestQueue {
public:
	/*! Self-test of queue ordering/priority behaviour. @return true on success. */
	static bool UnitTest();

	RequestQueue();
	virtual ~RequestQueue();

	/*! Initialise the queue. @param id Owner id used for logging. @return true. */
	bool init(uint32 id);

	/*! @return Number of requests currently queued. */
	uint32 getCount() { return (uint32)requestQ.size(); }

	/*! Insert a request, ordered by @p priority (higher = closer to the front).
		@param req Request (queue does not take ownership of the pooled object).
		@param priority Ordering key; 0 appends in FIFO order.
		@return true on success. */
	bool addRequest(RequestReply* req, double priority = 0);
	/*! Pop the next request to dispatch. @return The request, or NULL when empty. */
	RequestReply* getNextRequest();
	/*! Find and remove the next request whose deadline has passed.
		@return The timed-out request, or NULL when none. */
	RequestReply* getNextTimedoutRequest();
	// RequestReply* getRequest(uint64 ref);
	/*! Mark a request as done and remove it from tracking.
		@param req The completed request. @return true if found. */
	bool completeRequest(RequestReply* req);
	//uint32 getActiveRequestCount();

	/*! Fail and remove requests older than @p ttlMS milliseconds.
		@param ttlMS Age limit. @return Number of requests removed. */
	uint32 removeStaleRequests(uint32 ttlMS);
	/*! Atomically take all queued requests (used to redistribute a dead
		executor's backlog). @return The removed list; queue is left empty. */
	std::list<RequestReply*> takeQueue();

	/*! @return Queue contents summarised as XML (for PsyProbe/diagnostics). */
	std::string toXML();
	/*! @return Queue contents summarised as JSON (for diagnostics APIs). */
	std::string toJSON();

protected:
	std::list<RequestReply*> requestQ;	/*!< The ordered pending requests. */
	utils::Mutex mutex;					/*!< Guards ::requestQ. */
	uint32 id;							/*!< Owner id for logging. */
};


/////////////////////////////////////////////////////////////
// Request Connection
/////////////////////////////////////////////////////////////

/*! Gateway-side record of one connected client or executor.

	Tracks the network connection, last-seen/failure times, the peer's
	self-reported queue sizes (from status heartbeats) and per-connection
	short/long request queues plus latency statistics. The gateway's
	load-balancing decision (RequestGateway::getBestExecutorID()) is based
	on these fields.
	@ingroup Requests */
struct RequestConnection {
	uint64 lastConTime;				/*!< Last successful connect/heartbeat time (ms epoch). */
	uint64 lastFailTime;			/*!< Last connection failure time (ms epoch). */
	uint64 conID;					/*!< NetworkManager connection id. */
	uint64 location;				/*!< Remote endpoint packed as uint64. */
	uint64 lastStatusTime;			/*!< When the peer last reported its status (ms epoch). */
	uint32 reportedShortReqQSize;	/*!< Peer-reported short-request queue size. */
	uint32 reportedLongReqQSize;	/*!< Peer-reported long-request queue size. */
	uint32 longReqQProcessingSize;	/*!< Peer-reported long requests currently processing. */
	uint32 count;					/*!< Total requests routed via this connection. */
	RequestQueue shortReqQueue;		/*!< Gateway-side queue of short requests for this peer. */
	RequestQueue longReqQueue;		/*!< Gateway-side queue of long requests for this peer. */
	MovingAverage shortAvgStats;	/*!< Rolling latency stats for short requests. */
	MovingAverage longAvgStats;		/*!< Rolling latency stats for long requests. */
	/*! Reset all counters/ids to the disconnected state. */
	void clear() { lastConTime=lastFailTime=conID=location=lastStatusTime=0; reportedShortReqQSize = reportedLongReqQSize = longReqQProcessingSize = count = 0; }
	/*! Fail and drop requests older than @p ttlMS from both queues.
		@param ttlMS Age limit in ms. @return Number of requests removed. */
	uint32 removeStaleRequests(uint32 ttlMS);
};

/*! Client/executor-side record of one gateway it connects to.

	A RequestClient or RequestExecutor can be configured with several
	gateways (addGateway()) for redundancy; each gets one of these records
	holding the address and current connection state.
	@ingroup Requests */
struct RequestGatewayConnection {
	uint32 id;				/*!< Gateway id (configuration key). */
	uint64 lastConTime;		/*!< Last successful connect time (ms epoch). */
	uint64 lastFailTime;	/*!< Last failed connect time (ms epoch; drives retry backoff). */
	std::string addr;		/*!< Gateway host name or IP. */
	uint16 port;			/*!< Gateway port. */
	uint64 conID;			/*!< NetworkManager connection id (0 when disconnected). */
	uint64 location;		/*!< Resolved endpoint packed as uint64. */
	uint8 encryption;		/*!< ::NOENC or ::SSLENC for this link. */
	/*! Reset to the unconfigured/disconnected state. */
	void clear() { lastConTime=lastFailTime=conID=0; port=0; id=0; addr.clear(); conID=location=0; encryption = NOENC; }
};

/*! Application-facing client of the request system: posts DataMessage
	requests to one or more RequestGateways and delivers replies.

	Owns its own NetworkManager/NetworkChannel and a worker thread that
	maintains gateway connections (with automatic reconnection and periodic
	status messages), drains the outgoing queue and matches incoming replies
	to pending RequestReply objects via reference ids. See the file header
	for a full round-trip example.
	@par Ownership
	RequestReply objects returned by postRequest() remain owned by the
	client; release them with finishRequest(). DataMessages passed to
	postRequest() are taken over by the reply object.
	@par Thread-safety
	All public methods may be called from any thread.
	@ingroup Requests */
class RequestClient : public Runnable, public NetworkReceiver {
public:
	friend THREAD_RET THREAD_FUNCTION_CALL RequestClientRun(THREAD_ARG arg);

	/*! Manual test: log in to a request server and issue a request.
		@param address Gateway host. @param port Gateway port.
		@param username Login user. @param password Login password.
		@param reqAfterLogin Request string to send after login.
		@return true on success. */
	static bool TestServerLogin(const char* address, uint16 port, const char* username, const char* password, const char* reqAfterLogin);

	/*! Convenience: post @p msg on @p client, wait, and return the reply's JSON text.
		@param client Client to use. @param msg Request (ownership transfers).
		@param timeoutMS Max wait in ms. @return JSON string, empty on failure. Blocking. */
	static std::string SendRequestAndWaitForJSON(RequestClient* client, DataMessage* msg, int timeoutMS);
	/*! Convenience: post @p msg, wait, and return the reply message.
		@param client Client to use. @param msg Request (ownership transfers).
		@param timeoutMS Max wait in ms. @return Reply owned by the caller, or NULL. Blocking. */
	static DataMessage* SendRequestAndWaitForReply(RequestClient* client, DataMessage* msg, int timeoutMS);
	/*! Convenience: post @p msg, wait, and return the whole RequestReply object.
		@param client Client to use. @param msg Request (ownership transfers).
		@param timeoutMS Max wait in ms.
		@return The reply object (release with finishRequest()), or NULL. Blocking. */
	static RequestReply* SendRequestAndWaitForReplyObject(RequestClient* client, DataMessage* msg, int timeoutMS);
	/*! Extract a named top-level string value from a JSON reply.
		@param json JSON text. @param name Key to find. @return The value, or empty. */
	static std::string GetJSONReplyParameter(const char* json, const char* name);
	/*! Build a request DataMessage from an operation, request name and up to 20
		key/value string pairs (NULL-terminated usage; pass pairs only as needed).
		@param operation HTTP_*-style operation id. @param req Request name/path.
		@param key1,key2,key3,key4,key5,key6,key7,key8,key9,key10,key11,key12,key13,key14,key15,key16,key17,key18,key19,key20 Optional pair keys (NULL = unused).
		@param val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12,val13,val14,val15,val16,val17,val18,val19,val20 Optional pair values (NULL = unused).
		@return A new DataMessage owned by the caller. */
	static DataMessage* CreateRequestMessage(uint8 operation, const char* req,
		const char* key1 = NULL, const char* val1 = NULL,
		const char* key2 = NULL, const char* val2 = NULL,
		const char* key3 = NULL, const char* val3 = NULL,
		const char* key4 = NULL, const char* val4 = NULL,
		const char* key5 = NULL, const char* val5 = NULL,
		const char* key6 = NULL, const char* val6 = NULL,
		const char* key7 = NULL, const char* val7 = NULL,
		const char* key8 = NULL, const char* val8 = NULL,
		const char* key9 = NULL, const char* val9 = NULL,
		const char* key10 = NULL, const char* val10 = NULL,
		const char* key11 = NULL, const char* val11 = NULL,
		const char* key12 = NULL, const char* val12 = NULL,
		const char* key13 = NULL, const char* val13 = NULL,
		const char* key14 = NULL, const char* val14 = NULL,
		const char* key15 = NULL, const char* val15 = NULL,
		const char* key16 = NULL, const char* val16 = NULL,
		const char* key17 = NULL, const char* val17 = NULL,
		const char* key18 = NULL, const char* val18 = NULL,
		const char* key19 = NULL, const char* val19 = NULL,
		const char* key20 = NULL, const char* val20 = NULL
		);

	RequestClient();
	virtual ~RequestClient();

	/*! @return true when at least one gateway connection is currently up. */
	bool isConnected();
	/*! Block until a gateway connection is established.
		@param timeoutMS Max wait in ms. @return true if connected in time. Blocking. */
	bool waitForConnection(uint32 timeoutMS);
	/*! Force reconnection of gateway links. @return true if reconnection was initiated. */
	bool reconnect();
	/*! Register a gateway to connect to (may be called several times for redundancy).
		@param id Gateway id. @param addr Host. @param port Port.
		@param encryption ::NOENC or ::SSLENC. @return true on success. */
	bool addGateway(uint32 id, std::string addr, uint16 port, uint8 encryption = NOENC);

	/*! Post a request asynchronously.
		Assigns the next client reference id to @p msg (used to match the
		incoming reply), wraps it in a pooled RequestReply and queues it for
		the worker thread to dispatch to the best-connected gateway.
		@param msg Request message (ownership transfers).
		@return A RequestReply future owned by the client — wait on it, then
		release it with finishRequest(). NULL on local failure. Non-blocking.
		@warning finishRequest() deletes the RequestReply @e and both messages
		inside it (request and reply). Any pointer obtained via peek*() or
		waitForMessage(..., false) is invalid afterwards.
		A complete round-trip, taking ownership of the reply:
		@code
		DataMessage* msg = RequestClient::CreateRequestMessage(HTTP_GET, "status");
		RequestReply* reply = client.postRequest(msg);      // msg now owned by reply
		if (reply) {
		    DataMessage* answer = reply->waitForMessage(3000, true); // take ownership
		    client.finishRequest(reply);                    // reply object gone now
		    if (answer) {
		        // ... use answer ...
		        delete answer;                              // caller must delete
		    }
		}
		@endcode */
	RequestReply* postRequest(DataMessage *msg);
	/*! Post a request with a completion callback (fire-and-forget style).
		@param msg Request message (ownership transfers).
		@param callback Invoked on an internal thread upon completion.
		@param timeoutMS Request timeout in ms. @return true if queued.
		@warning Not yet implemented: the current implementation is a stub that
		always returns false without taking ownership of @p msg. Use
		postRequest(DataMessage*) plus RequestReply::setCallback(), or the
		blocking waits, instead. */
	bool postRequest(DataMessage *msg, RequestCallbackFunction callback, uint32 timeoutMS);
	/*! Release a RequestReply obtained from postRequest() back to the client's pool.
		@param reply The finished reply object. @return true on success.
		@warning Do not access @p reply after this call. */
	bool finishRequest(RequestReply* reply);

	/*! NetworkReceiver hook: gateway connect/disconnect events (drives reconnection). */
	bool receiveNetworkEvent(NetworkEvent* evt, NetworkChannel* channel, uint64 conid);
	/*! NetworkReceiver hook: reply messages from gateways, matched to pending requests. */
	bool receiveMessage(DataMessage* msg, NetworkChannel* channel, uint64 conid);
	/*! NetworkReceiver hook: HTTP replies (when talking to a gateway over HTTP). */
	bool receiveHTTPReply(HTTPReply* reply, HTTPRequest* req, NetworkChannel* channel, uint64 conid);

protected:
	NetworkManager* manager;		/*!< Owned network stack for gateway links. */
	NetworkChannel* channel;		/*!< Channel carrying the gateway connections. */
	std::list<RequestGatewayConnection> connections;	/*!< Configured gateways and their state. */
	utils::Mutex mutex;				/*!< Guards request bookkeeping. */
	utils::Mutex conMutex;			/*!< Guards ::connections. */
	utils::WaitQueuePointer<RequestReply*> outQ;	/*!< Outgoing requests awaiting dispatch. */
	std::map<uint64,RequestReply*> requestMap;		/*!< In-flight requests by client reference id. */
	uint32 threadID;				/*!< Worker thread id. */
	uint64 lastRefID;				/*!< Last issued client reference id. */
	uint64 sentCount;				/*!< Total requests sent. */
	uint64 receivedCount;			/*!< Total replies received. */
	MovingAverage avgStats;			/*!< Rolling round-trip statistics. */

	/*! Serialise and send one request to the best available gateway. @return true if sent. */
	bool sendRequest(RequestReply* reply);
	/*! Worker loop: connection upkeep, outgoing queue, timeouts. @return true to continue. */
	bool run();
	/*! Send a message on a specific gateway link. @return true if sent. */
	bool sendMessageToGateway(DataMessage* msg, RequestGatewayConnection& con);
	/*! Send a status/heartbeat message to all gateways. @return Number of gateways notified. */
	uint32 sendStatusNow();
};

/*! Thread entry point for the RequestClient worker loop. @ingroup Requests */
THREAD_RET THREAD_FUNCTION_CALL RequestClientRun(THREAD_ARG arg);

/*! Load-test client driving a RequestGateway with binary-protocol requests
	at a configurable frequency/payload (used by RequestGateway::UnitTest()).
	@ingroup Requests */
class TestRequestClient {
public:
	friend THREAD_RET THREAD_FUNCTION_CALL TestRequestClientRun(THREAD_ARG arg);
	TestRequestClient(uint32 id);
	bool init(uint16 port, uint32 gatewayCount, double sendfreq, int32 reconnectMS, uint32 payload, const char* longTestName);
	bool finishUp();
	bool isStillRunning() { return isRunning; }
protected:
	bool run();

	uint32 id;
	uint16 port;
	uint32 gatewayCount;
	double sendfreq;
	int32 reconnectMS;
	uint32 payload;
	std::string longTestName;
	bool shouldContinue;
	bool shouldFinishUp;
	bool isRunning;
};

THREAD_RET THREAD_FUNCTION_CALL TestRequestClientRun(THREAD_ARG arg);

/*! Load-test client driving a RequestGateway over plain HTTP web requests.
	@ingroup Requests */
class TestWebRequestClient {
public:
	friend THREAD_RET THREAD_FUNCTION_CALL TestWebRequestClientRun(THREAD_ARG arg);
	TestWebRequestClient(uint32 id);
	bool init(uint16 port, uint32 gatewayCount, double sendfreq, int32 reconnectMS, uint32 payload, const char* longTestName);
	bool finishUp();
	bool isStillRunning() { return isRunning; }
protected:
	bool run();

	uint32 id;
	uint16 port;
	uint32 gatewayCount;
	double sendfreq;
	int32 reconnectMS;
	uint32 payload;
	std::string longTestName;
	bool shouldContinue;
	bool shouldFinishUp;
	bool isRunning;
};

THREAD_RET THREAD_FUNCTION_CALL TestWebRequestClientRun(THREAD_ARG arg);



/*! Load-test client driving a RequestGateway over WebSocket connections.
	@ingroup Requests */
class TestWebSocketRequestClient {
public:
	friend THREAD_RET THREAD_FUNCTION_CALL TestWebSocketRequestClientRun(THREAD_ARG arg);
	TestWebSocketRequestClient(uint32 id);
	bool init(uint16 port, uint32 gatewayCount, double sendfreq, int32 reconnectMS, uint32 payload, const char* longTestName);
	bool finishUp();
	bool isStillRunning() { return isRunning; }
protected:
	bool run();

	uint32 id;
	uint16 port;
	uint32 gatewayCount;
	double sendfreq;
	int32 reconnectMS;
	uint32 payload;
	std::string longTestName;
	bool shouldContinue;
	bool shouldFinishUp;
	bool isRunning;
};

THREAD_RET THREAD_FUNCTION_CALL TestWebSocketRequestClientRun(THREAD_ARG arg);


}

#endif // _REQUESTCLIENT_H_
