/**
 *  Class describing initial connection setup
 * 
 *  This frame is sent by the server to the client, right after the connection
 *  is opened. It contains the initial connection properties, and the protocol
 *  number.
 * 
 *  @copyright 2014 - 2023 Copernica BV
 */

/**
 *  Dependencies
 */
#include "programname.h"
#if !defined(_WIN32) && !defined(_WIN64)
#include "platformname.h"
#endif

/**
 *  Cause we want to print out version string that is passed to compiled with -D
 *  flag. Why 2 macros? https://www.guyrutenberg.com/2008/12/20/expanding-macros-into-string-constants-in-c/
 */
#define STR_EXPAND(s) #s
#define STR(s) STR_EXPAND(s)

/**
 *  The version and distro names
 */
#define VERSION_NAME    STR(VERSION)

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

/**
 *  Class implementation
 */
class ConnectionStartFrame : public ConnectionFrame
{
private:
    /**
     *  Major AMQP version number
     *  @var uint8_t
     */
    uint8_t _major;

    /**
     *  Minor AMQP version number
     *  @var uint8_t
     */
    uint8_t _minor;

    /**
     *  Additional server properties
     *  @note:  exact properties are not specified
     *          and are implementation-dependent
     *  @var Table
     */
    Table _properties;
    
    /**
     *  Available security mechanisms
     *  @var LongString
     */
    LongString _mechanisms;

    /**
     *  Available message locales
     *  @var LongString
     */
    LongString _locales;

protected:
    /**
     *  Encode a frame on a string buffer
     *
     *  @param  buffer  buffer to write frame to
     */
    virtual void fill(OutBuffer& buffer) const override
    {
        // call base
        ConnectionFrame::fill(buffer);

        // encode all fields
        buffer.add(_major);
        buffer.add(_minor);
        _properties.fill(buffer);
        _mechanisms.fill(buffer);
        _locales.fill(buffer);
    }

public:
    /**
     *  Client-side constructer for a connection start frame
     *
     *  @param  major       major protocol version
     *  @param  minor       minor protocol version
     *  @param  properties  server properties
     *  @param  mechanisms  available security mechanisms
     *  @param  locales     available locales
     */
    ConnectionStartFrame(uint8_t major, uint8_t minor, const Table& properties, const std::string& mechanisms, const std::string& locales) :
        ConnectionFrame((uint32_t)(properties.size() + mechanisms.length() + locales.length() + 10)), // 4 for each longstring (size-uint32), 2 major/minor
        _major(major),
        _minor(minor),
        _properties(properties),
        _mechanisms(mechanisms),
        _locales(locales)
    {}

    /**
     *  Construct a connection start frame from a received frame
     *
     *  @param  frame   received frame
     */
    ConnectionStartFrame(ReceivedFrame &frame) :
        ConnectionFrame(frame),
        _major(frame.nextUint8()),
        _minor(frame.nextUint8()),
        _properties(frame),
        _mechanisms(frame),
        _locales(frame)
    {}

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

    /**
     *  Method id
     *  @return uint16_t
     */
    virtual uint16_t methodID() const override
    {
        return 10;
    }

    /**
     *  Major AMQP version number
     *  @return uint8_t
     */
    uint8_t majorVersion() const
    {
        return _major;
    }

    /**
     *  Minor AMQP version number
     *  @return uint8_t
     */
    uint8_t minorVersion() const
    {
        return _minor;
    }

    /**
     *  Additional server properties
     *  @note:  exact properties are not specified
     *          and are implementation-dependent
     * 
     *  @return Table
     */
    const Table& properties() const
    {
        return _properties;
    }

    /**
     *  The capabilities of the connection
     *  @return Table
     */
    const Table& capabilities() const
    {
        // retrieve the capabilities
        return _properties.get("capabilities");
    }

    /**
     *  Available security mechanisms
     *  @return string
     */
    const std::string &mechanisms() const
    {
        return _mechanisms;
    }

    /**
     *  Available message locales
     *  @return string
     */
    const std::string &locales() const
    {
        return _locales;
    }
    
    /**
     *  Process the connection start frame
     *  @param  connection
     *  @return bool
     *  @internal
     */
    virtual bool process(ConnectionImpl *connection) override
    {
        // the client properties
        Table properties;

        // move connection to handshake mode
        connection->setProtocolOk(_properties, properties);

        // the capabilities
        Table capabilities;
        
        // we want a special treatment for authentication failures
        capabilities["authentication_failure_close"] = true;
        
        // when the server cancels a consumer (for example because a queue is delete, or the node on which the 
        // queue lives dies, we want to receive a notification that the consumer is no longer alive)
        capabilities["consumer_cancel_notify"] = true;
        
        // when the rabbitmq server reaches its max capacity, it can send a notification to us, we want them
        capabilities["connection.blocked"] = true;
        
        // fill the peer properties
        if (!properties.contains("version")) properties["version"] = "AMQP-CPP " VERSION_NAME;
        if (!properties.contains("copyright")) properties["copyright"] = "Copernica AMQP-CPP library :: Copyright 2015-2023 Copernica BV";
        if (!properties.contains("information")) properties["information"] = "https://github.com/CopernicaMarketingSoftware/AMQP-CPP";
        if (!properties.contains("capabilities")) properties["capabilities"] = capabilities;
        if (!properties.contains("product")) properties["product"] = ProgramName();;
        if (!properties.contains("connection_name")) properties["connection_name"] = ProgramName();

#if defined(_WIN32) || defined(_WIN64)
        // i don't know that much about win32, so let's use hardcoded string
        if (!properties.contains("platform")) properties["platform"] = "windows";
#else
        // on unix-like systems I know how to retrieve application and platform info
        if (!properties.contains("platform")) properties["platform"] = PlatformName();
#endif

        // send back a connection start ok frame
        connection->send(ConnectionStartOKFrame(properties, "PLAIN", connection->login().saslPlain(), "en_US"));
        
        // done
        return true;
    }

};

/**
 *  end namespace
 */
}

