#pragma once

#include <algorithm>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

#include "dynamic_binary_buffer.hpp"
#include "fast_endianless.hpp"
#include "fast_library.hpp"
#include "fastnetmon_networks.hpp"

#include <boost/serialization/nvp.hpp>

#include "iana/iana_ip_protocols.hpp"

class bgp_attribute_origin;
class bgp_attribute_next_hop_ipv4;
class IPv4UnicastAnnounce;
class IPv6UnicastAnnounce;

extern log4cpp::Category& logger;

bool decode_bgp_subnet_encoding_ipv4(int len, uint8_t* value, subnet_cidr_mask_t& extracted_prefix, uint32_t& parsed_nlri_length);
uint32_t how_much_bytes_we_need_for_storing_certain_subnet_mask(uint8_t prefix_bit_length);
std::string get_bgp_attribute_name_by_number(uint8_t bgp_attribute_type);

enum BGP_PROTOCOL_MESSAGE_TYPES_UNTYPED : uint8_t {
    BGP_PROTOCOL_MESSAGE_OPEN         = 1,
    BGP_PROTOCOL_MESSAGE_UPDATE       = 2,
    BGP_PROTOCOL_MESSAGE_NOTIFICATION = 3,
    BGP_PROTOCOL_MESSAGE_KEEPALIVE    = 4,
};

// More details here https://tools.ietf.org/html/rfc4271#page-12
class __attribute__((__packed__)) bgp_message_header_t {
    public:
    uint8_t marker[16] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                           0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
    uint16_t length    = 0;
    uint8_t type       = 0;

    void host_byte_order_to_network_byte_order() {
        length = htons(length);
    }
};

static_assert(sizeof(bgp_message_header_t) == 19, "Broken size for bgp_message_header_t");

// https://www.ietf.org/rfc/rfc4271.txt pages 15 and 16
// https://github.com/osrg/gobgp/blob/d6148c75a30d87c3f8c1d0f68725127e4c5f3a65/packet/bgp.go#L3012
struct __attribute__((__packed__)) bgp_attribute_flags {
    uint8_t : 4, extended_length_bit : 1 = 0, partial_bit : 1 = 0, transitive_bit : 1 = 0, optional_bit : 1 = 0;

    bgp_attribute_flags(uint8_t flag_as_integer) {
        memcpy(this, &flag_as_integer, sizeof(flag_as_integer));
    }

    bgp_attribute_flags() {
        memset(this, 0, sizeof(*this));
    }

    void set_optional_bit(bool bit_value) {
        optional_bit = (int)bit_value;
    }

    void set_transitive_bit(bool bit_value) {
        transitive_bit = (int)bit_value;
    }

    void set_partial_bit(bool bit_value) {
        partial_bit = (int)bit_value;
    }

    void set_extended_length_bit(bool bit_value) {
        extended_length_bit = (int)bit_value;
    }

    bool get_optional_bit() {
        return optional_bit == 1 ? true : false;
    }

    bool get_transitive_bit() {
        return transitive_bit == 1 ? true : false;
    }

    bool get_partial_bit() {
        return partial_bit == 1 ? true : false;
    }

    bool get_extended_length_bit() {
        return extended_length_bit == 1 ? true : false;
    }

    std::string print() const {
        std::stringstream buf;

        buf << "optional: " << int(optional_bit) << " transitive: " << int(transitive_bit)
            << " partial_bit: " << int(partial_bit) << " extended_length_bit: " << int(extended_length_bit);

        return buf.str();
    }
};

class bgp_attibute_common_header_t {
    public:
    uint8_t attribute_flags         = 0;
    uint8_t attribute_type          = 0;
    uint32_t length_of_length_field = 0;
    uint32_t attribute_value_length = 0;
    uint32_t attribute_body_shift   = 0;
    // Just const value
    uint32_t attribute_flag_and_type_length = 2;

    std::string print() const {
        std::stringstream buffer;

        buffer << "attribute_flags: " << uint32_t(attribute_flags)
               << " "
               // << "attribute_pretty_flags: " <<
               // bgp_attribute_flags(attribute_flags).print() << " "
               << "attribute_type: " << uint32_t(attribute_type) << " "
               << "attribute_name: " << get_bgp_attribute_name_by_number(attribute_type) << " "
               << "length_of_length_field: " << length_of_length_field << " "
               << "attribute_value_length: " << attribute_value_length;

        return buffer.str();
    }

    // More user friendly form
    bool parse_raw_bgp_attribute_binary_buffer(dynamic_binary_buffer_t dynamic_binary_buffer) {
        return parse_raw_bgp_attribute((uint8_t*)dynamic_binary_buffer.get_pointer(), dynamic_binary_buffer.get_used_size());
    }

    bool parse_raw_bgp_attribute(uint8_t* value, size_t len) {
        if (len < attribute_flag_and_type_length or value == NULL) {
            logger << log4cpp::Priority::WARN << "Too short attribute. We need least two bytes here but get " << len << " bytes";
            return false;
        }

        // https://www.ietf.org/rfc/rfc4271.txt page 15
        attribute_flags = value[0];
        attribute_type  = value[1];

        bgp_attribute_flags attr_flags(attribute_flags);
        // attr_flags.print();

        length_of_length_field = 1;

        if (attr_flags.extended_length_bit == 1) {
            // When we have extended_length_bit we have two bytes for length
            // information
            length_of_length_field = 2;
        }

        if (len < attribute_flag_and_type_length + length_of_length_field) {
            logger << log4cpp::Priority::WARN << "Too short attribute because we need least "
                   << attribute_flag_and_type_length + length_of_length_field << " bytes";
            return false;
        }

        if (length_of_length_field == 1) {
            // Read one byte
            attribute_value_length = value[2];
        } else if (length_of_length_field == 2) {
            // Read two bytes
            attribute_value_length = fast_ntoh(*(uint16_t*)(value + 2));
        } else {
            logger << log4cpp::Priority::ERROR << "Broken length of length field: " << length_of_length_field;
            return false;
        }

        uint32_t total_attribute_length = attribute_flag_and_type_length + length_of_length_field + attribute_value_length;

        if (len < total_attribute_length) {
            logger << log4cpp::Priority::WARN << "Atrribute value length: " << total_attribute_length
                   << " length exceed whole packet length " << len;

            return false;
        }

        // Store shift to attribute payload
        attribute_body_shift = attribute_flag_and_type_length + length_of_length_field;

        return true;
    }
};


bool decode_attributes_to_ipv4_announce(char* value, int len, IPv4UnicastAnnounce& unicast_ipv4_announce);
bool encode_bgp_subnet_encoding(const subnet_cidr_mask_t& prefix, dynamic_binary_buffer_t& buffer_result);
std::string get_origin_name_by_value(uint8_t origin_value);

const unsigned int AFI_IP  = 1;
const unsigned int AFI_IP6 = 2;

const unsigned int SAFI_UNICAST           = 1;
const unsigned int SAFI_FLOW_SPEC_UNICAST = 133;

const unsigned int ipv4_unicast_route_family   = AFI_IP << 16 | SAFI_UNICAST;
const unsigned int ipv4_flow_spec_route_family = AFI_IP << 16 | SAFI_FLOW_SPEC_UNICAST;

const unsigned int ipv6_unicast_route_family   = AFI_IP6 << 16 | SAFI_UNICAST;
const unsigned int ipv6_flow_spec_route_family = AFI_IP6 << 16 | SAFI_FLOW_SPEC_UNICAST;

// http://www.iana.org/assignments/bgp-extended-communities/bgp-extended-communities.xhtml
// https://www.iana.org/assignments/bgp-extended-communities/bgp-extended-communities.xhtml#transitive
enum EXTENDED_COMMUNITY_TYPES_HIGHT_UNTYPED : uint8_t {
    // Transitive IPv4-Address-Specific Extended Community (Sub-Types are defined in the "Transitive IPv4-Address-Specific Extended Community Sub-Types" registry)
    EXTENDED_COMMUNITY_TRANSITIVE_IPV4_ADDRESS_SPECIFIC = 1, // 0x01

    // We are encoding attributes for BGP flow spec this way
    // Generic Transitive Experimental Use Extended Community
    EXTENDED_COMMUNITY_TRANSITIVE_EXPEREMENTAL = 128, // 0x80
};

// Subtypes for EXTENDED_COMMUNITY_TRANSITIVE_EXPEREMENTAL
// http://www.iana.org/assignments/bgp-extended-communities/bgp-extended-communities.xhtml
// https://www.iana.org/assignments/bgp-extended-communities/bgp-extended-communities.xhtml#transitive
enum EXTENDED_COMMUNITY_TYPES_LOW_FOR_COMMUNITY_TRANSITIVE_EXPEREMENTAL : uint8_t {
    FLOW_SPEC_EXTENDED_COMMUNITY_SUBTYPE_TRAFFIC_RATE         = 6,
    FLOW_SPEC_EXTENDED_COMMUNITY_SUBTYPE_TRAFFIC_ACTION       = 7,
    FLOW_SPEC_EXTENDED_COMMUNITY_SUBTYPE_REDIRECT_AS_TWO_BYTE = 8,
    FLOW_SPEC_EXTENDED_COMMUNITY_SUBTYPE_TRAFFIC_REMARKING    = 9,
};

enum BGP_ATTRIBUTES_TYPES : uint8_t {
    // https://www.ietf.org/rfc/rfc4271.txt from page 17
    BGP_ATTRIBUTE_ORIGIN = 1,

    BGP_ATTRIBUTE_AS_PATH = 2,

    BGP_ATTRIBUTE_NEXT_HOP = 3,

    BGP_ATTRIBUTE_MULTI_EXIT_DISC = 4,

    BGP_ATTRIBUTE_LOCAL_PREF = 5,

    BGP_ATTRIBUTE_ATOMIC_AGGREGATE = 6,

    BGP_ATTRIBUTE_AGGREGATOR = 7,

    // https://tools.ietf.org/html/rfc1997 from page 1
    BGP_ATTRIBUTE_COMMUNITY = 8,

    // https://datatracker.ietf.org/doc/html/rfc4456#page-6
    BGP_ATTRIBUTE_ORIGINATOR_ID = 9,

    // https://datatracker.ietf.org/doc/html/rfc4456#page-6
    BGP_ATTRIBUTE_CLUSTER_LIST = 10,

    // https://tools.ietf.org/html/rfc4760 from page 2
    BGP_ATTRIBUTE_MP_REACH_NLRI = 14,

    // https://tools.ietf.org/html/rfc4360 from page 1
    BGP_ATTRIBUTE_EXTENDED_COMMUNITY = 16,
};

// https://www.ietf.org/rfc/rfc4271.txt from page 17
enum BGP_ORIGIN_TYPES : uint8_t { BGP_ORIGIN_IGP = 0, BGP_ORIGIN_EGP = 1, BGP_ORIGIN_INCOMPLETE = 2 };

// https://www.iana.org/assignments/bgp-extended-communities/bgp-extended-communities.xhtml#trans-ipv4
enum BGP_IPV4_EXTENDED_COMMUNITY_SUBTYPES_TRANSITIVE : uint8_t {
    BGP_IPV4_EXTENDED_COMMUNITY_SUBTYPE_FLOW_SPEC_REDIRECT_IPv4 = 0x0c,
};

// It's short MP reach NLRI header. We use it in case when we have non zero length for length_of_next_hop
// I use it only to decode/encode IPv6 annnounces
class __attribute__((__packed__)) bgp_mp_reach_short_header_t {
    public:
    uint16_t afi_identifier = AFI_IP6;
    uint8_t safi_identifier = SAFI_UNICAST;
    // According to https://www.ietf.org/rfc/rfc2545.txt we should set it to 16 for global IP addresses
    uint8_t length_of_next_hop = 16;

    void network_to_host_byte_order() {
        afi_identifier = fast_ntoh(afi_identifier);
    }

    void host_byte_order_to_network_byte_order() {
        afi_identifier = fast_hton(afi_identifier);
    }

    std::string print() const {
        std::stringstream buffer;

        buffer << "afi_identifier: " << uint32_t(afi_identifier) << " "
               << "safi_identifier: " << uint32_t(safi_identifier) << " "
               << "length_of_next_hop: " << uint32_t(length_of_next_hop);

        return buffer.str();
    }
};

class __attribute__((__packed__)) bgp_attribute_community_t {
    public:
    bgp_attribute_community_t() {
        attribute_type = BGP_ATTRIBUTE_COMMUNITY;

        // Set attribute flags
        attribute_flags.set_transitive_bit(true);
        attribute_flags.set_partial_bit(false);
        attribute_flags.set_optional_bit(true);
        attribute_flags.set_extended_length_bit(false);
    }

    bgp_attribute_flags attribute_flags;
    uint8_t attribute_type = 0;

    // This variable store total size in bytes of all community elements (each
    // element should has
    // size 4 bytes)
    uint8_t attribute_length = 0;

    // Here we have multiple elements of type: bgp_community_attribute_element_t
};

// Format of AS_PATH element described here: https://www.rfc-editor.org/rfc/rfc4271.html#page-17
// And with 32 bit / 4 byte ASN corrections from here: https://www.rfc-editor.org/rfc/rfc6793 page 2
// The AS path information exchanged between NEW BGP speakers is carried in the existing AS_PATH attribute,
// except that each AS number in the attribute is encoded as a four-octet entity (instead of a two-octet entity).
class __attribute__((__packed__)) bgp_as_path_segment_element_t {
    public:
    // Path segment type declares on of two possible types of segments:
    // 1 AS_SET: unordered set of ASes a route in the UPDATE message has traversed
    // 2 AS_SEQUENCE: ordered set of ASes a route in the UPDATE message has traversed
    // We use AS_SEQUENCE as default option
    uint8_t path_segment_type = 2;

    // The path segment length is a 1-octet length field, containing the number of ASes (not the number of octets) in
    // the path segment value field.
    uint8_t path_segment_length = 0;

    // Here we store list of 4 byte ASNs encoded in 4 byte format each
};

// BGP attribute AS_PATH
// In RFC this encoding format is covered here: https://www.rfc-editor.org/rfc/rfc4271.html#page-18
class __attribute__((__packed__)) bgp_attribute_as_path_t {
    public:
    bgp_attribute_as_path_t() {
        attribute_type = BGP_ATTRIBUTE_AS_PATH;

        // Set attribute flags
        attribute_flags.set_transitive_bit(true);
        attribute_flags.set_partial_bit(false);

        // It's mandatory: https://www.rfc-editor.org/rfc/rfc4271.html#section-5.1.2
        attribute_flags.set_optional_bit(false);

        // Means that we using single octet length field encoding:
        // https://www.rfc-editor.org/rfc/rfc4271.html#page-17
        attribute_flags.set_extended_length_bit(false);
    }

    bgp_attribute_flags attribute_flags;
    uint8_t attribute_type = 0;

    // This variable store total size in bytes of all AS_PATH elements
    uint8_t attribute_length = 0;

    // Here we have multiple elements of type: bgp_as_path_segment_element_t
};


// This is new extended communities:
// More details: https://tools.ietf.org/html/rfc4360
class __attribute__((__packed__)) bgp_extended_community_attribute_t {
    public:
    bgp_extended_community_attribute_t() {
        // Set attribute flags
        attribute_flags.set_transitive_bit(true);
        attribute_flags.set_partial_bit(false);
        attribute_flags.set_optional_bit(true);
        attribute_flags.set_extended_length_bit(false);
    }

    bgp_attribute_flags attribute_flags;
    uint8_t attribute_type = BGP_ATTRIBUTE_EXTENDED_COMMUNITY;

    // This variable store total size in bytes of all community elements. Each
    // community element has
    // size 8 bytes
    uint8_t attribute_length = 0;
    // Here we have multiple elements of type: bgp_extended_community_element_t
};

// BGP extended community attribute
class __attribute__((__packed__)) bgp_extended_community_element_t {
    public:
    uint8_t type_hight = 0;
    uint8_t type_low   = 0;

    // This data depends on implementation and values in type_* variables
    uint8_t value[6] = { 0, 0, 0, 0, 0, 0 };

    std::string print() const {
        std::stringstream buffer;

        buffer << "type hight: " << uint32_t(type_hight) << " "
               << "type low: " << uint32_t(type_low) << " "
               << "value raw: " << print_binary_string_as_hex_with_leading_0x(value, sizeof(value));

        return buffer.str();
    }
};

static_assert(sizeof(bgp_extended_community_element_t) == 8, "Bad size for bgp_extended_community_element_t");

// This is class for storing old style BGP communities which support only 16
// bit AS numbers
class __attribute__((__packed__)) bgp_community_attribute_element_t {
    public:
    uint16_t asn_number       = 0;
    uint16_t community_number = 0;

    bool operator==(const bgp_community_attribute_element_t& other) const {
        return asn_number == other.asn_number && community_number == other.community_number;
    }

    void host_byte_order_to_network_byte_order() {
        asn_number       = htons(asn_number);
        community_number = htons(community_number);
    }
};

bool encode_bgp_flow_spec_elements_into_bgp_mp_attributebgp_community_from_string(std::string community_as_string,
                                                                                  bgp_community_attribute_element_t& bgp_community_attribute_element);

static_assert(sizeof(bgp_community_attribute_element_t) == 4, "Broken size of bgp_community_attribute_element_t");

// With this attribute we are encoding different things (flow spec for example)
class __attribute__((__packed__)) bgp_attribute_multiprotocol_extensions_t {
    public:
    bgp_attribute_multiprotocol_extensions_t() {
        attribute_flags.set_transitive_bit(false);
        attribute_flags.set_optional_bit(true);

        attribute_flags.set_partial_bit(false);
        attribute_flags.set_extended_length_bit(false);
    }

    std::string print() const {
        std::stringstream buf;

        buf << attribute_flags.print() << "attribute type: " << int(attribute_type)
            << " attribute_length: " << int(attribute_length);

        return buf.str();
    }

    bgp_attribute_flags attribute_flags;
    uint8_t attribute_type   = BGP_ATTRIBUTE_MP_REACH_NLRI;
    uint8_t attribute_length = 0;
    // value has very complex format and we are encoding it with external code
};

class __attribute__((__packed__)) bgp_attribute_origin {
    public:
    bgp_attribute_origin() {
        // Set attribute flags
        attribute_flags.set_transitive_bit(true);

        attribute_flags.set_partial_bit(false);
        attribute_flags.set_optional_bit(false);
        attribute_flags.set_extended_length_bit(false);
    }

    std::string print() const {
        std::stringstream buf;

        buf << attribute_flags.print() << "attribute type: " << int(attribute_type)
            << " attribute_length: " << int(attribute_length) << " attribute_value: " << attribute_value;

        return buf.str();
    }

    bgp_attribute_flags attribute_flags;
    uint8_t attribute_type   = BGP_ATTRIBUTE_ORIGIN;
    uint8_t attribute_length = 1;
    uint8_t attribute_value  = (uint8_t)BGP_ORIGIN_INCOMPLETE;
};

class __attribute__((__packed__)) bgp_attribute_next_hop_ipv4 {
    public:
    bgp_attribute_next_hop_ipv4() {
        // Set attribute flags
        attribute_flags.set_transitive_bit(true);

        attribute_flags.set_partial_bit(false);
        attribute_flags.set_optional_bit(false);
        attribute_flags.set_extended_length_bit(false);
    }

    bgp_attribute_next_hop_ipv4(uint32_t next_hop) : bgp_attribute_next_hop_ipv4() {
        attribute_value = next_hop;
    }

    bgp_attribute_flags attribute_flags;
    uint8_t attribute_type   = BGP_ATTRIBUTE_NEXT_HOP;
    uint8_t attribute_length = 4;
    uint32_t attribute_value = 0;

    std::string print() const {
        std::stringstream buf;

        buf << attribute_flags.print() << "attribute type: " << int(attribute_type)
            << " attribute_length: " << int(attribute_length) << " attribute_value: " << attribute_value;

        return buf.str();
    }
};


class IPv4UnicastAnnounce {
    public:
    void set_withdraw(bool withdraw) {
        this->is_withdraw = withdraw;
    }

    bool get_withdraw() {
        return this->is_withdraw;
    }

    void set_next_hop(uint32_t next_hop) {
        this->next_hop = next_hop;
    }

    void set_prefix(subnet_cidr_mask_t prefix) {
        this->prefix = prefix;
    }

    void set_origin(BGP_ORIGIN_TYPES origin) {
        this->origin = origin;
    }

    uint32_t get_next_hop() {
        return next_hop;
    }

    subnet_cidr_mask_t get_prefix() const {
        return this->prefix;
    }
    BGP_ORIGIN_TYPES get_origin() {
        return this->origin;
    }

    // Returns prefix in text form
    std::string get_prefix_in_cidr_form() {
        return convert_ip_as_uint_to_string(prefix.subnet_address) + "/" + std::to_string(prefix.cidr_prefix_length);
    }

    bool generate_nlri(dynamic_binary_buffer_t& buffer_result) const {
        return encode_bgp_subnet_encoding(this->prefix, buffer_result);
    }

    bool get_attributes(std::vector<dynamic_binary_buffer_t>& attributes_list) const {
        /*
         *   The sender of an UPDATE message SHOULD order path attributes within
         *   the UPDATE message in ascending order of attribute type.  The
         *   receiver of an UPDATE message MUST be prepared to handle path
         *   attributes within UPDATE messages that are out of order.
         */

        bgp_attribute_origin origin_attr;
        origin_attr.attribute_value = static_cast<uint8_t>(this->origin);

        // logger << log4cpp::Priority::WARN << "origin_attr: "          <<
        // origin_attr.print() <<
        // std::endl;

        dynamic_binary_buffer_t origin_as_binary_array;
        origin_as_binary_array.set_buffer_size_in_bytes(sizeof(origin_attr));
        origin_as_binary_array.append_data_as_object_ptr(&origin_attr);

        bgp_attribute_next_hop_ipv4 next_hop_attr(next_hop);

        // logger << log4cpp::Priority::WARN << "next_hop_attr: "          <<
        // next_hop_attr.print() <<
        // std::endl;

        dynamic_binary_buffer_t next_hop_as_binary_array;
        next_hop_as_binary_array.set_buffer_size_in_bytes(sizeof(next_hop_attr));
        next_hop_as_binary_array.append_data_as_object_ptr(&next_hop_attr);

        // Vector should be ordered in ascending order of attribute types
        // Check numbers in enum BGP_ATTRIBUTES_TYPES for information
        // We expect that we receive empty vector here
        attributes_list.clear();

        // It has attribute #1 and will be first in all the cases
        attributes_list.push_back(origin_as_binary_array);

        // AS Path should be here and it's #2
        if (this->as_path_asns.size() > 0) {
            // We have ASNs for AS_PATH attribute
            bgp_attribute_as_path_t bgp_attribute_as_path;

            uint64_t long_attribute_length = sizeof(bgp_as_path_segment_element_t) + this->as_path_asns.size() * sizeof(uint32_t);

            // TODO: We need to enable extended length encoding when we have more than 255 bytes for attribute value
            if (long_attribute_length > 255) {
                logger << log4cpp::Priority::ERROR << "Too big AS_PATH attribute length: " << long_attribute_length;
                return false;
            }

            // Populate attribute length
            bgp_attribute_as_path.attribute_length = (uint8_t)long_attribute_length;

            logger << log4cpp::Priority::DEBUG << "AS_PATH attribute length: " << uint32_t(bgp_attribute_as_path.attribute_length);

            uint32_t as_path_attribute_full_length = sizeof(bgp_attribute_as_path_t) + bgp_attribute_as_path.attribute_length;

            logger << log4cpp::Priority::DEBUG << "AS_PATH attribute full length: " << as_path_attribute_full_length;

            dynamic_binary_buffer_t as_path_as_binary_array;
            as_path_as_binary_array.set_buffer_size_in_bytes(as_path_attribute_full_length);

            // Append attribute header
            as_path_as_binary_array.append_data_as_object_ptr(&bgp_attribute_as_path);

            bgp_as_path_segment_element_t bgp_as_path_segment_element;

            // Numbers of ASNs in list
            bgp_as_path_segment_element.path_segment_length = this->as_path_asns.size();

            logger << log4cpp::Priority::DEBUG
                   << "AS_PATH segments number: " << uint32_t(bgp_as_path_segment_element.path_segment_length);

            // Append segment header
            as_path_as_binary_array.append_data_as_object_ptr(&bgp_as_path_segment_element);

            logger << log4cpp::Priority::DEBUG << "AS_PATH ASN number: " << this->as_path_asns.size();

            for (auto asn : this->as_path_asns) {
                // Append all ASNs in big endian encoding
                uint32_t asn_big_endian = fast_hton(asn);

                as_path_as_binary_array.append_data_as_object_ptr(&asn_big_endian);
            }

            if (as_path_as_binary_array.is_failed()) {
                logger << log4cpp::Priority::ERROR << "Issue with storing AS_PATH";
            }

            attributes_list.push_back(as_path_as_binary_array);
        }

        // Vector should be ordered in ascending order of attribute types
        // Next hop attribute is #3
        attributes_list.push_back(next_hop_as_binary_array);

        if (community_list.empty()) {
            return true;
        }

        // We have communities
        bgp_attribute_community_t bgp_attribute_community;

        // Each record has this of 4 bytes
        bgp_attribute_community.attribute_length = community_list.size() * sizeof(bgp_community_attribute_element_t);
        uint32_t community_attribute_full_length = sizeof(bgp_attribute_community_t) + bgp_attribute_community.attribute_length;

        dynamic_binary_buffer_t communities_list_as_binary_array;
        communities_list_as_binary_array.set_buffer_size_in_bytes(community_attribute_full_length);

        communities_list_as_binary_array.append_data_as_object_ptr(&bgp_attribute_community);

        for (auto bgp_community_element : community_list) {
            // TODO: Encode they in network byte order
            // That's HORRIBLE and very error prone approach
            bgp_community_element.host_byte_order_to_network_byte_order();

            communities_list_as_binary_array.append_data_as_object_ptr(&bgp_community_element);
        }

        // Community is attribute #8
        attributes_list.push_back(communities_list_as_binary_array);

        return true;
    }

    std::string print() const {
        std::stringstream buf;

        buf << "Prefix: " << convert_ip_as_uint_to_string(prefix.subnet_address) << "/" << prefix.cidr_prefix_length << " "
            << "Origin: " << get_origin_name_by_value(origin) << " "
            << "Next hop: " << convert_ip_as_uint_to_string(next_hop) + "/32";
        // TODO: print pretty communities!!!

        return buf.str();
    }

    // Add multiple communities in single step
    bool add_multiple_communities(std::vector<bgp_community_attribute_element_t> bgp_communities) {
        for (auto bgp_community : bgp_communities) {
            community_list.push_back(bgp_community);
        }

        return true;
    }

    bool add_community(bgp_community_attribute_element_t bgp_community) {
        community_list.push_back(bgp_community);

        return true;
    }

    std::vector<bgp_community_attribute_element_t> get_communities() const {
        return community_list;
    }

    // Add ASN to AS_PATH
    void add_asn_as_path(uint32_t asn) {
        as_path_asns.push_back(asn);
    }

    // Sets AS_PATH to specified vector of uint32_t
    void set_as_path(std::vector<uint32_t>& as_path_asns_param) {
        as_path_asns = as_path_asns_param;
    }

    private:
    uint32_t next_hop = 0;
    subnet_cidr_mask_t prefix;
    BGP_ORIGIN_TYPES origin = BGP_ORIGIN_INCOMPLETE;
    bool is_withdraw        = false;

    // TODO: it's horrible encoding idea
    // We store data here in little endian and then convert it into network byte order when need to send it to GoBGP
    std::vector<bgp_community_attribute_element_t> community_list;

    // List of ASNs in 32 bit format. May be empty
    std::vector<uint32_t> as_path_asns;
};

class IPv6UnicastAnnounce {
    public:
    void set_withdraw(bool withdraw) {
        this->is_withdraw = withdraw;
    }

    bool get_withdraw() {
        return this->is_withdraw;
    }

    void set_next_hop(subnet_ipv6_cidr_mask_t next_hop) {
        this->next_hop = next_hop;
    }

    void set_prefix(subnet_ipv6_cidr_mask_t prefix) {
        this->prefix = prefix;
    }

    void set_origin(BGP_ORIGIN_TYPES origin) {
        this->origin = origin;
    }

    subnet_ipv6_cidr_mask_t get_next_hop() const {
        return next_hop;
    }

    subnet_ipv6_cidr_mask_t get_prefix() const {
        return this->prefix;
    }

    BGP_ORIGIN_TYPES get_origin() const {
        return this->origin;
    }

    std::string print() const {
        std::stringstream buf;

        buf << "Prefix: " << convert_ipv6_subnet_to_string(prefix) << " "
            << "Origin: " << get_origin_name_by_value(origin) << " "
            << "Next hop: " << convert_ipv6_subnet_to_string(next_hop);

        return buf.str();
    }

    // Returns prefix in text form
    std::string get_prefix_in_cidr_form() const {
        return convert_ipv6_subnet_to_string(prefix);
    }

    // Add multiple communities in single step
    bool add_multiple_communities(std::vector<bgp_community_attribute_element_t> bgp_communities) {
        for (auto bgp_community : bgp_communities) {
            community_list.push_back(bgp_community);
        }

        return true;
    }

    bool add_community(bgp_community_attribute_element_t bgp_community) {
        community_list.push_back(bgp_community);

        return true;
    }

    std::vector<bgp_community_attribute_element_t> get_communities() const {
        return community_list;
    }

    // Add ASN to AS_PATH
    void add_asn_as_path(uint32_t asn) {
        as_path_asns.push_back(asn);
    }

    // Sets AS_PATH to specified vector of uint32_t
    void set_as_path(std::vector<uint32_t>& as_path_asns_param) {
        as_path_asns = as_path_asns_param;
    }

    private:
    subnet_ipv6_cidr_mask_t next_hop{};
    subnet_ipv6_cidr_mask_t prefix{};
    BGP_ORIGIN_TYPES origin = BGP_ORIGIN_INCOMPLETE;
    bool is_withdraw        = false;

    // TODO: it's horrible encoding idea
    // We store data here in little endian and then convert it into network byte order when need to send it to GoBGP
    std::vector<bgp_community_attribute_element_t> community_list;

    // TODO: we need to rework AnnounceUnicastPrefixLowLevelIPv6
    // and pull all logic from it into get_attributes() right here
    public:
    // List of ASNs in 32 bit format. May be empty
    std::vector<uint32_t> as_path_asns;
};

static_assert(sizeof(bgp_attribute_flags) == 1, "broken size for bgp_attribute_flags");
static_assert(sizeof(bgp_attribute_origin) == 4, "Bad size for bgp_attribute_origin");
static_assert(sizeof(bgp_attribute_next_hop_ipv4) == 7, "Bad size for bgp_attribute_next_hop_ipv4");

bool is_bgp_community_valid(std::string community_as_string);
bool decode_ipv6_announce_from_binary_encoded_atributes(std::vector<dynamic_binary_buffer_t> binary_attributes,
                                                        IPv6UnicastAnnounce& ipv6_announce);
bool decode_mp_reach_attribute_to_ipv6_announce(uint8_t* data,
                                                int length,
                                                bgp_attibute_common_header_t bgp_attibute_common_header,
                                                IPv6UnicastAnnounce& ipv6_announce);
bool encode_ipv6_announces_into_bgp_mp_reach_attribute_internal(const IPv6UnicastAnnounce& ipv6_announce,
                                                                dynamic_binary_buffer_t& bgp_mp_reach_ipv6_attribute);

bool encode_ipv6_announces_into_bgp_mp_reach_attribute(const IPv6UnicastAnnounce& ipv6_announce,

                                                       dynamic_binary_buffer_t& bgp_mp_reach_ipv6_attribute);
bool encode_ipv6_prefix(const subnet_ipv6_cidr_mask_t& prefix, dynamic_binary_buffer_t& dynamic_buffer);
bool read_bgp_community_from_string(std::string community_as_string, bgp_community_attribute_element_t& bgp_community_attribute_element);