/*
  broker-protocol.h

  Copyright (c) 2021 Kevin Matz (kevin.matz@gmail.com)

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  SOFTWARE.
*/
#pragma once

#include "pdu.h"
#include "rdm/uid.h"
#include "uuid/uuid.h"

namespace RDMnet::BrokerProtocol {

/**
 * @brief 1.2.2 Broker Protocol
 *
 * The Broker Protocol enables the scalability and multi-controller
 * functionality of RDMnet. It uses a central server called a Broker to handle
 * discovery, maintain the state of the system and facilitate message routing.
 * The Broker routes messages between a set of other Components, which are
 * referred to as Clients. The Broker Protocol contains a small set of messages
 * that facilitate Client connection and discovery of other Clients.
 */


namespace ClientEntry {

/**
 * @brief The ClientEntry::Pdu class
 */
class Pdu
  : public RDMnet::Pdu
{
public:
  Pdu();
  void iStream(ACN::PDU::Stream) override;
};


/**
 * @brief The ClientEntry::header struct
 */
struct entry_header
    : ACN::PDU::pdu_header
{
  UUID::uuid cid; //!< Component's UUID

  size_t streamSize() const override;
  void   iStream(ACN::PDU::Stream) override;
  void   oStream(ACN::PDU::Stream) const override;
};


/**
 * @brief The ClientEntry::RptClientEntry_data struct
 */
struct entry_rpt_data
    : ACN::PDU::pdu_data
{
  RDM::UID   client_uid;    //!< client RDM UID
  uint8_t    client_type;   //!< client type identifier
  UUID::uuid binding_cid;   //!< UUID to bind

  size_t streamSize() const override { return 23; }
  void   iStream(ACN::PDU::Stream) override;
  void   oStream(ACN::PDU::Stream) const override;
};


/**
 * @brief The ClientEntry::EptClientEntry_data struct
 */
struct entry_ept_data
    : ACN::PDU::pdu_data
{
  std::vector<uint8_t> subprotocol_list; //!< list of sub-protocols

  size_t streamSize() const override { return subprotocol_list.size(); }
  void   iStream(ACN::PDU::Stream) override;
  void   oStream(ACN::PDU::Stream) const override;
};

} // namespace CLientEntry



/**
 * @brief The BrokerProtocol::Pdu class
 */
class Pdu
  : public RDMnet::Pdu
{
public:
  Pdu();
  void iStream(ACN::PDU::Stream) override;
};


/**
 * @brief The connection_flags union
 */
union connection_flags {
  uint8_t   byte;                   //!< packed byte
  struct {
    bool    incrementalUpdates : 1; //!< incremental updates
    uint8_t flags_reserved  : 7;    //!< Brokers shall ignore.
  }__attribute__((packed));
};


/**
 * @brief 6.3.1.2 Client Connect
 */
struct client_connect_data
    : ACN::PDU::pdu_data
{
  std::string clientScope;    //!< scope of the client
  uint16_t    version;        //!< version
  std::string searchDomain;   //!< search domain
  connection_flags flags;     //!< connection flags
  ACN::PDU::Message<ClientEntry::Pdu> pdu;  //!< client entry PDU

  size_t streamSize() const override { return 297 + pdu->streamSize(); }
  void   iStream(ACN::PDU::Stream) override;
  void   oStream(ACN::PDU::Stream) const override;
};


/**
 * @brief 6.3.1.3 Connect Reply
 */
struct connect_reply_data
    : ACN::PDU::pdu_data
{
  uint16_t code;          //!< reply code
  uint16_t version;       //!< version
  RDM::UID broker_uid;    //!< broker's UID
  RDM::UID client_uid;    //!< client's UID

  size_t streamSize() const override { return 16; }
  void   iStream(ACN::PDU::Stream) override;
  void   oStream(ACN::PDU::Stream) const override;
};


/**
 * @brief 6.3.1.4 Client Entry Update
 */
struct client_entry_update_data
    : ACN::PDU::pdu_data
{
  connection_flags flags;   //!< connection flags
  ACN::PDU::Message<ClientEntry::Pdu> pdu;  //!< client entry PDU

  size_t streamSize() const override { return 1 + pdu->streamSize(); }
  void   iStream(ACN::PDU::Stream) override;
  void   oStream(ACN::PDU::Stream) const override;
};


/**
 * @brief 6.3.1.5 Client Redirect IPv4
 */
struct client_redirect_ipv4_data
    : ACN::PDU::pdu_data
{
  uint32_t ip;      //!< IP address
  uint16_t port;    //!< Port

  size_t streamSize() const override { return 6; }
  void   iStream(ACN::PDU::Stream) override;
  void   oStream(ACN::PDU::Stream) const override;
};


/**
 * @brief 6.3.1.6 Client Redirect IPv6
 */
struct client_redirect_ipv6_data
    : ACN::PDU::pdu_data
{
  uint8_t  ip[16];  //!< IP address
  uint16_t port;    //!< Port

  size_t streamSize() const override { return 18; }
  void   iStream(ACN::PDU::Stream) override;
  void   oStream(ACN::PDU::Stream) const override;
};


/**
 * @brief The dynamic_uid_request struct
 */
struct dynamic_uid_request
    : ACN::PDU::pdu_stream_object
{

  RDM::UID   uid;   //!< UID
  UUID::uuid rid;   //!< UUID

  size_t streamSize() const override { return 22; }
  void   iStream(ACN::PDU::Stream) override;
  void   oStream(ACN::PDU::Stream) const override;
};


/**
 * @brief 6.3.1.12 Request Dynamic UID Assignment
 */
struct request_dynamic_uid_data
    : ACN::PDU::pdu_data
{
  std::vector<dynamic_uid_request> list; //!< dynamic UIDs

  size_t streamSize() const override { return 22 * list.size(); }
  void   iStream(ACN::PDU::Stream) override;
  void   oStream(ACN::PDU::Stream) const override;
};


/**
 * @brief The dynamic_uid_mapping struct
 */
struct dynamic_uid_mapping
    : ACN::PDU::pdu_stream_object
{

  RDM::UID   uid;     //!< UID
  UUID::uuid rid;     //!< UUID
  uint16_t   status;  //!< status

  size_t streamSize() const override { return 24; }
  void   iStream(ACN::PDU::Stream) override;
  void   oStream(ACN::PDU::Stream) const override;
};


/**
 * @brief 6.3.1.13 Dynamic UID Assignment List
 */
struct dynamic_uid_assignment_data
    : ACN::PDU::pdu_data
{
  std::vector<dynamic_uid_mapping> list; //!< dynamic mappings

  size_t streamSize() const override { return 24 * list.size(); }
  void   iStream(ACN::PDU::Stream) override;
  void   oStream(ACN::PDU::Stream) const override;
};


/**
 * @brief 6.3.1.14 Fetch Dynamic UID Assignment List
 */
struct fetch_dynamcic_assignment_data
    : ACN::PDU::pdu_data
{
  std::vector<RDM::UID> list; //!< list of UID

  size_t streamSize() const override { return 6 * list.size(); }
  void   iStream(ACN::PDU::Stream) override;
  void   oStream(ACN::PDU::Stream) const override;
};


/**
 * @brief 6.3.1.15 Client Disconnect
 */
struct disconnect_data
    : ACN::PDU::pdu_data
{
  uint16_t reason;  //!< reason code

  size_t streamSize() const override { return 2; }
  void   iStream(ACN::PDU::Stream s) override { *s >> reason; }
  void   oStream(ACN::PDU::Stream s) const override { *s << reason; };
};


} // namespace RDMnet::BrokerProtocol
