/**
 *  Class describing a mid-level Amqp connection
 * 
 *  @copyright 2014 - 2018 Copernica BV
 */

/**
 *  Include guard
 */
#pragma once

/**
 *  Set up namespace
 */
namespace AMQP {

/**
 *  Class definition
 */
class Connection
{
private:
    /**
     *  The actual implementation
     *  @var    ConnectionImpl
     */
    ConnectionImpl _implementation;

public:
    /**
     *  Construct an AMQP object based on full login data
     * 
     *  The first parameter is a handler object. This handler class is
     *  an interface that should be implemented by the caller.
     * 
     *  @param  handler         Connection handler
     *  @param  login           Login data
     *  @param  vhost           Vhost to use
     */
    Connection(ConnectionHandler *handler, const Login &login, const std::string &vhost) : _implementation(this, handler, login, vhost) {}

    /**
     *  Construct with default vhost
     *  @param  handler         Connection handler
     *  @param  login           Login data
     */
    Connection(ConnectionHandler *handler, const Login &login) : _implementation(this, handler, login, "/") {}

    /**
     *  Construct an AMQP object with default login data and default vhost
     *  @param  handler         Connection handler
     */
    Connection(ConnectionHandler *handler, const std::string &vhost) : _implementation(this, handler, Login(), vhost) {}

    /**
     *  Construct an AMQP object with default login data and default vhost
     *  @param  handler         Connection handler
     */
    Connection(ConnectionHandler *handler) : _implementation(this, handler, Login(), "/") {}

    /**
     *  No copy'ing, we do not support having two identical connection objects
     *  @param  connection
     */
    Connection(const Connection &connection) = delete;

    /**
     *  Destructor
     */
    virtual ~Connection() {}

    /**
     *  No assignments of other connections
     *  @param  connection
     *  @return Connection
     */
    Connection &operator=(const Connection &connection) = delete;
    
    /**
     *  Retrieve the login data
     *  @return Login
     */
    const Login &login() const
    {
        return _implementation.login();
    }

    /**
     *  Retrieve the vhost
     *  @return string
     */
    const std::string &vhost() const
    {
        return _implementation.vhost();
    }

    /**
     *  Send a ping/heartbeat to the channel to keep it alive
     *  @return bool
     */
    bool heartbeat()
    {
        return _implementation.heartbeat();
    }

    /**
     *  Parse data that was recevied from RabbitMQ
     *  
     *  Every time that data comes in from RabbitMQ, you should call this method to parse
     *  the incoming data, and let it handle by the AMQP library. This method returns the number
     *  of bytes that were processed.
     *
     *  If not all bytes could be processed because it only contained a partial frame, you should
     *  call this same method later on when more data is available. The AMQP library does not do
     *  any buffering, so it is up to the caller to ensure that the old data is also passed in that
     *  later call.
     *
     *  @param  buffer      buffer to decode
     *  @param  size        size of the buffer to decode
     *  @return             number of bytes that were processed
     */
    uint64_t parse(const char *buffer, size_t size)
    {
        return _implementation.parse(ByteBuffer(buffer, size));
    }

    /**
     *  Parse data that was recevied from RabbitMQ
     *  
     *  Every time that data comes in from RabbitMQ, you should call this method to parse
     *  the incoming data, and let it handle by the AMQP library. This method returns the number
     *  of bytes that were processed.
     *
     *  If not all bytes could be processed because it only contained a partial frame, you should
     *  call this same method later on when more data is available. The AMQP library does not do
     *  any buffering, so it is up to the caller to ensure that the old data is also passed in that
     *  later call.
     * 
     *  This method accepts a buffer object. This is an interface that is defined by the AMQP
     *  library, that can be implemented by you to allow faster access to a buffer.
     *
     *  @param  buffer      buffer to decode
     *  @return             number of bytes that were processed
     */
    uint64_t parse(const Buffer &buffer)
    {
        return _implementation.parse(buffer);
    }
    
    /**
     *  Report that the connection was lost in the middle of an operation
     * 
     *  The AMQP protocol normally has a nice closing handshake, and a connection 
     *  is elegantly closed via calls to the close() and parse() methods. The parse() 
     *  methods recognizes the close-confirmation and will report this to the handler. 
     *  However, if you notice yourself that the connection is lost in the middle of 
     *  an operation (for example due to a crashing RabbitMQ server), you should 
     *  explicitly tell the connection object about it, so that it can cancel all 
     *  pending operations. For all pending operations the error and finalize callbacks
     *  will be called. The ConnectionHandler::onError() method will however _not_ be
     *  called.
     * 
     *  @param  message     the message that has to be passed to all error handlers
     *  @return bool        false if the connection already was failed
     */
    bool fail(const char *message)
    {
        return _implementation.fail(message);
    }
    
    /**
     *  Max frame size
     *  
     *  If you allocate memory to receive data that you are going to pass to the parse() method,
     *  it might be useful to have an insight in the max frame size. The parse() method process
     *  one frame at a time, so you must at least be able to read in buffers of this specific
     *  frame size.
     * 
     *  @return size_t
     */
    uint32_t maxFrame() const
    {
        return _implementation.maxFrame();
    }
    
    /**
     *  Expected number of bytes for the next parse() call.
     * 
     *  This method returns the number of bytes that the next call to parse() at least expects to 
     *  do something meaningful with it.
     * 
     *  @return size_t
     */
    uint32_t expected() const
    {
        return _implementation.expected();
    }
    
    /**
     *  Is the connection ready to accept instructions / has passed the login handshake and not closed?
     *  @return bool
     */
    bool ready() const
    {
        return _implementation.ready();
    }

    /**
     *  Is (or was) the connection initialized
     *  @return bool
     */
    bool initialized() const
    {
        return _implementation.initialized();
    }
    
    /**
     *  Is the connection in a usable state, or is it already closed or
     *  in the process of being closed?
     *  @return bool
     */
    bool usable() const
    {
        return _implementation.usable();
    }

    /**
     *  Close the connection
     *  This will close all channels
     *  @return bool
     */
    bool close()
    {
        return _implementation.close();
    }

    /**
     *  Retrieve the number of channels that are active for this connection
     *  @return std::size_t
     */
    std::size_t channels() const
    {
        return _implementation.channels();
    }
    
    /**
     *  Is the connection busy waiting for an answer from the server? (in the
     *  meantime you can already send more instructions over it)
     *  @return bool
     */
    bool waiting() const
    {
        return _implementation.waiting();
    }

    /**
     *  Some classes have access to private properties
     */
    friend class ChannelImpl;
};

/**
 *  End of namespace
 */
}

