#ifndef FACT_Connection
#define FACT_Connection

#include <deque>
#include <string>
#include <boost/asio.hpp>
#include <boost/function.hpp>
#include <boost/asio/deadline_timer.hpp>

#include "MessageImp.h"

class Connection : public MessageImp, public boost::asio::ip::tcp::socket
{
private:
    MessageImp *fLog;

    std::string fAddress;
    std::string fPort;

    bool fDebugTx;

    enum ConnectionStatus_t
    {
        kDisconnected = 0,
        kConnecting   = 1,
        kConnected    = 2,
    };

protected:
    boost::asio::deadline_timer   fInTimeout;

private:
    boost::asio::deadline_timer   fOutTimeout;
    boost::asio::deadline_timer   fConnectionTimer;
    std::deque<std::vector<char>> fOutQueue;

    ConnectionStatus_t fConnectionStatus;

    std::string fErrConnect;
    std::string fMsgConnect;

public:
    void SetLogStream(MessageImp *log) { fLog = log; }
    std::ostream &Out() { return fLog ? fLog->Out() : Out(); }

    // -------- Abbreviations for starting async tasks ---------

    void AsyncRead(const boost::asio::mutable_buffers_1 buffers, int type=0);
    void AsyncWrite(const boost::asio::const_buffers_1 &buffers);
    void AsyncWait(boost::asio::deadline_timer &timer, int millisec,
                   void (Connection::*handler)(const boost::system::error_code&));

private:
    void AsyncConnect(boost::asio::ip::tcp::resolver::iterator iterator);

    void CloseImp(bool restart=true);

    void ConnectImp(const boost::system::error_code& error,
                    boost::asio::ip::tcp::resolver::iterator endpoint_iterator);

    void HandleConnectionTimer(const boost::system::error_code &error);
    void HandleWriteTimeout(const boost::system::error_code &error);
    void HandleSentData(const boost::system::error_code& error, size_t);

    int Write(const Time &t, const std::string &txt, int qos=kInfo);

    virtual void ConnectionEstablished() { }

public:
    Connection(boost::asio::io_service& io_service, std::ostream &out);

    // ------------------------ connect --------------------------

    void SetEndpoint(const std::string &addr, int port);
    void SetEndpoint(const std::string &addr, const std::string &port);
    void SetEndpoint(const std::string &addr);

    void StartConnect();

    // ------------------------ close --------------------------
    void PostClose(bool restart=true);

    // ------------------------ write --------------------------
    void SendMessageImp(const std::vector<char> msg);
    void PostMessage(const void *msg, size_t s=0);
    void PostMessage(const std::string &cmd, size_t s=-1);

    template<typename T, size_t N>
        void PostMessage(const boost::array<T, N> &msg)
    {
        PostMessage(msg.begin(), msg.size()*sizeof(T));
    }

    template<typename T>
        void PostMessage(const std::vector<T> &msg)
    {
        PostMessage(&msg[0], msg.size()*sizeof(T));
    }

    // ------------------------ others --------------------------

    virtual void HandleReceivedData(const boost::system::error_code&, size_t, int = 0) { }
    virtual void HandleReadTimeout(const boost::system::error_code&) { }

    int IsClosed() const { return !is_open(); }

    bool IsDisconnected() const { return fConnectionStatus==kDisconnected; }
    bool IsConnected()  const   { return fConnectionStatus==kConnected;    }
    bool IsConnecting() const   { return fConnectionStatus==kConnecting;   }

    void SetDebugTx(bool b=true) { fDebugTx=b; }

    std::string URL() const { return fAddress + ":" + fPort; }
};

#endif
