#include <stdlib.h>
#include <string.h>

static bool is_big_endian(void)
{
    union
    {
        uint32_t i;
        char c[4];
    } bint = {0x01020304};

    return bint.c[0] == 1;
}

/* Decode an unsigned int LEB128 at buf into r, returning the nr of bytes read.
 */
inline size_t decode_uint_leb(const uint8_t* buf,
                              const uint8_t* buf_end,
                              uint64_t* r)
{
    const uint8_t* p = buf;
    uint8_t shift = 0;
    uint64_t result = 0;
    uint8_t byte;

    do
    {
        if (p >= buf_end)
        {
            return 0;
        }
        byte = *p++;
        result |= ((uint64_t)(byte & 0x7f)) << shift;
        shift += 7;
    } while ((byte & 0x80) != 0);
    *r = result;
    return p - buf;
}

/* Decode an unsigned int LEB128 at buf into r, returning the nr of bytes read.
 */
inline size_t decode_uint_leb32(const uint8_t* buf,
                                const uint8_t* buf_end,
                                uint32_t* r)
{
    const uint8_t* p = buf;
    uint8_t shift = 0;
    uint32_t result = 0;
    uint8_t byte;

    do
    {
        if (p >= buf_end)
        {
            return 0;
        }
        byte = *p++;
        result |= ((uint32_t)(byte & 0x7f)) << shift;
        shift += 7;
    } while ((byte & 0x80) != 0);
    *r = result;
    return p - buf;
}

/* Decodes a string
 */
inline uint64_t decode_string(uint64_t str_len,
                              const uint8_t* buf,
                              const uint8_t* buf_end,
                              char* char_buf)
{
    // Return zero bytes read on buffer overflow
    if (buf_end < buf + str_len)
    {
        return 0;
    }
    const uint8_t* p = buf;
    for (int i = 0; i < str_len; i++)
    {
        char_buf[i] = *p++;
    }
    // Add the null terminator
    char_buf[str_len] = '\0';
    return str_len;
}

/* Decodes a double (8 bytes)
 */
inline size_t decode_double(const uint8_t* buf,
                            const uint8_t* buf_end,
                            double* r)
{
    // Return zero bytes read on buffer overflow
    if (buf_end - buf < sizeof(double))
    {
        return 0;
    }
    if (is_big_endian())
    {
        uint8_t inverted[8] =
            {buf[7], buf[6], buf[5], buf[4], buf[3], buf[2], buf[1], buf[0]};
        memcpy(r, inverted, sizeof(double));
    }
    else
    {
        memcpy(r, buf, sizeof(double));
    }
    return sizeof(double);
}

/* Decodes a float (4 bytes)
 */
inline size_t decode_float(const uint8_t* buf, const uint8_t* buf_end, float* r)
{
    // Return zero bytes read on buffer overflow
    if (buf_end - buf < (unsigned)sizeof(float))
    {
        return 0;
    }
    if (is_big_endian())
    {
        uint8_t inverted[4] = {buf[3], buf[2], buf[1], buf[0]};
        memcpy(r, inverted, sizeof(float));
    }
    else
    {
        memcpy(r, buf, sizeof(float));
    }
    return sizeof(float);
}

/* Decodes a single byte
 */
inline size_t decode_uint_8(const uint8_t* buf,
                            const uint8_t* buf_end,
                            uint8_t* r)
{
    // Return zero bytes read on buffer overflow
    if (buf_end - buf < (unsigned)sizeof(uint8_t))
    {
        return 0;
    }
    memcpy(r, buf, sizeof(uint8_t));
    return sizeof(uint8_t);
}

/* Decodes a 16 bit unsigned integer.
 */
inline size_t decode_uint_16(const uint8_t* buf,
                             const uint8_t* buf_end,
                             uint16_t* r)
{
    // Return zero bytes read on buffer overflow
    if (buf_end - buf < (unsigned)sizeof(uint16_t))
    {
        return 0;
    }
    if (is_big_endian())
    {
        uint8_t inverted[2] = {buf[1], buf[0]};
        memcpy(r, inverted, sizeof(uint16_t));
    }
    else
    {
        memcpy(r, buf, sizeof(uint16_t));
    }
    return sizeof(uint16_t);
}

/* Decodes a 32 bit unsigned integer.
 */
inline size_t decode_uint_32(const uint8_t* buf,
                             const uint8_t* buf_end,
                             uint32_t* r)
{
    // Return zero bytes read on buffer overflow
    if (buf_end - buf < (unsigned)sizeof(uint32_t))
    {
        return 0;
    }
    if (is_big_endian())
    {
        uint8_t inverted[4] = {buf[3], buf[2], buf[1], buf[0]};
        memcpy(r, inverted, sizeof(uint32_t));
    }
    else
    {
        memcpy(r, buf, sizeof(uint32_t));
    }
    return sizeof(uint32_t);
}