/**
 *  MetaData.h
 *
 *  With every published message a set of meta data is passed to. This class
 *  holds all that meta data.
 *
 *  @copyright 2014 - 2023 Copernica BV
 */

/**
 *  Include guard
 */
#pragma once

/**
 *  Dependencies
 */
#include "booleanset.h"
#include "stringfield.h"
#include "table.h"

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

/**
 *  Class definition
 */
class MetaData
{
protected:
    /**
     *  First set of booleans
     *  @var    BooleanSet
     */
    BooleanSet _bools1;

    /**
     *  Second set of booleans
     *  @var    BooleanSet
     */
    BooleanSet _bools2;

    /**
     *  MIME content type
     *  @var    ShortString
     */
    ShortString _contentType;

    /**
     *  MIME content encoding
     *  @var    ShortString
     */
    ShortString _contentEncoding;

    /**
     *  message header field table
     *  @var    Table
     */
    Table _headers;

    /**
     *  Delivery mode (non-persistent (1) or persistent (2))
     *  @var    UOctet
     */
    UOctet _deliveryMode = 0;

    /**
     *  boolean whether field was sent to us
     *  @var    UOctet
     */
    UOctet _priority = 0;

    /**
     *  application correlation identifier
     *  @var    ShortString
     */
    ShortString _correlationID;

    /**
     *  address to reply to
     *  @var    ShortString
     */
    ShortString _replyTo;

    /**
     *  message expiration identifier
     *  @var    ShortString
     */
    ShortString _expiration;

    /**
     *  application message identifier
     *  @var    ShortString
     */
    ShortString _messageID;

    /**
     *  message timestamp
     *  @var    Timestamp
     */
    Timestamp _timestamp;

    /**
     *  message type name
     *  @var    ShortString
     */
    ShortString _typeName;

    /**
     *  creating user id
     *  @var    ShortString
     */
    ShortString _userID;

    /**
     *  creating application id
     *  @var    ShortString
     */
    ShortString _appID;

    /**
     *  Deprecated cluster ID
     *  @var    ShortString
     */
    ShortString _clusterID;


public:
    /**
     *  Constructor for empty meta data. Can be useful when user-space wants to preserve all meta-data
     */
    MetaData() {}

    /**
     *  Read incoming frame
     *  @param  frame
     */
    MetaData(InBuffer &frame) :
        _bools1(frame),
        _bools2(frame)
    {
        // only copy the properties that were sent
        if (hasContentType())       _contentType = ShortString(frame);
        if (hasContentEncoding())   _contentEncoding = ShortString(frame);
        if (hasHeaders())           _headers = Table(frame);
        if (hasDeliveryMode())      _deliveryMode = UOctet(frame);
        if (hasPriority())          _priority = UOctet(frame);
        if (hasCorrelationID())     _correlationID = ShortString(frame);
        if (hasReplyTo())           _replyTo = ShortString(frame);
        if (hasExpiration())        _expiration = ShortString(frame);
        if (hasMessageID())         _messageID = ShortString(frame);
        if (hasTimestamp())         _timestamp = Timestamp(frame);
        if (hasTypeName())          _typeName = ShortString(frame);
        if (hasUserID())            _userID = ShortString(frame);
        if (hasAppID())             _appID = ShortString(frame);
        if (hasClusterID())         _clusterID = ShortString(frame);
    }

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

    /**
     *  Set all meta data
     *  @param  data
     */
    void set(const MetaData &data)
    {
        // simply copy all fields
        _bools1 = data._bools1;
        _bools2 = data._bools2;
        _contentType = data._contentType;
        _contentEncoding = data._contentEncoding;
        _headers = data._headers;
        _deliveryMode = data._deliveryMode;
        _priority = data._priority;
        _correlationID = data._correlationID;
        _replyTo = data._replyTo;
        _expiration = data._expiration;
        _messageID = data._messageID;
        _timestamp = data._timestamp;
        _typeName = data._typeName;
        _userID = data._userID;
        _appID = data._appID;
        _clusterID = data._clusterID;
    }

    /**
     *  Check if a certain field is set
     *  @return bool
     */
    bool hasExpiration      () const { return _bools1.get(0); }
    bool hasReplyTo         () const { return _bools1.get(1); }
    bool hasCorrelationID   () const { return _bools1.get(2); }
    bool hasPriority        () const { return _bools1.get(3); }
    bool hasDeliveryMode    () const { return _bools1.get(4); }
    bool hasHeaders         () const { return _bools1.get(5); }
    bool hasContentEncoding () const { return _bools1.get(6); }
    bool hasContentType     () const { return _bools1.get(7); }
    bool hasClusterID       () const { return _bools2.get(2); }
    bool hasAppID           () const { return _bools2.get(3); }
    bool hasUserID          () const { return _bools2.get(4); }
    bool hasTypeName        () const { return _bools2.get(5); }
    bool hasTimestamp       () const { return _bools2.get(6); }
    bool hasMessageID       () const { return _bools2.get(7); }

    /**
     *  Set the various supported fields
     *  @param  value
     */
    void setExpiration      (const std::string &value) { _expiration        = value; _bools1.set(0,true); }
    void setReplyTo         (const std::string &value) { _replyTo           = value; _bools1.set(1,true); }
    void setCorrelationID   (const std::string &value) { _correlationID     = value; _bools1.set(2,true); }
    void setPriority        (uint8_t value)            { _priority          = value; _bools1.set(3,true); }
    void setDeliveryMode    (uint8_t value)            { _deliveryMode      = value; _bools1.set(4,true); }
    void setHeaders         (const Table &value)       { _headers           = value; _bools1.set(5,true); }
    void setContentEncoding (const std::string &value) { _contentEncoding   = value; _bools1.set(6,true); }
    void setContentType     (const std::string &value) { _contentType       = value; _bools1.set(7,true); }
    void setClusterID       (const std::string &value) { _clusterID         = value; _bools2.set(2,true); }
    void setAppID           (const std::string &value) { _appID             = value; _bools2.set(3,true); }
    void setUserID          (const std::string &value) { _userID            = value; _bools2.set(4,true); }
    void setTypeName        (const std::string &value) { _typeName          = value; _bools2.set(5,true); }
    void setTimestamp       (uint64_t value)           { _timestamp         = value; _bools2.set(6,true); }
    void setMessageID       (const std::string &value) { _messageID         = value; _bools2.set(7,true); }

    /**
     *  Set the various supported fields using r-value references
     *  @param  value   moveable value
     */
    void setExpiration      (std::string &&value) { _expiration       = std::move(value); _bools1.set(0,true); }
    void setReplyTo         (std::string &&value) { _replyTo          = std::move(value); _bools1.set(1,true); }
    void setCorrelationID   (std::string &&value) { _correlationID    = std::move(value); _bools1.set(2,true); }
    void setHeaders         (Table &&value)       { _headers          = std::move(value); _bools1.set(5,true); }
    void setContentEncoding (std::string &&value) { _contentEncoding  = std::move(value); _bools1.set(6,true); }
    void setContentType     (std::string &&value) { _contentType      = std::move(value); _bools1.set(7,true); }
    void setClusterID       (std::string &&value) { _clusterID        = std::move(value); _bools2.set(2,true); }
    void setAppID           (std::string &&value) { _appID            = std::move(value); _bools2.set(3,true); }
    void setUserID          (std::string &&value) { _userID           = std::move(value); _bools2.set(4,true); }
    void setTypeName        (std::string &&value) { _typeName         = std::move(value); _bools2.set(5,true); }
    void setMessageID       (std::string &&value) { _messageID        = std::move(value); _bools2.set(7,true); }

    /**
     *  Set the various supported fields using data buffers
     *  @param  value   data buffer
     *  @param  size    size of the buffer
     */
    void setExpiration      (const char *value, size_t size) { _expiration.assign(value, size);      _bools1.set(0,true); }
    void setReplyTo         (const char *value, size_t size) { _replyTo.assign(value, size);         _bools1.set(1,true); }
    void setCorrelationID   (const char *value, size_t size) { _correlationID.assign(value, size);   _bools1.set(2,true); }
    void setContentEncoding (const char *value, size_t size) { _contentEncoding.assign(value, size); _bools1.set(6,true); }
    void setContentType     (const char *value, size_t size) { _contentType.assign(value, size);     _bools1.set(7,true); }
    void setClusterID       (const char *value, size_t size) { _clusterID.assign(value, size);       _bools2.set(2,true); }
    void setAppID           (const char *value, size_t size) { _appID.assign(value, size);           _bools2.set(3,true); }
    void setUserID          (const char *value, size_t size) { _userID.assign(value, size);          _bools2.set(4,true); }
    void setTypeName        (const char *value, size_t size) { _typeName.assign(value, size);        _bools2.set(5,true); }
    void setMessageID       (const char *value, size_t size) { _messageID.assign(value, size);       _bools2.set(7,true); }

    /**
     *  Set the various supported fields using c strings
     *  @param  value   data buffer
     */
    void setExpiration      (const char *value) { _expiration.assign(value);      _bools1.set(0,true); }
    void setReplyTo         (const char *value) { _replyTo.assign(value);         _bools1.set(1,true); }
    void setCorrelationID   (const char *value) { _correlationID.assign(value);   _bools1.set(2,true); }
    void setContentEncoding (const char *value) { _contentEncoding.assign(value); _bools1.set(6,true); }
    void setContentType     (const char *value) { _contentType.assign(value);     _bools1.set(7,true); }
    void setClusterID       (const char *value) { _clusterID.assign(value);       _bools2.set(2,true); }
    void setAppID           (const char *value) { _appID.assign(value);           _bools2.set(3,true); }
    void setUserID          (const char *value) { _userID.assign(value);          _bools2.set(4,true); }
    void setTypeName        (const char *value) { _typeName.assign(value);        _bools2.set(5,true); }
    void setMessageID       (const char *value) { _messageID.assign(value);       _bools2.set(7,true); }

    /**
     *  Methods to remove properties from the header
     */
    void removeExpiration      () { _expiration     .clear(); _bools1.set(0,false); }
    void removeReplyTo         () { _replyTo        .clear(); _bools1.set(1,false); }
    void removeCorrelationID   () { _correlationID  .clear(); _bools1.set(2,false); }
    void removePriority        () { _priority       .clear(); _bools1.set(3,false); }
    void removeDeliveryMode    () { _deliveryMode   .clear(); _bools1.set(4,false); }
    void removeHeaders         () { _headers        .clear(); _bools1.set(5,false); }
    void removeContentEncoding () { _contentEncoding.clear(); _bools1.set(6,false); }
    void removeContentType     () { _contentType    .clear(); _bools1.set(7,false); }
    void removeClusterID       () { _clusterID      .clear(); _bools2.set(2,false); }
    void removeAppID           () { _appID          .clear(); _bools2.set(3,false); }
    void removeUserID          () { _userID         .clear(); _bools2.set(4,false); }
    void removeTypeName        () { _typeName       .clear(); _bools2.set(5,false); }
    void removeTimestamp       () { _timestamp      .clear(); _bools2.set(6,false); }
    void removeMessageID       () { _messageID      .clear(); _bools2.set(7,false); }

    /**
     *  Retrieve the fields
     *  @return string
     */
    const std::string &expiration     () const { return _expiration;        }
    const std::string &replyTo        () const { return _replyTo;           }
    const std::string &correlationID  () const { return _correlationID;     }
          uint8_t      priority       () const { return _priority;          }
          uint8_t      deliveryMode   () const { return _deliveryMode;      }
    const Table       &headers        () const { return _headers;           }
    const std::string &contentEncoding() const { return _contentEncoding;   }
    const std::string &contentType    () const { return _contentType;       }
    const std::string &clusterID      () const { return _clusterID;         }
    const std::string &appID          () const { return _appID;             }
    const std::string &userID         () const { return _userID;            }
    const std::string &typeName       () const { return _typeName;          }
          uint64_t     timestamp      () const { return _timestamp;         }
    const std::string &messageID      () const { return _messageID;         }

    /**
     *  Is this a message with persistent storage
     *  This is an alias for retrieving the delivery mode and checking if it is set to 2
     *  @return bool
     */
    bool persistent() const
    {
        return hasDeliveryMode() && deliveryMode() == 2;
    }

    /**
     *  Set whether storage should be persistent or not
     *  @param  bool
     */
    void setPersistent(bool value = true)
    {
        if (value)
        {
            // simply set the delivery mode
            setDeliveryMode(2);
        }
        else
        {
            // we remove the field from the header
            _deliveryMode = 0;
            _bools1.set(4,false);
        }
    }

    /**
     *  Total size
     *  @return uint32_t
     */
    uint32_t size() const
    {
        // the result (2 for the two boolean sets)
        uint32_t result = 2;

        if (hasExpiration())        result += (uint32_t)_expiration.size();
        if (hasReplyTo())           result += (uint32_t)_replyTo.size();
        if (hasCorrelationID())     result += (uint32_t)_correlationID.size();
        if (hasPriority())          result += (uint32_t)_priority.size();
        if (hasDeliveryMode())      result += (uint32_t)_deliveryMode.size();
        if (hasHeaders())           result += (uint32_t)_headers.size();
        if (hasContentEncoding())   result += (uint32_t)_contentEncoding.size();
        if (hasContentType())       result += (uint32_t)_contentType.size();
        if (hasClusterID())         result += (uint32_t)_clusterID.size();
        if (hasAppID())             result += (uint32_t)_appID.size();
        if (hasUserID())            result += (uint32_t)_userID.size();
        if (hasTypeName())          result += (uint32_t)_typeName.size();
        if (hasTimestamp())         result += (uint32_t)_timestamp.size();
        if (hasMessageID())         result += (uint32_t)_messageID.size();

        // done
        return result;
    }

    /**
     *  Fill an output buffer
     *  @param  buffer
     */
    void fill(OutBuffer &buffer) const
    {
        // the two boolean sets are always present
        _bools1.fill(buffer);
        _bools2.fill(buffer);

        // only copy the properties that were sent
        if (hasContentType())       _contentType.fill(buffer);
        if (hasContentEncoding())   _contentEncoding.fill(buffer);
        if (hasHeaders())           _headers.fill(buffer);
        if (hasDeliveryMode())      _deliveryMode.fill(buffer);
        if (hasPriority())          _priority.fill(buffer);
        if (hasCorrelationID())     _correlationID.fill(buffer);
        if (hasReplyTo())           _replyTo.fill(buffer);
        if (hasExpiration())        _expiration.fill(buffer);
        if (hasMessageID())         _messageID.fill(buffer);
        if (hasTimestamp())         _timestamp.fill(buffer);
        if (hasTypeName())          _typeName.fill(buffer);
        if (hasUserID())            _userID.fill(buffer);
        if (hasAppID())             _appID.fill(buffer);
        if (hasClusterID())         _clusterID.fill(buffer);
    }
};

/**
 *  End of namespace
 */
}

