//+------------------------------------------------------------------+
//|                                                   PipeClient.mqh |
//+------------------------------------------------------------------+
#ifndef PIPECLIENT_MQH
#define PIPECLIENT_MQH

#include "Message.mqh"

//--- client access and open constants
#define GENERIC_READ            0x80000000
#define GENERIC_WRITE           0x40000000
#define OPEN_EXISTING           3
#define CLIENT_INVALID_HANDLE   (-1)

//--- Windows named pipe client API
#import "kernel32.dll"
long CreateFileW(string name, uint desired_access, uint share_mode, long security,
                 uint creation, uint flags, long template_file);
int  ReadFile(long handle, uchar &buffer[], uint bytes_to_read, uint &bytes_read, long overlapped);
int  WriteFile(long handle, uchar &buffer[], uint bytes_to_write, uint &bytes_written, long overlapped);
int  CloseHandle(long handle);
uint GetLastError(void);
#import

//+------------------------------------------------------------------+
//| CPipeClient                                                      |
//| Wraps the Windows named pipe client API for a slave EA. Opens an |
//| existing pipe by name and exchanges typed messages with the      |
//| broker EA using the CMessage serialization.                      |
//+------------------------------------------------------------------+
class CPipeClient
  {
private:
   string            m_pipe_name;
   long              m_handle;
   bool              m_connected;
   uint              m_last_error;

public:
                     CPipeClient(void);
                    ~CPipeClient(void);

   bool              Connect(const string pipe_name);
   bool              SendMessage(const CMessage &msg);
   bool              ReceiveMessage(CMessage &msg);
   void              Close(void);
   bool              IsConnected(void) const { return(m_connected); }
   uint              LastError(void)   const { return(m_last_error); }
  };

//+------------------------------------------------------------------+
//| Constructor: Marks the handle invalid and the client             |
//| unconnected until Connect succeeds.                              |
//+------------------------------------------------------------------+
CPipeClient::CPipeClient(void)
  {
//--- start from a known idle state
   m_pipe_name  = "";
   m_handle     = CLIENT_INVALID_HANDLE;
   m_connected  = false;
   m_last_error = 0;
  }

//+------------------------------------------------------------------+
//| Destructor: Closes the pipe handle if it is still open.          |
//+------------------------------------------------------------------+
CPipeClient::~CPipeClient(void)
  {
//--- release the handle on destruction
   Close();
  }

//+------------------------------------------------------------------+
//| Connect                                                          |
//| Opens the existing named pipe by name with read and write        |
//| access. Returns true on success; a failure usually means the     |
//| broker EA has not created the endpoint yet.                      |
//+------------------------------------------------------------------+
bool CPipeClient::Connect(const string pipe_name)
  {
//--- open the existing pipe for duplex access
   m_pipe_name = pipe_name;
   uint access = GENERIC_READ | GENERIC_WRITE;
   m_handle = ::CreateFileW(m_pipe_name, access, 0, 0, OPEN_EXISTING, 0, 0);
//--- an invalid handle means the server is not ready
   if(m_handle == CLIENT_INVALID_HANDLE)
     {
      m_last_error = ::GetLastError();
      m_connected  = false;
      return(false);
     }
//--- the client is now attached to the endpoint
   m_connected = true;
   return(true);
  }

//+------------------------------------------------------------------+
//| SendMessage                                                      |
//| Serializes msg to a byte array and writes it to the pipe.        |
//| Returns true when WriteFile confirms all bytes were written.     |
//+------------------------------------------------------------------+
bool CPipeClient::SendMessage(const CMessage &msg)
  {
//--- a connected pipe is required
   if(m_handle == CLIENT_INVALID_HANDLE || !m_connected)
      return(false);
//--- serialize the message to a flat byte array before writing
   uchar buf[];
   msg.Serialize(buf);
//--- write the full buffer to the named pipe in one call
   uint bytes_written = 0;
   int  ok = ::WriteFile(m_handle, buf, MESSAGE_BYTE_SIZE, bytes_written, 0);
   if(ok == 0 || bytes_written != (uint)MESSAGE_BYTE_SIZE)
     {
      m_last_error = ::GetLastError();
      ::PrintFormat("CPipeClient: SendMessage failed, wrote %d of %d bytes, error %d",
                    bytes_written, MESSAGE_BYTE_SIZE, m_last_error);
      return(false);
     }
   return(true);
  }

//+------------------------------------------------------------------+
//| ReceiveMessage                                                   |
//| Reads one serialized message from the pipe and deserializes it   |
//| into msg. Returns true only on a full, successfully decoded      |
//| message.                                                         |
//+------------------------------------------------------------------+
bool CPipeClient::ReceiveMessage(CMessage &msg)
  {
//--- a connected pipe is required
   if(m_handle == CLIENT_INVALID_HANDLE || !m_connected)
      return(false);
//--- read a full message-sized buffer from the pipe
   uchar buf[];
   ::ArrayResize(buf, MESSAGE_BYTE_SIZE);
   uint bytes_read = 0;
   int  ok = ::ReadFile(m_handle, buf, MESSAGE_BYTE_SIZE, bytes_read, 0);
//--- a short or failed read is not a valid message
   if(ok == 0 || bytes_read != (uint)MESSAGE_BYTE_SIZE)
     {
      m_last_error = ::GetLastError();
      ::PrintFormat("CPipeClient: ReceiveMessage failed, read %d of %d bytes, error %d",
                    bytes_read, MESSAGE_BYTE_SIZE, m_last_error);
      return(false);
     }
//--- decode the bytes into the typed message
   return(msg.Deserialize(buf));
  }

//+------------------------------------------------------------------+
//| Close                                                            |
//| Closes the client handle and marks the client unconnected.       |
//+------------------------------------------------------------------+
void CPipeClient::Close(void)
  {
//--- release the handle if it is open
   if(m_handle != CLIENT_INVALID_HANDLE)
     {
      ::CloseHandle(m_handle);
      m_handle = CLIENT_INVALID_HANDLE;
     }
   m_connected = false;
  }
  
#endif // PIPECLIENT_MQH
//+------------------------------------------------------------------+