/*
 * Copyright (c) 2020 Legrand North America, LLC.
 *
 * SPDX-License-Identifier: MIT
 */

/* @file
 * @brief test BACnet integer encode/decode APIs
 */

#include <limits.h>
#include <zephyr/ztest.h>
#include <bacnet/basic/sys/ringbuf.h>

/**
 * @addtogroup bacnet_tests
 * @{
 */

/**
 * @brief Test
 */
/**
 * Unit Test for the ring buffer
 *
 * @param  test_buffer - pointer to RING_BUFFER structure
 * @param  data_element - one data element
 * @param  element_size - size of one data element
 * @param  element_count - number of data elements in the store
 */
static void testRingAroundBuffer(
    RING_BUFFER *test_buffer,
    uint8_t *data_element,
    unsigned element_size,
    unsigned element_count)
{
    volatile uint8_t *test_data;
    unsigned index;
    unsigned data_index;
    unsigned count;
    uint8_t value;
    bool status;

    zassert_true(Ringbuf_Empty(test_buffer), NULL);
    /* test the ring around the buffer */
    for (index = 0; index < element_count; index++) {
        for (count = 1; count < 4; count++) {
            value = (index * count) % 255;
            for (data_index = 0; data_index < element_size; data_index++) {
                data_element[data_index] = value;
            }
            status = Ringbuf_Put(test_buffer, data_element);
            zassert_true(status, NULL);
            zassert_equal(Ringbuf_Count(test_buffer), count, NULL);
        }
        for (count = 1; count < 4; count++) {
            value = (index * count) % 255;
            test_data = Ringbuf_Peek(test_buffer);
            zassert_not_null(test_data, NULL);
            if (test_data) {
                for (data_index = 0; data_index < element_size; data_index++) {
                    zassert_equal(test_data[data_index], value, NULL);
                }
            }
            status = Ringbuf_Pop(test_buffer, NULL);
            zassert_true(status, NULL);
        }
    }
    zassert_true(Ringbuf_Empty(test_buffer), NULL);
}

/**
 * Unit Test for the ring buffer
 *
 * @param  data_store - buffer to store elements
 * @param  data_element - one data element
 * @param  element_size - size of one data element
 * @param  element_count - number of data elements in the store
 */
static void testRingBuf(
    uint8_t *data_store,
    uint8_t *data_element,
    unsigned element_size,
    unsigned element_count)
{
    RING_BUFFER test_buffer;
    volatile uint8_t *test_data;
    unsigned index;
    unsigned data_index;
    bool status;

    status =
        Ringbuf_Init(&test_buffer, data_store, element_size, element_count);
    if (!status) {
        return;
    }
    zassert_true(Ringbuf_Empty(&test_buffer), NULL);
    zassert_equal(Ringbuf_Depth(&test_buffer), 0, NULL);

    for (data_index = 0; data_index < element_size; data_index++) {
        data_element[data_index] = data_index;
    }
    status = Ringbuf_Put(&test_buffer, data_element);
    zassert_true(status, NULL);
    zassert_false(Ringbuf_Empty(&test_buffer), NULL);
    zassert_equal(Ringbuf_Depth(&test_buffer), 1, NULL);

    test_data = Ringbuf_Peek(&test_buffer);
    for (data_index = 0; data_index < element_size; data_index++) {
        zassert_equal(test_data[data_index], data_element[data_index], NULL);
    }
    zassert_false(Ringbuf_Empty(&test_buffer), NULL);
    (void)Ringbuf_Pop(&test_buffer, NULL);
    zassert_true(Ringbuf_Empty(&test_buffer), NULL);
    zassert_equal(Ringbuf_Depth(&test_buffer), 1, NULL);

    /* fill to max */
    for (index = 0; index < element_count; index++) {
        for (data_index = 0; data_index < element_size; data_index++) {
            data_element[data_index] = index;
        }
        status = Ringbuf_Put(&test_buffer, data_element);
        zassert_true(status, NULL);
        zassert_false(Ringbuf_Empty(&test_buffer), NULL);
        zassert_equal(Ringbuf_Depth(&test_buffer), (index + 1), NULL);
    }
    zassert_equal(Ringbuf_Depth(&test_buffer), element_count, NULL);
    /* verify actions on full buffer */
    for (index = 0; index < element_count; index++) {
        for (data_index = 0; data_index < element_size; data_index++) {
            data_element[data_index] = index;
        }
        status = Ringbuf_Put(&test_buffer, data_element);
        zassert_false(status, NULL);
        zassert_false(Ringbuf_Empty(&test_buffer), NULL);
        zassert_equal(Ringbuf_Depth(&test_buffer), element_count, NULL);
    }

    /* check buffer full */
    for (index = 0; index < element_count; index++) {
        test_data = Ringbuf_Peek(&test_buffer);
        zassert_not_null(test_data, NULL);
        if (test_data) {
            for (data_index = 0; data_index < element_size; data_index++) {
                zassert_equal(test_data[data_index], index, NULL);
            }
        }
        (void)Ringbuf_Pop(&test_buffer, NULL);
    }
    zassert_true(Ringbuf_Empty(&test_buffer), NULL);
    zassert_equal(Ringbuf_Depth(&test_buffer), element_count, NULL);
    Ringbuf_Depth_Reset(&test_buffer);
    zassert_equal(Ringbuf_Depth(&test_buffer), 0, NULL);

    testRingAroundBuffer(
        &test_buffer, data_element, element_size, element_count);

    /* adjust the internal index of Ringbuf to test unsigned wrapping */
    test_buffer.head = UINT_MAX - 1;
    test_buffer.tail = UINT_MAX - 1;

    testRingAroundBuffer(
        &test_buffer, data_element, element_size, element_count);
}

/**
 * Unit Test for the ring buffer with 16 data elements
 */
static void testRingBufSizeSmall(void)
{
    uint8_t data_element[5];
    uint8_t data_store[sizeof(data_element) * NEXT_POWER_OF_2(16)];

    testRingBuf(data_store, data_element, sizeof(data_element),
        sizeof(data_store) / sizeof(data_element));
}

/**
 * Unit Test for the ring buffer with 32 data elements
 */
static void testRingBufSizeLarge(void)
{
    uint8_t data_element[16];
    uint8_t data_store[sizeof(data_element) * NEXT_POWER_OF_2(99)];

    testRingBuf(data_store, data_element, sizeof(data_element),
        sizeof(data_store) / sizeof(data_element));
}

/**
 * Unit Test for the ring buffer with 32 data elements
 */
static void testRingBufSizeInvalid(void)
{
    RING_BUFFER test_buffer;
    uint8_t data_element[16];
    uint8_t data_store[sizeof(data_element) * 99];

    zassert_false(Ringbuf_Init(&test_buffer,
		     data_store, sizeof(data_element),
                     sizeof(data_store) / sizeof(data_element)),
		  NULL);
}

static void testRingBufPowerOfTwo(void)
{
    zassert_equal(NEXT_POWER_OF_2(3), 4, NULL);
    zassert_equal(NEXT_POWER_OF_2(100), 128, NULL);
    zassert_equal(NEXT_POWER_OF_2(127), 128, NULL);
    zassert_equal(NEXT_POWER_OF_2(128), 128, NULL);
    zassert_equal(NEXT_POWER_OF_2(129), 256, NULL);
    zassert_equal(NEXT_POWER_OF_2(300), 512, NULL);
    zassert_equal(NEXT_POWER_OF_2(500), 512, NULL);
}

/**
 * Unit Test for the ring buffer peek/pop next element
 *
 * @param  data_store - buffer to store elements
 * @param  data_element - one data element
 * @param  element_size - size of one data element
 * @param  element_count - number of data elements in the store
 */
static bool testRingBufNextElement(
    uint8_t *data_store,
    uint8_t *data_element,
    unsigned element_size,
    unsigned element_count)
{
    RING_BUFFER test_buffer;
    volatile uint8_t *test_data;
    unsigned index;
    unsigned data_index;
    bool status;
    status =
        Ringbuf_Init(&test_buffer, data_store, element_size, element_count);
    if (!status) {
        return false;
    }
    zassert_true(Ringbuf_Empty(&test_buffer), NULL);
    zassert_equal(Ringbuf_Depth(&test_buffer), 0, NULL);

    for (data_index = 0; data_index < element_size; data_index++) {
        data_element[data_index] = data_index;
    }
    status = Ringbuf_Put(&test_buffer, data_element);
    zassert_true(status, NULL);
    zassert_false(Ringbuf_Empty(&test_buffer), NULL);
    zassert_equal(Ringbuf_Depth(&test_buffer), 1, NULL);

    test_data = Ringbuf_Peek(&test_buffer);
    for (data_index = 0; data_index < element_size; data_index++) {
        zassert_equal(test_data[data_index], data_element[data_index], NULL);
    }
    zassert_false(Ringbuf_Empty(&test_buffer), NULL);
    (void)Ringbuf_Pop(&test_buffer, NULL);
    zassert_true(Ringbuf_Empty(&test_buffer), NULL);
    zassert_equal(Ringbuf_Depth(&test_buffer), 1, NULL);

    /* fill to max */
    for (index = 0; index < element_count; index++) {
        for (data_index = 0; data_index < element_size; data_index++) {
            data_element[data_index] = index;
        }
        status = Ringbuf_Put(&test_buffer, data_element);
        zassert_true(status, NULL);
        zassert_false(Ringbuf_Empty(&test_buffer), NULL);
        zassert_equal(Ringbuf_Depth(&test_buffer), (index + 1), NULL);
    }
    zassert_equal(Ringbuf_Depth(&test_buffer), element_count, NULL);
    zassert_equal(Ringbuf_Count(&test_buffer), element_count, NULL);

    /* Walk through ring buffer */
    test_data = Ringbuf_Peek(&test_buffer);
    zassert_not_null(test_data, NULL);
    for (index = 1; index < element_count; index++) {
        test_data = Ringbuf_Peek_Next(&test_buffer, (uint8_t *)test_data);
        zassert_not_null(test_data, NULL);
        if (test_data) {
            for (data_index = 0; data_index < element_size; data_index++) {
                zassert_equal(test_data[data_index], index, NULL);
            }
        }
    }
    zassert_equal(Ringbuf_Count(&test_buffer), element_count, NULL);
    /* Try to walk off end of buffer - should return NULL */
    test_data = Ringbuf_Peek_Next(&test_buffer, (uint8_t *)test_data);
    zassert_is_null(test_data, NULL);

    /* Walk through ring buffer and pop alternate elements */
    test_data = Ringbuf_Peek(&test_buffer);
    zassert_not_null(test_data, NULL);
    for (index = 1; index < element_count / 2; index++) {
        test_data = Ringbuf_Peek_Next(&test_buffer, (uint8_t *)test_data);
        zassert_not_null(test_data, NULL);
        (void)Ringbuf_Pop_Element(&test_buffer, (uint8_t *)test_data, NULL);
        test_data = Ringbuf_Peek_Next(&test_buffer, (uint8_t *)test_data);
    }
    zassert_equal(Ringbuf_Count(&test_buffer), element_count / 2 + 1, NULL);

    /* Walk through ring buffer and check data */
    test_data = Ringbuf_Peek(&test_buffer);
    zassert_not_null(test_data, NULL);
    for (index = 0; index < element_count / 2; index++) {
        if (test_data) {
            for (data_index = 0; data_index < element_size; data_index++) {
                zassert_equal(test_data[data_index], index * 2, NULL);
            }
        }
        test_data = Ringbuf_Peek_Next(&test_buffer, (uint8_t *)test_data);
        zassert_not_null(test_data, NULL);
    }
    zassert_equal(Ringbuf_Count(&test_buffer), element_count / 2 + 1, NULL);

    return true;
}

/**
 * Unit Test for the ring buffer with 16 data elements
 */
static void testRingBufNextElementSizeSmall(void)
{
    bool status;
    uint8_t data_element[5];
    uint8_t data_store[sizeof(data_element) * NEXT_POWER_OF_2(16)];

    status = testRingBufNextElement(data_store, data_element,
        sizeof(data_element), sizeof(data_store) / sizeof(data_element));
    zassert_true(status, NULL);
}
/**
 * @}
 */


void test_main(void)
{
    ztest_test_suite(ringbuf_tests,
     ztest_unit_test(testRingBufPowerOfTwo),
     ztest_unit_test(testRingBufSizeSmall),
     ztest_unit_test(testRingBufSizeLarge),
     ztest_unit_test(testRingBufSizeInvalid),
     ztest_unit_test(testRingBufNextElementSizeSmall)
     );

    ztest_run_test_suite(ringbuf_tests);
}
