#include <stddef.h>
#include <stdint.h>

#include "FreeRTOS.h"
#include "task.h"

#if defined(HOST_PRINTF)
#include <stdio.h>
#endif

uint8_t ucHeap[configTOTAL_HEAP_SIZE] __attribute__((aligned(portBYTE_ALIGNMENT)));

HeapStats_t g_initial_stats;
HeapStats_t g_after_a_stats;
HeapStats_t g_after_b_stats;
HeapStats_t g_after_allocations_stats;
HeapStats_t g_after_free_a_stats;
HeapStats_t g_fragmented_stats;
HeapStats_t g_final_stats;
volatile size_t g_initial_free;
volatile size_t g_after_a_free;
volatile size_t g_after_b_free;
volatile size_t g_after_allocations;
volatile size_t g_after_free_a;
volatile size_t g_fragmented_free;
volatile size_t g_final_free;
volatile size_t g_minimum_ever_free;
volatile size_t g_a_consumed;
volatile size_t g_b_consumed;
volatile size_t g_c_consumed;
void * volatile g_probe;
void * volatile g_a;
void * volatile g_b;
void * volatile g_c;
void * volatile g_too_large;
volatile int g_a_aligned;
volatile int g_b_aligned;
volatile int g_c_aligned;
volatile int g_allocations_aligned;
volatile int g_oom_is_null;
volatile int g_malloc_failed_hooks;
volatile int g_assert_failures;
volatile int g_rv32_layout_expected;
volatile int g_task03_pass;

void vApplicationMallocFailedHook(void)
{
    g_malloc_failed_hooks++;
}

void heap4_lab_assert(const char *file, int line)
{
    (void)file;
    (void)line;
    g_assert_failures++;
}

#if defined(HEAP4_HOST_ADAPTER)
void vTaskSuspendAll(void)
{
}

BaseType_t xTaskResumeAll(void)
{
    return 0;
}
#endif

__attribute__((noinline)) void heap4_reset_checkpoint(void)
{
#if defined(__GNUC__)
    __asm__ volatile("" ::: "memory");
#endif
}

__attribute__((noinline)) void heap4_alloc_a_checkpoint(void)
{
#if defined(__GNUC__)
    __asm__ volatile("" ::: "memory");
#endif
}

__attribute__((noinline)) void heap4_alloc_b_checkpoint(void)
{
#if defined(__GNUC__)
    __asm__ volatile("" ::: "memory");
#endif
}

__attribute__((noinline)) void heap4_alloc_c_checkpoint(void)
{
#if defined(__GNUC__)
    __asm__ volatile("" ::: "memory");
#endif
}

__attribute__((noinline)) void heap4_free_a_checkpoint(void)
{
#if defined(__GNUC__)
    __asm__ volatile("" ::: "memory");
#endif
}

__attribute__((noinline)) void heap4_fragmented_checkpoint(void)
{
#if defined(__GNUC__)
    __asm__ volatile("" ::: "memory");
#endif
}

__attribute__((noinline)) void heap4_coalesced_checkpoint(void)
{
#if defined(__GNUC__)
    __asm__ volatile("" ::: "memory");
#endif
}

__attribute__((noinline)) void task03_debug_checkpoint(void)
{
#if defined(__GNUC__)
    __asm__ volatile("" ::: "memory");
#endif
}

int main(void)
{
    vPortHeapResetState();
    g_probe = pvPortMalloc(1U);
    if (g_probe != NULL)
    {
        vPortFree(g_probe);
    }
    g_initial_free = xPortGetFreeHeapSize();
    vPortGetHeapStats(&g_initial_stats);
    heap4_reset_checkpoint();

    g_a = pvPortMalloc(24U);
    g_after_a_free = xPortGetFreeHeapSize();
    g_a_consumed = g_initial_free - g_after_a_free;
    g_a_aligned =
        g_a != NULL && ((uintptr_t)g_a % portBYTE_ALIGNMENT) == 0U;
    vPortGetHeapStats(&g_after_a_stats);
    heap4_alloc_a_checkpoint();

    g_b = pvPortMalloc(40U);
    g_after_b_free = xPortGetFreeHeapSize();
    g_b_consumed = g_after_a_free - g_after_b_free;
    g_b_aligned =
        g_b != NULL && ((uintptr_t)g_b % portBYTE_ALIGNMENT) == 0U;
    vPortGetHeapStats(&g_after_b_stats);
    heap4_alloc_b_checkpoint();

    g_c = pvPortMalloc(16U);
    g_after_allocations = xPortGetFreeHeapSize();
    g_c_consumed = g_after_b_free - g_after_allocations;
    g_c_aligned =
        g_c != NULL && ((uintptr_t)g_c % portBYTE_ALIGNMENT) == 0U;
    g_allocations_aligned =
        g_a_aligned && g_b_aligned && g_c_aligned;
    vPortGetHeapStats(&g_after_allocations_stats);
    heap4_alloc_c_checkpoint();

    vPortFree(g_a);
    g_after_free_a = xPortGetFreeHeapSize();
    vPortGetHeapStats(&g_after_free_a_stats);
    heap4_free_a_checkpoint();

    vPortFree(g_c);
    g_fragmented_free = xPortGetFreeHeapSize();
    vPortGetHeapStats(&g_fragmented_stats);
    heap4_fragmented_checkpoint();

    vPortFree(g_b);
    g_final_free = xPortGetFreeHeapSize();
    g_minimum_ever_free = xPortGetMinimumEverFreeHeapSize();
    vPortGetHeapStats(&g_final_stats);
    heap4_coalesced_checkpoint();

    g_too_large = pvPortMalloc(configTOTAL_HEAP_SIZE * 2U);
    g_oom_is_null = g_too_large == NULL;
#if defined(__riscv) && (__riscv_xlen == 32)
    g_rv32_layout_expected =
        portBYTE_ALIGNMENT == 16U &&
        g_initial_free == 4080U &&
        g_after_a_free == 4032U && g_a_consumed == 48U &&
        g_after_b_free == 3968U && g_b_consumed == 64U &&
        g_after_allocations == 3936U && g_c_consumed == 32U &&
        g_after_free_a == 3984U &&
        g_fragmented_free == 4016U &&
        g_final_free == 4080U;
#else
    g_rv32_layout_expected = 1;
#endif
    g_task03_pass =
        g_probe != NULL && g_allocations_aligned &&
        g_rv32_layout_expected &&
        g_initial_stats.xNumberOfFreeBlocks == 1U &&
        g_after_a_free < g_initial_free &&
        g_after_b_free < g_after_a_free &&
        g_initial_free > g_after_allocations &&
        g_after_free_a > g_after_allocations &&
        g_fragmented_free > g_after_free_a &&
        g_final_free == g_initial_free &&
        g_minimum_ever_free <= g_after_allocations &&
        g_after_free_a_stats.xNumberOfFreeBlocks == 2U &&
        g_fragmented_stats.xNumberOfFreeBlocks == 2U &&
        g_fragmented_stats.xAvailableHeapSpaceInBytes >
            g_fragmented_stats.xSizeOfLargestFreeBlockInBytes &&
        g_final_stats.xNumberOfFreeBlocks == 1U &&
        g_final_stats.xSizeOfLargestFreeBlockInBytes == g_initial_free &&
        g_oom_is_null && g_malloc_failed_hooks == 1 &&
        g_assert_failures == 0;

    task03_debug_checkpoint();

#if defined(HOST_PRINTF)
    printf("initial=%zu after_a=%zu after_b=%zu after_c=%zu "
           "free_a=%zu fragmented=%zu final=%zu min=%zu "
           "consumed=%zu/%zu/%zu aligned=%d oom=%d hooks=%d "
           "asserts=%d pass=%d\n",
           g_initial_free, g_after_a_free, g_after_b_free,
           g_after_allocations, g_after_free_a, g_fragmented_free,
           g_final_free, g_minimum_ever_free,
           g_a_consumed, g_b_consumed, g_c_consumed,
           g_allocations_aligned, g_oom_is_null,
           g_malloc_failed_hooks, g_assert_failures, g_task03_pass);
#endif
    return g_task03_pass ? 0 : 1;
}
