{#-
 # Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 # Copyright (C) 2020  UAVCAN Development Team  <uavcan.org>
 # This software is distributed under the terms of the MIT License.
 # Authors: David Lenfesty, Scott Dixon <dixonsco@amazon.com>, Pavel Kirienko <pavel@uavcan.org>,
 #          Peter van der Perk <peter.vanderperk@nxp.com>
-#}

{% from 'definitions.j2' import assert, LITTLE_ENDIAN %}


{# ----------------------------------------------------------------------------------------------------------------- #}
{% macro deserialize(t) %}
    if ((out_obj == {{ valuetoken_null }}) || (inout_buffer_size_bytes == {{ valuetoken_null }}) || {# -#}
        ((buffer == {{ valuetoken_null }}) && (0 != *inout_buffer_size_bytes)))
    {
        return -NUNAVUT_ERROR_INVALID_ARGUMENT;
    }
    if (buffer == {{ valuetoken_null }})
    {
        buffer = (const {{ typename_byte }}*)"";
    }
{% if t.inner_type.bit_length_set.max > 0 %}
    {{ _deserialize_impl(t) }}
{% else %}
    *inout_buffer_size_bytes = 0U;
{% endif %}
    return NUNAVUT_SUCCESS;
{% endmacro %}


{# ----------------------------------------------------------------------------------------------------------------- #}
{% macro _deserialize_impl(t) %}
    const {{ typename_unsigned_length }} capacity_bytes = *inout_buffer_size_bytes;
    const {{ typename_unsigned_bit_length }} capacity_bits = capacity_bytes * ({{ typename_unsigned_bit_length }}) 8U;
    {{ typename_unsigned_bit_length }} offset_bits = 0U;
{% if t.inner_type is StructureType %}
    {% for f, offset in t.inner_type.iterate_fields_with_offsets() %}
        {% if loop.first %}
            {% assert f.data_type.alignment_requirement <= t.inner_type.alignment_requirement %}
        {% else %}
    {{ _pad_to_alignment(f.data_type.alignment_requirement) }}
        {% endif %}
    // {{ f }}
    {{ _deserialize_any(f.data_type, 'out_obj->' + (f|id), offset)|trim|remove_blank_lines }}
    {% endfor %}
{% elif t.inner_type is UnionType %}
    // Union tag field: {{ t.inner_type.tag_field_type }}
    {{ _deserialize_integer(t.inner_type.tag_field_type, 'out_obj->_tag_', 0|bit_length_set)|trim|remove_blank_lines }}
    {% for f, offset in t.inner_type.iterate_fields_with_offsets() %}
    {{ 'if' if loop.first else 'else if' }} ({{ loop.index0 }}U == out_obj->_tag_)  // {{ f }}
    {
        {%- assert f.data_type.alignment_requirement <= (offset.min) %}
        {{ _deserialize_any(f.data_type, 'out_obj->' + (f|id), offset)|trim|remove_blank_lines|indent }}
    }
    {%- endfor %}
    else
    {
        return -NUNAVUT_ERROR_REPRESENTATION_BAD_UNION_TAG;
    }
{% else %}{% assert False %}
{% endif %}
    {{ _pad_to_alignment(t.inner_type.alignment_requirement) }}
    {{ assert('offset_bits % 8U == 0U') }}
    *inout_buffer_size_bytes = ({{ typename_unsigned_length }}) (nunavutChooseMin(offset_bits, capacity_bits) / 8U);
    {{ assert('capacity_bytes >= *inout_buffer_size_bytes') }}
{% endmacro %}


{# ----------------------------------------------------------------------------------------------------------------- #}
{% macro _pad_to_alignment(n_bits) %}
{%- if n_bits > 1 -%}
    {%- assert n_bits in (8, 16, 32, 64) -%}
    offset_bits = (offset_bits + {{ n_bits - 1 }}U) & ~({{ typename_unsigned_bit_length }}) {{ n_bits - 1 }}U;  {# -#}
        // Align on {{ n_bits }} bits.
{%- endif -%}
{% endmacro %}


{# ----------------------------------------------------------------------------------------------------------------- #}
{% macro _deserialize_any(t, reference, offset) %}
{% if t.alignment_requirement > 1 %}
    {{ assert('offset_bits %% %dU == 0U'|format(t.alignment_requirement)) }}
{% endif %}
{% if offset.is_aligned_at_byte() %}
    {{ assert('offset_bits % 8U == 0U') }}
{% endif %}
{%   if t is VoidType %}                {{- _deserialize_void                 (t,            offset) }}
{% elif t is BooleanType %}             {{- _deserialize_boolean              (t, reference, offset) }}
{% elif t is IntegerType %}             {{- _deserialize_integer              (t, reference, offset) }}
{% elif t is FloatType %}               {{- _deserialize_float                (t, reference, offset) }}
{% elif t is FixedLengthArrayType %}    {{- _deserialize_fixed_length_array   (t, reference, offset) }}
{% elif t is VariableLengthArrayType %} {{- _deserialize_variable_length_array(t, reference, offset) }}
{% elif t is CompositeType %}           {{- _deserialize_composite            (t, reference, offset) }}
{% else %}{% assert False %}
{% endif %}
{% endmacro %}


{# ----------------------------------------------------------------------------------------------------------------- #}
{% macro _deserialize_void(t, offset) %}
    offset_bits += {{ t.bit_length }};
{% endmacro %}


{# ----------------------------------------------------------------------------------------------------------------- #}
{% macro _deserialize_boolean(t, reference, offset) %}
    if (offset_bits < capacity_bits)
    {
{% if offset.is_aligned_at_byte() %}
        {{ reference }} = (buffer[offset_bits / 8U] & 1U) != 0U;
{% else %}
        {{ reference }} = (buffer[offset_bits / 8U] & (1U << (offset_bits % 8U))) != 0U;
{% endif %}
    }
    else
    {
        {{ reference }} = {{ valuetoken_false }};
    }
    offset_bits += 1U;
{% endmacro %}


{# ----------------------------------------------------------------------------------------------------------------- #}
{% macro _deserialize_integer(t, reference, offset) %}
{% set getter = 'nunavutGet%s%d'|format('U' if t is UnsignedIntegerType else 'I', t|to_standard_bit_length) %}
{# Mem-copy optimization is difficult to perform on non-standard-size signed integers because the C standard does
 # not define a portable way of unsigned-to-signed conversion (but the other way around is well-defined).
 # See 6.3.1.8 Usual arithmetic conversions, 6.3.1.3 Signed and unsigned integers.
 # This template can be greatly expanded with additional special cases if needed.
 #}
{% if offset.is_aligned_at_byte() and t is UnsignedIntegerType and t.bit_length <= 8 %}
    if ((offset_bits + {{ t.bit_length }}U) <= capacity_bits)
    {
        {{ reference }} = buffer[offset_bits / 8U] & {{ 2 ** t.bit_length - 1 }}U;
    }
    else
    {
        {{ reference }} = 0U;
    }
{% else %}
    {{ reference }} = {{ getter }}(&buffer[0], capacity_bytes, offset_bits, {{ t.bit_length }});
{% endif %}
    offset_bits += {{ t.bit_length }}U;
{% endmacro %}


{# ----------------------------------------------------------------------------------------------------------------- #}
{% macro _deserialize_float(t, reference, offset) %}
    {# TODO: apply special case optimizations for aligned data and little-endian IEEE754-conformant platforms. #}
    {{ reference }} = nunavutGetF{{ t.bit_length }}(&buffer[0], capacity_bytes, offset_bits);
    offset_bits += {{ t.bit_length }}U;
{% endmacro %}


{# ----------------------------------------------------------------------------------------------------------------- #}
{% macro _deserialize_fixed_length_array(t, reference, offset) %}
{# SPECIAL CASE: PACKED BIT ARRAY #}
{% if t.element_type is BooleanType %}
    nunavutGetBits(&{{ reference }}_bitpacked_[0], &buffer[0], capacity_bytes, offset_bits, {{ t.capacity }}UL);
    offset_bits += {{ t.capacity }}UL;

{# SPECIAL CASE: BYTES-LIKE ARRAY #}
{% elif t.element_type is PrimitiveType and t.element_type.bit_length == 8 and t.element_type is zero_cost_primitive %}
    nunavutGetBits(&{{ reference }}[0], &buffer[0], capacity_bytes, offset_bits, {{ t.capacity }}UL * 8U);
    offset_bits += {{ t.capacity }}UL * 8U;

{# SPECIAL CASE: ZERO-COST PRIMITIVES #}
{% elif t.element_type is PrimitiveType and t.element_type is zero_cost_primitive %}
    {% if t.element_type is FloatType %}
    static_assert(NUNAVUT_PLATFORM_IEEE754_FLOAT, "Native IEEE754 binary32 required. TODO: relax constraint");
        {% if t.element_type.bit_length > 32 %}
    static_assert(NUNAVUT_PLATFORM_IEEE754_DOUBLE, "Native IEEE754 binary64 required. TODO: relax constraint");
        {% endif %}
    {% endif %}
    nunavutGetBits(&{{ reference }}[0], &buffer[0], capacity_bytes, offset_bits, {# -#}
                   {{ t.capacity }}UL * {{ t.element_type.bit_length }}U);
    offset_bits += {{ t.capacity }}UL * {{ t.element_type.bit_length }}U;

{# GENERAL CASE #}
{% else %}
    {# Element offset is the superposition of each individual element offset plus the array's own offset.
     # For example, an array like uint8[3] offset by 16 bits would have its element_offset = {16, 24, 32}.
     # We can also unroll element deserialization for small arrays (e.g., below ~10 elements) to take advantage of
     # spurious alignment of elements but the benefit of such optimization is believed to be negligible. #}
    {% set element_offset = offset + t.element_type.bit_length_set.repeat_range(t.capacity - 1) %}
    {% set ref_index = 'index'|to_template_unique_name %}
    for (size_t {{ ref_index }} = 0U; {{ ref_index }} < {{ t.capacity }}UL; ++{{ ref_index }})
    {
        {{ _deserialize_any(t.element_type, reference + ('[%s]'|format(ref_index)), element_offset)|trim|indent }}
    }
    {# Size cannot be checked here because if implicit zero extension rule is applied it won't match. #}
{% endif %}
{% endmacro %}


{# ----------------------------------------------------------------------------------------------------------------- #}
{% macro _deserialize_variable_length_array(t, reference, offset) %}
{# DESERIALIZE THE IMPLICIT ARRAY LENGTH FIELD #}
    // Array length prefix: {{ t.length_field_type }}
    {{ _deserialize_integer(t.length_field_type, reference + '.count', offset) }}
    if ({{ reference }}.count > {{ t.capacity }}U)
    {
        return -NUNAVUT_ERROR_REPRESENTATION_BAD_ARRAY_LENGTH;
    }

{# COMPUTE THE ARRAY ELEMENT OFFSETS #}
{# NOTICE: The offset is no longer valid at this point because we just emitted the array length prefix. #}
{% set element_offset = offset + t.bit_length_set %}
{% set first_element_offset = offset + t.length_field_type.bit_length %}
{% assert (element_offset.min) == (first_element_offset.min) %}
{% if first_element_offset.is_aligned_at_byte() %}
    {{ assert('offset_bits % 8U == 0U') }}
{% endif %}

{# SPECIAL CASE: PACKED BIT ARRAY #}
{% if t.element_type is BooleanType %}
    nunavutGetBits(&{{ reference }}.bitpacked[0], &buffer[0], capacity_bytes, offset_bits, {{ reference }}.count);
    offset_bits += {{ reference }}.count;

{# SPECIAL CASE: BYTES-LIKE ARRAY #}
{% elif t.element_type is PrimitiveType and t.element_type.bit_length == 8 and t.element_type is zero_cost_primitive %}
    nunavutGetBits(&{{ reference }}.elements[0], &buffer[0], capacity_bytes, offset_bits, {{ reference }}.count * 8U);
    offset_bits += {{ reference }}.count * 8U;

{# SPECIAL CASE: ZERO-COST PRIMITIVES #}
{% elif t.element_type is PrimitiveType and t.element_type is zero_cost_primitive %}
    {% if t.element_type is FloatType %}
    static_assert(NUNAVUT_PLATFORM_IEEE754_FLOAT, "Native IEEE754 binary32 required. TODO: relax constraint");
        {% if t.element_type.bit_length > 32 %}
    static_assert(NUNAVUT_PLATFORM_IEEE754_DOUBLE, "Native IEEE754 binary64 required. TODO: relax constraint");
        {% endif %}
    {% endif %}
    nunavutGetBits(&{{ reference }}.elements[0], &buffer[0], capacity_bytes, offset_bits, {# -#}
                   {{ reference }}.count * {{ t.element_type.bit_length }}U);
    offset_bits += {{ reference }}.count * {{ t.element_type.bit_length }}U;

{# GENERAL CASE #}
{% else %}
    {% set ref_index = 'index'|to_template_unique_name %}
    for (size_t {{ ref_index }} = 0U; {{ ref_index }} < {{ reference }}.count; ++{{ ref_index }})
    {
        {{
            _deserialize_any(t.element_type, reference + ('.elements[%s]'|format(ref_index)), element_offset)
           |trim|indent
        }}
    }
{% endif %}
{% endmacro %}


{# ----------------------------------------------------------------------------------------------------------------- #}
{% macro _deserialize_composite(t, reference, offset) %}
{% set ref_err        = 'err'        |to_template_unique_name %}
{% set ref_size_bytes = 'size_bytes' |to_template_unique_name %}
{% set ref_delimiter  = 'dh'         |to_template_unique_name %}
{% set remaining_bytes -%}
    (capacity_bytes - nunavutChooseMin((offset_bits / 8U), capacity_bytes))
{%- endset %}
    {
{% if t is DelimitedType %}
        // Delimiter header: {{ t.delimiter_header_type }}
        {{ typename_unsigned_length }} {{ ref_size_bytes }} = 0U;
        {{ _deserialize_integer(t.delimiter_header_type, ref_size_bytes, offset)|trim|indent }}
        if ({{ ref_size_bytes }} > {{ remaining_bytes }})
        {
            return -NUNAVUT_ERROR_REPRESENTATION_BAD_DELIMITER_HEADER;
        }
        const {{ typename_unsigned_length }} {{ ref_delimiter }} = {{ ref_size_bytes }};  {# -#}
            // Store the original delimiter header value.
{% else %}
        {{ typename_unsigned_length }} {{ ref_size_bytes }} = ({{ typename_unsigned_length }}){{ remaining_bytes }};
{% endif %}

        {{ assert('offset_bits % 8U == 0U') }}
        const {{ typename_error_type }} {{ ref_err }} = {{ t|full_reference_name }}_deserialize_(
            &{{ reference }}, &buffer[offset_bits / 8U], &{{ ref_size_bytes }});
        if ({{ ref_err }} < 0)
        {
            return {{ ref_err }};
        }

{% if t is DelimitedType %}
        // Advance the offset by the size of the delimiter header, even if the nested deserialization routine
        // consumed fewer bytes of data. This behavior implements the implicit truncation rule for nested objects.
        offset_bits += {{ ref_delimiter }} * 8U;
{% else %}
        offset_bits += {{ ref_size_bytes }} * 8U;  // Advance by the size of the nested serialized representation.
{% endif %}
    }
{% endmacro %}
