#ifndef _ARR_DESC_H_
#define _ARR_DESC_H_

/*--------------------------------------------------------------------------------*/
/* Includes */
/*--------------------------------------------------------------------------------*/
#include <stdint.h>
#include <string.h>             /* memset() */
#include "../util/util.h"       /* CONCAT() */

/*--------------------------------------------------------------------------------*/
/* Type Definitions */
/*--------------------------------------------------------------------------------*/

/**
 *  Array-descriptor struct.
 */
typedef struct ARR_DESC_struct
{
    void *  data_ptr;                /* Pointer to the array contents. */
    int32_t element_count;           /* Number of current elements. */
    int32_t element_size;            /* Size of current elements in bytes. */
    int32_t underlying_size;         /* Size of underlying array in bytes. */
} ARR_DESC_t;

/*--------------------------------------------------------------------------------*/
/* Macros and Defines */
/*--------------------------------------------------------------------------------*/

/**
 *  Prefix of the array variable's name when creating an array and an array
 *  descriptor at the same time.
 */
#define ARR_DESC_ARR_PREFIX ARR_DESC_ARR_

/**
 *  Evaluate to the array variable's name when creating an array and an array
 *  descriptor at the same time.
 */
#define ARR_DESC_ARR_NAME(name)                 \
    CONCAT(ARR_DESC_ARR_PREFIX, name)

/**
 *  Define an #ARR_DESC_t by itself.
 *
 *  @note The user must supply an array to store the data used by the
 *  #ARR_DESC_t.
 */
#define ARR_DESC_INTERNAL_DEFINE(name, data_ptr,                \
                                 element_count, element_size)   \
    ARR_DESC_t name = {                                         \
        data_ptr,                                               \
        element_count,                                          \
        element_size,                                           \
        element_count * element_size                            \
    }                                                           \

/**
 *  Define both an array and an #ARR_DESC_t that describes it.
 *
 *  @note Use the #CURLY() macro for the content field; it provides the curly
 *  braces necessary for an array initialization.
 */
#define ARR_DESC_DEFINE(type, name, element_count, content)             \
    type ARR_DESC_ARR_NAME(name)[element_count] = content;              \
    ARR_DESC_INTERNAL_DEFINE(name,                                      \
                             &ARR_DESC_ARR_NAME(name),                  \
                             element_count,                             \
                             sizeof(type)) /* Note the lacking semicolon */

/**
 *  Create a #ARR_DESC_t which refers to a subset of the data in another.
 *
 *  The new #ARR_DESC_t shares the same underlying array as the aliased
 *  #ARR_DESC_t, but only describes a subset of the originals values.
 */
#define ARR_DESC_DEFINE_SUBSET(name, original, element_cnt)         \
    ARR_DESC_INTERNAL_DEFINE(name,                                  \
                             &ARR_DESC_ARR_NAME(original),          \
                             element_cnt,                           \
                             sizeof(ARR_DESC_ARR_NAME(original)[0]) \
        ) /* Note the lacking semicolon */

/**
 *  Creat an #ARR_DESC_t which points to the data in an existing array.
 *
 *  @param start_idx Offset in array_ptr of first element.
 *  @param element_cnt Number of elements to include in the #ARR_DESC_t.
 *
 *  @example
 *
 *  float my_floats[4] = {0.0f, 1.0f, 2.0f, 3.0f};
 *
 *  ARR_DESC_DEFINE_USING_ARR(my_arr_desc, my_floats, 1, 3);
 *
 *  printf("Element 0: %f\n", ARR_DESC_ELT(float, 0, &my_arr_desc));
 *  printf("Element 1: %f\n", ARR_DESC_ELT(float, 1, &my_arr_desc));
 *
 *  Outputs:
 *
 *  Element 0: 1.000000
 *  Element 1: 2.000000
 *
 *  @warning There are no checks in place to catch invalid start indices; This
 *  is left to the user.
 */
#define ARR_DESC_DEFINE_USING_ARR(type, name, array_ptr, start_idx, element_cnt) \
    ARR_DESC_INTERNAL_DEFINE(                                           \
        name,                                                           \
        (type *) (array_ptr + start_idx),                               \
        element_cnt,                                                    \
        sizeof(type)                                                    \
        ) /* Note the lacking semicolon*/

/**
 *  Declare an #ARR_DESC_t object.
 */
#define ARR_DESC_DECLARE(name)                              \
    extern ARR_DESC_t name /* Note the lacking semicolon */

/**
 *  Evaluate to the number of bytes stored in the #ARR_DESC_t.
 */
#define ARR_DESC_BYTES(arr_desc_ptr)                                \
    ((arr_desc_ptr)->element_count * (arr_desc_ptr)->element_size)

/**
 *  Set the contents of #ARR_DESC_t to value.
 */
#define ARR_DESC_MEMSET(arr_desc_ptr, value, bytes)     \
    do                                                  \
    {                                                   \
        memset((arr_desc_ptr)->data_ptr,                \
               value,                                   \
               BOUND(0,                                 \
                     (arr_desc_ptr)->underlying_size,   \
                     bytes)                             \
            );                                          \
    } while (0)

/**
 *  Perform a memcpy of 'bytes' bytes from the source #ARR_DESC_t to the
 *  destination #ARR_DESC_t.
 */
#define ARR_DESC_MEMCPY(arr_desc_dest_ptr, arr_desc_src_ptr, bytes) \
    do                                                              \
    {                                                               \
        memcpy((arr_desc_dest_ptr)->data_ptr,                       \
               (arr_desc_src_ptr)->data_ptr,                        \
               BOUND(0,                                             \
                     (arr_desc_dest_ptr)->underlying_size,          \
                     bytes));                                       \
    } while (0)

/**
 *  Evaluate to true if the source #ARR_DESC_t contents will fit into the
 *  destination #ARR_DESC_t and false otherwise.
 */
#define ARR_DESC_COPYABLE(arr_desc_dest_ptr, arr_desc_src_ptr)  \
      (ARR_DESC_BYTES(arr_desc_src_ptr) <=                      \
       (arr_desc_dest_ptr)->underlying_size)

/**
 *  Copy all the data from the source #ARR_DESC_t to the destination
 *  #ARR_DESC_t.
 *
 *  @note If the destination #ARR_DESC_t is too small to fit the source data the
 *  copy is aborted and nothing happens.
 */
#define ARR_DESC_COPY(arr_desc_dest_ptr, arr_desc_src_ptr)      \
    do                                                          \
    {                                                           \
        if (ARR_DESC_COPYABLE(arr_desc_dest_ptr,                 \
                             arr_desc_src_ptr))                 \
        {                                                       \
            ARR_DESC_MEMCPY(arr_desc_dest_ptr,                  \
                            arr_desc_src_ptr,                   \
                            ARR_DESC_BYTES(arr_desc_src_ptr));  \
            /* Update the properties*/                          \
            (arr_desc_dest_ptr)->element_count =                \
                (arr_desc_src_ptr)->element_count;              \
            (arr_desc_dest_ptr)->element_size =                 \
                (arr_desc_src_ptr)->element_size;               \
        }                                                       \
    } while (0)

/**
 *  Compare the data in two #ARR_DESC_t structs for the specified number of
 *  bytes.
 */
#define ARR_DESC_MEMCMP(arr_desc_ptr_a, arr_desc_ptr_b, bytes)  \
        memcmp((arr_desc_ptr_a)->data_ptr,                      \
            (arr_desc_ptr_b)->data_ptr,                         \
               bytes) /* Note the lacking semicolon */          \

/**
 *  Zero out the contents of the #ARR_DESC_t.
 */
#define ARR_DESC_ZERO(arr_desc_ptr)             \
        ARR_DESC_MEMSET(arr_desc_ptr,           \
                        0,                      \
                        (arr_desc_ptr)->underlying_size)

/**
 *  Evaluate to the data address in #ARR_DESC_t at offset.
 */
#define ARR_DESC_DATA_ADDR(type, arr_desc_ptr, offset)  \
        ((void*)(((type *)                              \
                  ((arr_desc_ptr)->data_ptr))           \
                 + offset))

/**
 *  Evaluate to the element in #ARR_DESC_t with type at idx.
 */
#define ARR_DESC_ELT(type, idx, arr_desc_ptr)           \
        (*((type *) ARR_DESC_DATA_ADDR(type,            \
                                       arr_desc_ptr,    \
                                       idx)))

#endif /* _ARR_DESC_H_ */
