#pragma once
#include <afxmt.h>
#include <afxsock.h>

// STL
#include <memory>

#include "ConcurrentQueue.h"

#ifdef BUILD_COMMDLL
#define COMMDLL_DLLCLASS __declspec(dllexport)
#else
#define COMMDLL_DLLCLASS __declspec(dllimport)
#endif

class IBufferSocketCallBack
{
public:
    virtual void OnReceive(const char* buffer, int len) = 0;
    virtual void OnDisconnect(int errorCode)            = 0;
};

class COMMDLL_DLLCLASS CBufferedSocket
{
    // Forward declaration of internal classes
public:
    class CBufferedSocketMessage;
    class CBufferMessageThread;
    class CReceivingThread;
    class CSenderThread;
    class CInternalMessageThread;
    friend class CInternalMessageThread;
    friend class CReceivingThread;
    friend class CSenderThread;

    // Private fields
private:
    std::unique_ptr<CConcurrentQueue<CBufferedSocketMessage>> m_pMessageQueue;
    std::unique_ptr<CConcurrentQueue<CBufferedSocketMessage>> m_pSendQueue;

    std::unique_ptr<CBufferMessageThread>  m_pMessageThread;
    std::unique_ptr<CBufferMessageThread>  m_PSenderThread;
    std::unique_ptr<CReceivingThread>      m_pReceivingThread;
    std::unique_ptr<CEvt>                  m_hConnectedEvent;

    IBufferSocketCallBack* m_pCallBack{ nullptr };

    SOCKET m_socket{ 0 };
    bool   m_bIsConnected{ false };

    // Private methods
protected:
    void OnReceive(const char* buf, size_t len);
    void OnDisconnect(int errorCode);
    int  Read(char* buf, size_t size);
    int  Write(const char* buf, size_t len);

private:
    CBufferedSocket(const CBufferedSocket&);
    // Public methods
public:
    CBufferedSocket(IBufferSocketCallBack* pCallBack);
    ~CBufferedSocket(void);

    void ProcessMessage(const CBufferedSocketMessage& message);
    void Close();
    bool Connect(const char* IpAddress, int port);
    bool Send(const char* buf, size_t len, int milliSeconds = INFINITE);
    bool IsConnected()
    {
        return m_bIsConnected;
    }

    bool WaitConnected();
    bool WaitConnected(int milliseconds);
    /* returns the percentage representing how full the send queue is */
    double SendQueueFullState();
};
