// SPDX-License-Identifier: LGPL-3.0-or-later
#pragma once

#include <errno.h>
#include <limits.h>

#pragma pack(push, 1)
typedef struct
{
    uint8_t type;
    uint8_t subtype;
    uint16_t length;
} efidp_header;

enum EFIDP_TYPE
{
    {% for category in device_paths.values() %}
    EFIDP_TYPE_{{ category.slug.upper() }} = {{ "0x%02x" | format(category.type) }},
    {% endfor %}
};

{% for category in device_paths.values() %}
enum EFIDP_{{ category.slug.upper() }}
{
    {% for node in category.nodes %}
    EFIDP_{{ category.slug.upper() }}_{{ node.slug.upper() }} = {{ "0x%02x" | format(node.subtype) }},
    {% endfor %}
};

{% endfor %}
{% for category in device_paths.values() %}
/*
   {{ category.name }}
   {{ category.description }}
*/
{% for node in category.nodes %}
{% for field in node.fields %}{% if field.type == "enum" %}
enum EFIDP_{{ category.slug.upper() }}_{{ node.slug.upper() }}_{{ field.slug.upper() }}
{
    {% for enum in field.enum %}
    EFIDP_{{ category.slug.upper() }}_{{ node.slug.upper() }}_{{ field.slug.upper() }}_{{ enum.slug.upper() }} = {{ "0x%0x" | format(enum.value) }},
    {% endfor %}
};

{% endif %}{% endfor %}
/*
   {{ node.name }}
   {{ node.description }}
*/
typedef struct
{
    efidp_header header;
    {% for field in node.fields %}
    {%- if field.type in ("bool", "guid", "ip4", "ip6", "mac", "raw_data", "string", "uri") %}
    uint8_t
    {%- elif field.type == "wstring" %}
    uint16_t
    {%- elif field.type in ("int", "hex", "enum") %}
    uint{{ field.size * 8 }}_t
    {%- else %}
    {{ field.type }}
    {%- endif %} {{ field.slug }}
    {%- if field.type in ("guid", "ip4", "ip6", "mac") -%}
    [{{ field.size }}]
    {%- elif field.size == "n" -%}
    [ANYSIZE_ARRAY]
    {%- endif -%}
    ;
    {% endfor %}
} efidp_{{ category.slug }}_{{ node.slug }};

{% endfor %}{% endfor %}
/* utility functions */
typedef union
{
    efidp_header header;
{% for category in device_paths.values() %}{% for node in category.nodes %}
    efidp_{{ category.slug }}_{{ node.slug }} {{ category.slug }}_{{ node.slug }};
{% endfor %}{% endfor %}
} efidp_data;
typedef efidp_data *efidp;
typedef const efidp_data *const_efidp;

#pragma pack(pop)

static inline int16_t
    ATTR_ARTIFICIAL ATTR_NONNULL(1) ATTR_UNUSED
    efidp_type(const_efidp dp)
{
    if(ATTR_NONNULL_IS_NULL(dp))
    {
        errno = EINVAL;
        return -1;
    }

    return dp->header.type;
}

static inline int16_t
    ATTR_ARTIFICIAL ATTR_NONNULL(1) ATTR_UNUSED
    efidp_subtype(const_efidp dp)
{
    if(ATTR_NONNULL_IS_NULL(dp))
    {
        errno = EINVAL;
        return -1;
    }

    return dp->header.subtype;
}

static inline ssize_t
    ATTR_ARTIFICIAL ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT
    efidp_node_size(const_efidp dn)
{
    if(ATTR_NONNULL_IS_NULL(dn) || dn->header.length < 4)
    {
        errno = EINVAL;
        return -1;
    }

    return dn->header.length;
}

static inline int
    ATTR_ARTIFICIAL ATTR_NONNULL(1, 2) ATTR_UNUSED ATTR_WARN_UNUSED_RESULT
    efidp_next_node(const_efidp in, const_efidp *out)
{
    if(efidp_type(in) == EFIDP_TYPE_END && efidp_subtype(in) == EFIDP_END_ENTIRE)
        return 0;

    ssize_t sz = efidp_node_size(in);
    if(sz < 0)
        return -1;

    *out = STATIC_CAST(const_efidp)(advance_bytes(in, STATIC_CAST(size_t)(sz)));
    if(*out < in)
    {
        errno = EINVAL;
        return -1;
    }

    return 1;
}

static inline int
    ATTR_ARTIFICIAL ATTR_NONNULL(1, 2) ATTR_UNUSED ATTR_WARN_UNUSED_RESULT
    efidp_next_instance(const_efidp in, const_efidp *out)
{
    if(efidp_type(in) != EFIDP_TYPE_END || efidp_subtype(in) != EFIDP_END_INSTANCE)
    {
        errno = EINVAL;
        return -1;
    }

    ssize_t sz = efidp_node_size(in);
    if(sz < 0)
        return -1;

    *out = STATIC_CAST(const_efidp)(advance_bytes(in, STATIC_CAST(size_t)(sz)));
    if(*out < in)
    {
        errno = EINVAL;
        return -1;
    }

    return 1;
}

static inline int
    ATTR_ARTIFICIAL ATTR_NONNULL(1) ATTR_UNUSED ATTR_WARN_UNUSED_RESULT
    efidp_is_multiinstance(const_efidp dn)
{
    while(1)
    {
        const_efidp next = nullptr;
        int rc = efidp_next_node(dn, &next);
        if(rc < 0)
        {
            errno = EINVAL;
            return -1;
        }
        else if(rc == 0)
            return 0;

        dn = next;
        if(efidp_type(dn) == EFIDP_TYPE_END && efidp_subtype(dn) == EFIDP_END_INSTANCE)
            return 1;

        if(efidp_type(dn) == EFIDP_TYPE_END && efidp_subtype(dn) == EFIDP_END_ENTIRE)
            return 0;
    }

    return 0;
}

static inline int
    ATTR_ARTIFICIAL ATTR_NONNULL(1, 2) ATTR_UNUSED ATTR_WARN_UNUSED_RESULT
    efidp_get_next_end(const_efidp in, const_efidp *out)
{
    while(1)
    {
        if(efidp_type(in) == EFIDP_TYPE_END)
        {
            *out = in;
            return 0;
        }

        ssize_t sz = efidp_node_size(in);
        if(sz < 0)
            break;

        const_efidp next = STATIC_CAST(const_efidp)(advance_bytes(in, STATIC_CAST(size_t)(sz)));
        if(next < in)
        {
            errno = EINVAL;
            return -1;
        }

        in = next;
    }

    return -1;
}

static inline ssize_t
    ATTR_ARTIFICIAL ATTR_NONNULL(1) ATTR_UNUSED ATTR_WARN_UNUSED_RESULT
    efidp_size(const_efidp dp)
{
    ssize_t size = 0;
    if(ATTR_NONNULL_IS_NULL(dp))
    {
        errno = EINVAL;
        return -1;
    }

    if(efidp_type(dp) == EFIDP_TYPE_END && efidp_subtype(dp) == EFIDP_END_ENTIRE)
        return efidp_node_size(dp);

    while(1)
    {
        ssize_t sz = efidp_node_size(dp);
        if(sz < 0)
            return sz;

        size += sz;
        const_efidp next = nullptr;
        int rc = efidp_next_instance(dp, &next);
        if(rc < 0)
        {
            rc = efidp_next_node(dp, &next);
            if(rc < 0)
                return rc;

            if(rc == 0)
                break;
        }

        dp = next;
    }

    return size;
}

static inline ssize_t
    ATTR_ARTIFICIAL ATTR_NONNULL(1) ATTR_UNUSED ATTR_WARN_UNUSED_RESULT
    efidp_instance_size(const_efidp dpi)
{
    ssize_t size = 0;
    while(1)
    {
        ssize_t sz = efidp_node_size(dpi);
        if(sz < 0)
            return sz;

        size += sz;
        if(efidp_type(dpi) == EFIDP_TYPE_END)
            break;

        const_efidp next = nullptr;
        int rc = efidp_next_node(dpi, &next);
        if(rc < 0)
            return rc;

        dpi = next;
    }

    return size;
}

