{#-
 # Copyright (C) OpenCyphal Development Team  <opencyphal.org>
 # Copyright Amazon.com Inc. or its affiliates.
 # SPDX-License-Identifier: MIT
-#}
{%- from '_definitions.j2' import assert -%}
{%- ifuses "variant" %}
// +-------------------------------------------------------------------------------------------------------------------+
// | This implementation uses the C++17 standard library variant type with wrappers for the emplace and
// | get_if methods to support backwards-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 %}
{%- ifuses "cetl" %}
// +-------------------------------------------------------------------------------------------------------------------+
// | This implementation uses the CETL library C++17 polyfill variant type with wrappers for the emplace and
// | get_if methods to support backwards-compatibility with the C++14 version of this object. The union_value type
// | extends cetl::pf17::variant and can be used with the entire set of variant methods. Using cetl::pf17::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 -%}
{% 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
{
{%- if options.ctor_convention != ConstructorConvention.DEFAULT %}
    using allocator_type = {{ options.allocator_type }}<void>;
{%- endif %}

    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 %}
        static constexpr const char* FullNameAndVersion() { return "{{ composite_type }}"; }
    {%  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");
{%- 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 %}
{%- for field in composite_type.fields_except_padding if field.data_type is ArrayType %}
    {%- if loop.first %}
        struct ArrayCapacity
        {
            ArrayCapacity() = delete;
    {%- endif %}
            static constexpr std::size_t {{field.name|id}} = {{ field.data_type.capacity }}UL;
    {%- if loop.last %}
        };
    {%- endif %}
{%- endfor %}
    };
{% if options.ctor_convention != ConstructorConvention.DEFAULT %}
    {%- if options.allocator_is_default_constructible or composite_type.bit_length_set.fixed_length %}
    // Default constructor
    {%- if composite_type.inner_type is UnionType %}
    {{composite_type|short_reference_name}}() = default;
    {%- else %}
    {{composite_type|short_reference_name}}()
    {%- for field in composite_type.fields_except_padding %}
        {%if loop.first %}: {%else%}, {%endif-%}
        {{ field | id }}{{ field.data_type | default_value_initializer }}
    {%- endfor %}
    {
    }
    {%- endif %}
    {%- endif %}

    // Allocator constructor
    explicit {{composite_type|short_reference_name}}(const allocator_type& allocator)
    {%- if composite_type.inner_type is UnionType %}
        {%- set member_init_list_started = False -%}
        {%- if composite_type.fields_except_padding[0] | needs_allocator -%}
        {%- if options.ctor_convention != ConstructorConvention.DEFAULT %}
        : union_value{cetl::in_place_index_t<0u>{}, allocator}
        {%- set member_init_list_started = True -%}
        {%- endif -%}
        {%- endif -%}
        {%- if not composite_type.bit_length_set.fixed_length -%}
        {%- if member_init_list_started %}
        ,{{- " " -}}
        {%- else %}
        :{{- " " -}}
        {%- endif -%}
        allocator_{allocator}
        {%- endif %}
    {%- else %}
    {%- for field in composite_type.fields_except_padding %}
        {%if loop.first %}: {%else%}, {%endif-%}
        {{ field | id }}{{ field | value_initializer(SpecialMethod.ALLOCATOR_CONSTRUCTOR) }}
    {%- endfor %}
    {%- endif %}
    {
        static_cast<void>(allocator); // avoid unused param warning
    }

    {% if composite_type.fields_except_padding %}
    // Initializing constructor
    {%- if composite_type.inner_type is UnionType %}
    template<std::size_t I, class T>
    {{composite_type|short_reference_name}}(cetl::in_place_index_t<I>, T&& v, const allocator_type& allocator
                                            {%- if options.allocator_is_default_constructible %} = allocator_type(){% endif %} )
        : union_value{construct_variant_type(cetl::in_place_index_t<I>{}, std::forward<T>(v), allocator)}
        {%- if not composite_type.bit_length_set.fixed_length %}
        , allocator_{allocator}
        {%- endif %}
    {}
    {%- else %}
    {{ composite_type | explicit_decorator(SpecialMethod.INITIALIZING_CONSTRUCTOR_WITH_ALLOCATOR)}}(
    {%- for field in composite_type.fields_except_padding %}
        const _traits_::TypeOf::{{ field | id }}& param_{{ field | id }},
    {%- endfor %}
        const allocator_type& allocator
    {%- if options.allocator_is_default_constructible %} = allocator_type(){% endif %})
    {%- for field in composite_type.fields_except_padding %}
        {%if loop.first %}: {%else%}, {%endif-%}
        {{ field | id }}{{ field | value_initializer(SpecialMethod.INITIALIZING_CONSTRUCTOR_WITH_ALLOCATOR, "param_") }}
    {%- endfor %}
    {
        static_cast<void>(allocator); // avoid unused param warning
    }
    {%- endif %}
    {%- endif %}

    // Copy constructor
    {{composite_type|short_reference_name}}(const {{composite_type|short_reference_name}}& rhs)
    {%- if composite_type.inner_type is UnionType and not composite_type.bit_length_set.fixed_length %}
        : union_value{copy_variant_value_from(rhs.union_value, rhs.allocator_.select_on_container_copy_construction())}
        , allocator_{rhs.allocator_.select_on_container_copy_construction()}
    {}
    {%- else -%}
    {{ " " }}= default;
    {%- endif %}

    // Copy constructor with allocator
    {{composite_type|short_reference_name}}(const {{composite_type|short_reference_name}}& rhs, const allocator_type& allocator)
    {%- if composite_type.inner_type is UnionType %}
        : union_value{copy_variant_value_from(rhs.union_value, allocator)}
        {%- if not composite_type.bit_length_set.fixed_length %}
        , allocator_{allocator}
        {%- endif %}
    {}
    {%- else %}
    {%- for field in composite_type.fields_except_padding %}
        {%if loop.first %}: {%else%}, {%endif-%}
        {{ field | id }}{{ field | value_initializer(SpecialMethod.COPY_CONSTRUCTOR_WITH_ALLOCATOR) }}
    {%- endfor %}
    {
        static_cast<void>(rhs);       // avoid unused param warning
        static_cast<void>(allocator); // avoid unused param warning
    }
    {%- endif %}

    // 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 allocator_type& allocator)
    {%- if composite_type.inner_type is UnionType %}
        : union_value{move_variant_value_from(std::move(rhs.union_value), allocator)}
        {%- if not composite_type.bit_length_set.fixed_length %}
        , allocator_{allocator}
        {%- endif %}
    {}
    {%- else %}
    {%- for field in composite_type.fields_except_padding %}
        {%if loop.first %}: {%else%}, {%endif-%}
        {{ field | id }}{{ field | value_initializer(SpecialMethod.MOVE_CONSTRUCTOR_WITH_ALLOCATOR) }}
    {%- endfor %}
    {
        static_cast<void>(rhs);       // avoid unused param warning
        static_cast<void>(allocator); // avoid unused param warning
    }
    {%- endif %}

    {% if composite_type.inner_type is UnionType and not composite_type.bit_length_set.fixed_length %}
    // Copy assignment
    {{composite_type|short_reference_name}}& operator=(const {{composite_type|short_reference_name}}& rhs)
    {
        if (this != &rhs)
        {
            nunavut::support::assignInplace(allocator_, rhs.allocator_.select_on_container_copy_construction());
            union_value = copy_variant_value_from(rhs.union_value, allocator_);
        }
        return *this;
    }

    // Move assignment
    {{composite_type|short_reference_name}}& operator=({{composite_type|short_reference_name}}&& rhs)
    {
        nunavut::support::assignInplace(allocator_, std::move(rhs.allocator_));
        union_value = move_variant_value_from(std::move(rhs.union_value), allocator_);
        return *this;
    }
    {% else %}
    // 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;
    {%- endif %}

    // 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 "variant" -%}
{% include '_fields_as_cetl_variant.j2' %}
{%- else -%}
{% include '_fields_as_union.j2' %}
{%- endifuses -%}
{%- if options.ctor_convention != ConstructorConvention.DEFAULT %}
{%- if composite_type.inner_type is UnionType and not composite_type.bit_length_set.fixed_length %}
    allocator_type allocator_;
{%- endif %}
{%- endif %}
{%- 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);
    }
    {%- if field.data_type is FixedLengthArrayType and options.ctor_convention != ConstructorConvention.DEFAULT -%}
    {%- if not composite_type.bit_length_set.fixed_length and (field.data_type.element_type | needs_allocator) %}
    typename std::add_lvalue_reference<_traits_::TypeOf::{{field.name|id}}>::type set_{{field.name|id}}()
    {
        // Fixed-length array elements do not have a default constructor, initialize each instance with member allocator
        return union_value.emplace<VariantType::IndexOf::{{ field.name | id }}>(
            _traits_::TypeOf::{{field.name|id}}{
                {% for i in range(field.data_type.capacity) -%}
                {%- if i > 0 -%},{{ " " }}{%- endif -%}{{ field.data_type.element_type | declaration }}{allocator_}
                {%- endfor %}
            }
        );
    }
    {%- endif -%}
    {%- endif %}
    template<class... Args> typename
    std::add_lvalue_reference<_traits_::TypeOf::{{field.name|id}}>::type set_{{field.name|id}}(Args&&...v)
    {
        {%- if not composite_type.bit_length_set.fixed_length and (field | needs_allocator) -%}
        {%- if options.ctor_convention == ConstructorConvention.USES_LEADING_ALLOCATOR %}
        return union_value.emplace<VariantType::IndexOf::{{ field.name | id }}>(std::allocator_arg, allocator_, std::forward<Args>(v)...);
        {%- elif options.ctor_convention == ConstructorConvention.USES_TRAILING_ALLOCATOR %}
        return union_value.emplace<VariantType::IndexOf::{{field.name | id}}>(std::forward<Args>(v)..., allocator_);
        {%- else %}
        return union_value.emplace<VariantType::IndexOf::{{field.name | id}}>(std::forward<Args>(v)...);
        {%- endif -%}
        {%- else %}
        return union_value.emplace<VariantType::IndexOf::{{field.name | id}}>(std::forward<Args>(v)...);
        {%- endif %}
    }
{%- endfor %}

{%- if options.ctor_convention != ConstructorConvention.DEFAULT %}
private:

    template<std::size_t I, class T,
             std::enable_if_t<
                std::is_constructible<
                    typename VariantType::alternative<I, VariantType::Base>::type,
                    T&&,
                    const allocator_type&
                >::value>* = nullptr>
    static VariantType construct_variant_type(cetl::in_place_index_t<I>, T&& v, const allocator_type& allocator)
    {
        return VariantType{
                cetl::in_place_index_t<I>{},
                std::forward<T>(v),
                allocator
            };
    }

    template<std::size_t I, class T,
             std::enable_if_t<
                !std::is_constructible<
                    typename VariantType::alternative<I, VariantType::Base>::type,
                    T&&,
                    const allocator_type&
                >::value>* = nullptr>
    static VariantType construct_variant_type(cetl::in_place_index_t<I>, T&& v, const allocator_type& allocator)
    {
        (void)allocator; // Avoid unused parameter warning

        return VariantType{
                cetl::in_place_index_t<I>{},
                std::forward<T>(v)
            };
    }

    static VariantType copy_variant_value_from(const VariantType& src, const allocator_type& allocator)
    {
        (void)allocator; // Avoid unused parameter warning

{%- for field in composite_type.fields_except_padding %}
        {%- if loop.last %}
        else
        {%- else %}
        {% if loop.first %}if{% else %}else if{% endif %} (src.index() == VariantType::IndexOf::{{ field.name | id }})
        {%- endif %}
        {
    {%- if field.data_type is FixedLengthArrayType and field.data_type.element_type | needs_allocator %}
            // Fixed length array, emplace all elements initialized with member allocator
            const auto& src_array = *VariantType::get_if<VariantType::IndexOf::{{ field.name|id }}>(&src);
            return construct_variant_type(
                cetl::in_place_index_t<VariantType::IndexOf::{{ field.name|id }}>{},
                _traits_::TypeOf::{{ field.name|id }}{
                    {% for i in range(field.data_type.capacity) -%}
                    {%- if i > 0 -%},{{ " " }}{%- endif -%}
                        {{ field.data_type.element_type | declaration }}{src_array[{{ i }}], allocator}
                    {%- endfor %}
                },
                allocator
            );
    {%- else %}
            return construct_variant_type(
                cetl::in_place_index_t<VariantType::IndexOf::{{ field.name | id }}>{},
                *VariantType::get_if<VariantType::IndexOf::{{field.name|id}}>(&src),
                allocator
            );
    {%- endif %}
        }
{%- endfor %}
    }

    static VariantType move_variant_value_from(VariantType&& src, const allocator_type& allocator)
    {
{%- for field in composite_type.fields_except_padding %}
        {%- if loop.last %}
        else
        {%- else %}
        {% if loop.first %}if{% else %}else if{% endif %} (src.index() == VariantType::IndexOf::{{ field.name | id }})
        {%- endif %}
        {
    {%- if field.data_type is FixedLengthArrayType and field.data_type.element_type | needs_allocator %}
            // Fixed length array, emplace all elements initialized with member allocator
            const auto& src_array = *VariantType::get_if<VariantType::IndexOf::{{ field.name|id }}>(&src);
            return construct_variant_type(
                cetl::in_place_index_t<VariantType::IndexOf::{{ field.name|id }}>{},
                _traits_::TypeOf::{{ field.name|id }}{
                    {% for i in range(field.data_type.capacity) -%}
                    {%- if i > 0 -%},{{ " " }}{%- endif -%}
                        {{ field.data_type.element_type | declaration }}{std::move(src_array[{{ i }}]), allocator}
                    {%- endfor %}
                },
                allocator
            );
    {%- else %}
            return construct_variant_type(
                cetl::in_place_index_t<VariantType::IndexOf::{{ field.name | id }}>{},
                std::move(*VariantType::get_if<VariantType::IndexOf::{{field.name|id}}>(&src)),
                allocator
            );
    {%- endif %}
        }
{%- endfor %}
    }
{%- endif %}
{%- else -%}
{% include '_fields.j2' %}
{%- endif %}
};

{% if not options.omit_serialization_support %}
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 %}

{#- -#}
