{#-
 # Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 # Copyright (C) 2021  UAVCAN Development Team  <uavcan.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) }}
{{ composite_type | definition_begin }} final
{
    // +---------------------------------------------------------------------------------------------------------------+
    // | PORT IDENTIFIERS
    // +---------------------------------------------------------------------------------------------------------------+
{%- 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 }} EXTENT_BYTES                    = {#- -#}
        {{ composite_type.extent // 8 }}UL;
    static constexpr {{ typename_unsigned_length }} SERIALIZATION_BUFFER_SIZE_BYTES = {#- -#}
        {{ composite_type.inner_type.extent // 8 }}UL;
    static_assert(EXTENT_BYTES >= SERIALIZATION_BUFFER_SIZE_BYTES, "Internal constraint violation");
    static_assert({# -#}
        EXTENT_BYTES < (std::numeric_limits<{{ typename_unsigned_bit_length }}>::max() /8U), {# -#}
        "This message is too large to be handled by current types!");

{%- 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<{{ field.data_type | declaration }}>::type get_{{ field.name | id }}_if(){
        return VariantType::get_if<VariantType::IndexOf::{{ field.name | id }}>(&union_value);
    }

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

    typename std::add_lvalue_reference<{{ field.data_type | declaration }}>::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 {{ field.data_type | declaration }}>::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<{{ field.data_type | declaration }}>::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 %}

    nunavut::support::SerializeResult
    serialize(nunavut::support::bitspan out_buffer) const
    {
        {% from 'serialization.j2' import serialize -%}
        {{ serialize(composite_type) | trim | remove_blank_lines | indent }}
    }

    nunavut::support::SerializeResult
    deserialize(nunavut::support::const_bitspan in_buffer)
    {
        {% from 'deserialization.j2' import deserialize -%}
        {{ deserialize(composite_type) | trim | remove_blank_lines | indent }}
    }
{%- endif %}
}{{ composite_type | definition_end }}
{#- -#}
