#pragma once

#include <stdio.h>
#include <stdint.h>

#define DEFINE_LENGTH_PREFIX_ARRAY(LEN_TYPE, ITEM_TYPE)                        \
    typedef struct {                                                           \
        LEN_TYPE len;                                                          \
        ITEM_TYPE items[];                                                     \
    } LengthPrefix_##LEN_TYPE##_Array_##ITEM_TYPE;                             \
                                                                               \
    static inline void length_prefix_##LEN_TYPE##_array_##ITEM_TYPE##_clear(   \
        LengthPrefix_##LEN_TYPE##_Array_##ITEM_TYPE *data) {                   \
        data->len = 0;                                                         \
    }                                                                          \
                                                                               \
    static inline void length_prefix_##LEN_TYPE##_array_##ITEM_TYPE##_push(    \
        LengthPrefix_##LEN_TYPE##_Array_##ITEM_TYPE *data,                     \
        ITEM_TYPE value) {                                                     \
        data->items[data->len++] = value;                                      \
    }                                                                          \
                                                                               \
    static inline int length_prefix_##LEN_TYPE##_array_##ITEM_TYPE##_reverse(  \
        LengthPrefix_##LEN_TYPE##_Array_##ITEM_TYPE const * restrict src,      \
        LengthPrefix_##LEN_TYPE##_Array_##ITEM_TYPE *restrict dst) {           \
        for (LEN_TYPE i = 0; i < src->len; i++) {                              \
            dst->items[i] = src->items[src->len - 1 - i];                      \
        }                                                                      \
        dst->len = src->len;                                                   \
        return 0;                                                              \
    }                                                                          \
                                                                               \
    static inline void length_prefix_##LEN_TYPE##_array_##ITEM_TYPE##_dump(    \
        LengthPrefix_##LEN_TYPE##_Array_##ITEM_TYPE const * data) {            \
        printf("Length: %llu\n", (unsigned long long) data->len);              \
        for (LEN_TYPE i = 0; i < data->len; i++) {                             \
            printf("%llu ", (unsigned long long) data->items[i]);              \
        }                                                                      \
        printf("\n");                                                          \
    }

#define DEFINE_ALL_LENGTH_PREFIX_ARRAYS(LEN_TYPE)  \
    DEFINE_LENGTH_PREFIX_ARRAY(LEN_TYPE, uint8_t)  \
    DEFINE_LENGTH_PREFIX_ARRAY(LEN_TYPE, uint16_t) \
    DEFINE_LENGTH_PREFIX_ARRAY(LEN_TYPE, uint32_t) \
    DEFINE_LENGTH_PREFIX_ARRAY(LEN_TYPE, uint64_t)

DEFINE_ALL_LENGTH_PREFIX_ARRAYS(uint8_t)
DEFINE_ALL_LENGTH_PREFIX_ARRAYS(uint16_t)
DEFINE_ALL_LENGTH_PREFIX_ARRAYS(uint32_t)
DEFINE_ALL_LENGTH_PREFIX_ARRAYS(uint64_t)


#define DEFINE_LENGTH_PREFIX_ARRAY_MAP(LEN_TYPE, SRC_ITEM_TYPE, DST_ITEM_TYPE) \
    static inline int length_prefix_##LEN_TYPE##_array_map_##SRC_ITEM_TYPE##_to_##DST_ITEM_TYPE( \
        int (*f)(SRC_ITEM_TYPE const * restrict, DST_ITEM_TYPE * restrict),    \
        LengthPrefix_##LEN_TYPE##_Array_##SRC_ITEM_TYPE const * restrict src,  \
        LengthPrefix_##LEN_TYPE##_Array_##DST_ITEM_TYPE * restrict dst)        \
    {                                                                          \
        if (dst->len < src->len) return -1; /* Ensure enough space */          \
        for (LEN_TYPE i = 0; i < src->len; i++) {                              \
            if (f(&src->items[i], &dst->items[i]) != 0) return -1;             \
        }                                                                      \
        dst->len = src->len;                                                   \
        return 0;                                                              \
    }

#define DEFINE_ALL_MAPS(LEN_TYPE)                        \
    DEFINE_LENGTH_PREFIX_ARRAY_MAP(LEN_TYPE, uint8_t, uint8_t)   \
    DEFINE_LENGTH_PREFIX_ARRAY_MAP(LEN_TYPE, uint8_t, uint16_t)  \
    DEFINE_LENGTH_PREFIX_ARRAY_MAP(LEN_TYPE, uint8_t, uint32_t)  \
    DEFINE_LENGTH_PREFIX_ARRAY_MAP(LEN_TYPE, uint8_t, uint64_t)  \
    DEFINE_LENGTH_PREFIX_ARRAY_MAP(LEN_TYPE, uint16_t, uint8_t)  \
    DEFINE_LENGTH_PREFIX_ARRAY_MAP(LEN_TYPE, uint16_t, uint16_t) \
    DEFINE_LENGTH_PREFIX_ARRAY_MAP(LEN_TYPE, uint16_t, uint32_t) \
    DEFINE_LENGTH_PREFIX_ARRAY_MAP(LEN_TYPE, uint16_t, uint64_t) \
    DEFINE_LENGTH_PREFIX_ARRAY_MAP(LEN_TYPE, uint32_t, uint8_t)  \
    DEFINE_LENGTH_PREFIX_ARRAY_MAP(LEN_TYPE, uint32_t, uint16_t) \
    DEFINE_LENGTH_PREFIX_ARRAY_MAP(LEN_TYPE, uint32_t, uint32_t) \
    DEFINE_LENGTH_PREFIX_ARRAY_MAP(LEN_TYPE, uint32_t, uint64_t) \
    DEFINE_LENGTH_PREFIX_ARRAY_MAP(LEN_TYPE, uint64_t, uint8_t)  \
    DEFINE_LENGTH_PREFIX_ARRAY_MAP(LEN_TYPE, uint64_t, uint16_t) \
    DEFINE_LENGTH_PREFIX_ARRAY_MAP(LEN_TYPE, uint64_t, uint32_t) \
    DEFINE_LENGTH_PREFIX_ARRAY_MAP(LEN_TYPE, uint64_t, uint64_t)

DEFINE_ALL_MAPS(uint8_t)
DEFINE_ALL_MAPS(uint16_t)
DEFINE_ALL_MAPS(uint32_t)
DEFINE_ALL_MAPS(uint64_t)