{#-
 # Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 # Copyright (C) 2021  OpenCyphal Development Team  <opencyphal.org>
 # This software is distributed under the terms of the MIT License.
-#}
{%- from '_definitions.j2' import assert -%}
{%- ifuses "std_variant" %}
// +-------------------------------------------------------------------------------------------------------------------+
// | This implementation uses the C++17 standard library variant type with wrappers for the emplace and
// | get_if methods to support forward-compatibility with the C++14 version of this object. The union_value type
// | extends std::variant and can be used with the entire set of variant methods. Using std::variant directly does mean
// | your code will not be backwards compatible with the C++14 version of this object.
// +-------------------------------------------------------------------------------------------------------------------+
{% else %}
// +-------------------------------------------------------------------------------------------------------------------+
// | This implementation uses a minimal variant implementation that is forward-compatible with the same types generated
// | using the C++17 variant type in the standard library. This minimal variant implementation is limited in the
// | following ways:
// |    1. Supports only emplace and get_if.
// |    2. Only support access by index (see the IndexOf property of the VariantType).
// |    3. This object cannot be copy-constructed nor move-constructed.
// |    4. There is an O(n) lookup in this object's destructor and in the
// |       emplace method.
// |
// | The C++17 version of this object will define the same emplace and get_if wrappers so code written against this
// | version will be fully-forward compatible, but the C++17 version exposes the variant type directly allowing full
// | use of that standard library feature – it is therefore not backwards-compatible.
// +-------------------------------------------------------------------------------------------------------------------+
{% endifuses -%}
{{ composite_type.doc | block_comment('cpp-doxygen', 0, 120) }}
struct {% if composite_type.deprecated -%}
[[deprecated("{{ composite_type }} is reaching the end of its life; there may be a newer version available")]]
{%- endif -%}
{{composite_type|short_reference_name}} final
{
    struct _traits_  // The name is surrounded with underscores to avoid collisions with DSDL attributes.
    {
        _traits_() = delete;
{%- if T.has_fixed_port_id %}
        static constexpr bool HasFixedPortID = true;
        static constexpr {{ typename_unsigned_port }} FixedPortId = {{ T.fixed_port_id }}U;
{%- else %}
        /// This type does not have a fixed port-ID. See https://forum.opencyphal.org/t/choosing-message-and-service-ids/889
        static constexpr bool HasFixedPortID = false;
{% endif -%}
{%- if T is ServiceType %}
        static constexpr bool IsServiceType  = true;
        static constexpr bool IsService      = false;
        static constexpr bool IsRequest      = {{ (composite_type == T.request_type) | string | lower }};
        static constexpr bool IsResponse     = {{ (composite_type == T.response_type) | string | lower }};
{%- else %}
        static constexpr bool IsServiceType = false;
{% endif -%}
    {%- assert composite_type.extent % 8 == 0 %}
    {%- assert composite_type.inner_type.extent % 8 == 0 %}
        /// Extent is the minimum amount of memory required to hold any serialized representation of any compatible
        /// version of the data type; or, on other words, it is the the maximum possible size of received objects of this type.
        /// The size is specified in bytes (rather than bits) because by definition, extent is an integer number of bytes long.
        /// When allocating a deserialization (RX) buffer for this data type, it should be at least extent bytes large.
        /// When allocating a serialization (TX) buffer, it is safe to use the size of the largest serialized representation
        /// instead of the extent because it provides a tighter bound of the object size; it is safe because the concrete type
        /// is always known during serialization (unlike deserialization). If not sure, use extent everywhere.
        static constexpr {{ typename_unsigned_length }} ExtentBytes                  = {# -#}
            {{ composite_type.extent // 8 }}UL;
        static constexpr {{ typename_unsigned_length }} SerializationBufferSizeBytes = {# -#}
            {{ composite_type.inner_type.extent // 8 }}UL;
        static_assert(ExtentBytes >= SerializationBufferSizeBytes, "Internal constraint violation");
        static_assert({# -#}
            ExtentBytes < (std::numeric_limits<{{ typename_unsigned_bit_length }}>::max() / 8U), {# -#}
            "This message is too large to be handled by the selected types");
{% if options.ctor_convention != ConstructorConvention.Default.value %}
        using allocator_type = {{ options.allocator_type }}<decltype(nullptr)>;
{% endif %}
{%- for field in composite_type.fields_except_padding %}
    {%- if loop.first %}
        struct TypeOf
        {
            TypeOf() = delete;
    {%- endif %}
            using {{field.name|id}} = {{ field.data_type | declaration }};
    {%- if loop.last %}
        };
    {%- endif %}
{%- endfor %}
    };
{% if options.ctor_convention != ConstructorConvention.Default.value %}
    {%- if options.allocator_is_default_constructible %}
    // Default constructor
    {{composite_type|short_reference_name}}()
    {%- if composite_type.fields_except_padding %} :{% endif %}
    {%- for field in composite_type.fields_except_padding %}
        {{ field | id }}{{ field.data_type | default_value_initializer }}{%if not loop.last %},{%endif %}
    {%- endfor %}
    {
    }
    {%- endif %}

    // Allocator constructor
    explicit {{composite_type|short_reference_name}}(const _traits_::allocator_type& allocator)
    {%- if composite_type.fields_except_padding %} :{% endif %}
    {%- for field in composite_type.fields_except_padding %}
        {{ field | id }}{{ field | value_initializer(SpecialMethod.AllocatorConstructor) }}{%if not loop.last %},{%endif %}
    {%- endfor %}
    {
        (void)allocator; // avoid unused param warning
    }

    {% if composite_type.fields_except_padding %}
    // Initializing constructor
    {{ composite_type | explicit_decorator(SpecialMethod.InitializingConstructorWithAllocator)}}(
    {%- for field in composite_type.fields_except_padding %}
        const _traits_::TypeOf::{{ field | id }}& {{ field | id }},
    {%- endfor %}
        const _traits_::allocator_type& allocator
    {%- if options.allocator_is_default_constructible %} = _traits_::allocator_type(){% endif %})
    {%- if composite_type.fields_except_padding %} :{% endif %}
    {%- for field in composite_type.fields_except_padding %}
        {{ field | id }}{{ field | value_initializer(SpecialMethod.InitializingConstructorWithAllocator) }}{%if not loop.last %},{%endif %}
    {%- endfor %}
    {
        (void)allocator; // avoid unused param warning
    }
    {%- endif %}

    // Copy constructor
    {{composite_type|short_reference_name}}(const {{composite_type|short_reference_name}}&) = default;

    // Copy constructor with allocator
    {{composite_type|short_reference_name}}(const {{composite_type|short_reference_name}}& rhs, const _traits_::allocator_type& allocator)
    {%- if composite_type.fields_except_padding %} :{% endif %}
    {%- for field in composite_type.fields_except_padding %}
        {{ field | id }}{{ field | value_initializer(SpecialMethod.CopyConstructorWithAllocator) }}{%if not loop.last %},{%endif %}
    {%- endfor %}
    {
        (void)rhs;       // avoid unused param warning
        (void)allocator; // avoid unused param warning
    }

    // Move constructor
    {{composite_type|short_reference_name}}({{composite_type|short_reference_name}}&&) = default;

    // Move constructor with allocator
    {{composite_type|short_reference_name}}({{composite_type|short_reference_name}}&& rhs, const _traits_::allocator_type& allocator)
    {%- if composite_type.fields_except_padding %} :{% endif %}
    {%- for field in composite_type.fields_except_padding %}
        {{ field | id }}{{ field | value_initializer(SpecialMethod.MoveConstructorWithAllocator) }}{%if not loop.last %},{%endif %}
    {%- endfor %}
    {
        (void)rhs;       // avoid unused param warning
        (void)allocator; // avoid unused param warning
    }

    // Copy assignment
    {{composite_type|short_reference_name}}& operator=(const {{composite_type|short_reference_name}}&) = default;

    // Move assignment
    {{composite_type|short_reference_name}}& operator=({{composite_type|short_reference_name}}&&) = default;

    // Destructor
    ~{{composite_type|short_reference_name}}() = default;
{%- endif %}

{%- for constant in composite_type.constants %}
    {% if loop.first %}
    // +---------------------------------------------------------------------------------------------------------------+
    // | CONSTANTS
    // +---------------------------------------------------------------------------------------------------------------+
    {% endif -%}
    {{ constant.doc | block_comment('cpp-doxygen', 4, 120) }}
    static constexpr {{ constant.data_type | declaration }} {{ constant.name | id }} = {{ constant | constant_value }};
{%- endfor -%}
{%- if composite_type.inner_type is UnionType -%}
{%- ifuses "std_variant" -%}
{% include '_fields_as_variant.j2' %}
{%- else -%}
{% include '_fields_as_union.j2' %}
{%- endifuses -%}
{%- for field in composite_type.fields_except_padding %}
    bool is_{{field.name|id}}() const {
        return VariantType::IndexOf::{{field.name|id}} == union_value.index();
    }

    typename std::add_pointer<_traits_::TypeOf::{{field.name|id}}>::type get_{{field.name|id}}_if(){
        return VariantType::get_if<VariantType::IndexOf::{{field.name|id}}>(&union_value);
    }

    typename std::add_pointer<const _traits_::TypeOf::{{field.name|id}}>::type get_{{field.name|id}}_if() const{
        return VariantType::get_if<VariantType::IndexOf::{{field.name|id}}>(&union_value);
    }

    typename std::add_lvalue_reference<_traits_::TypeOf::{{field.name|id}}>::type get_{{field.name|id}}(){
        {{ assert('is_%s()' | format(field.name | id)) }}
        return *VariantType::get_if<VariantType::IndexOf::{{field.name|id}}>(&union_value);
    }

    typename std::add_lvalue_reference<const _traits_::TypeOf::{{field.name|id}}>::type get_{{field.name|id}}() const{
        {{ assert('is_%s()' | format(field.name | id)) }}
        return *VariantType::get_if<VariantType::IndexOf::{{field.name|id}}>(&union_value);
    }

    template<class... Args> typename std::add_lvalue_reference<_traits_::TypeOf::{{field.name|id}}>::type
    set_{{field.name|id}}(Args&&...v){
        return union_value.emplace<VariantType::IndexOf::{{field.name|id}}>(v...);
    }
{%- endfor %}
{%- else -%}
{% include '_fields.j2' %}
{%- endif %}
};

{% if not nunavut.support.omit %}
inline nunavut::support::SerializeResult serialize(const {{composite_type|short_reference_name}}& obj,
                                                   nunavut::support::bitspan out_buffer)
{
    {% from 'serialization.j2' import serialize -%}
    {{ serialize(composite_type) | trim | remove_blank_lines }}
}

inline nunavut::support::SerializeResult deserialize({{composite_type|short_reference_name}}& obj,
                                                     nunavut::support::const_bitspan in_buffer)
{
    {% from 'deserialization.j2' import deserialize -%}
    {{ deserialize(composite_type) | trim | remove_blank_lines }}
}
{%- endif %}

{#- -#}
