#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_fragmented_stats;
HeapStats_t g_final_stats;
volatile size_t g_initial_free;
volatile size_t g_after_allocations;
volatile size_t g_final_free;
volatile size_t g_minimum_ever_free;
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_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_fragmented_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)
{
    void *probe;
    void *a;
    void *b;
    void *c;
    void *too_large;

    vPortHeapResetState();
    probe = pvPortMalloc(1U);
    if (probe != NULL)
    {
        vPortFree(probe);
    }
    g_initial_free = xPortGetFreeHeapSize();

    a = pvPortMalloc(24U);
    b = pvPortMalloc(40U);
    c = pvPortMalloc(16U);
    g_after_allocations = xPortGetFreeHeapSize();
    g_allocations_aligned =
        a != NULL && b != NULL && c != NULL &&
        ((uintptr_t)a % portBYTE_ALIGNMENT) == 0U &&
        ((uintptr_t)b % portBYTE_ALIGNMENT) == 0U &&
        ((uintptr_t)c % portBYTE_ALIGNMENT) == 0U;

    vPortFree(a);
    vPortFree(c);
    vPortGetHeapStats(&g_fragmented_stats);
    heap4_fragmented_checkpoint();

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

    too_large = pvPortMalloc(configTOTAL_HEAP_SIZE * 2U);
    g_oom_is_null = too_large == NULL;
    g_task03_pass =
        probe != NULL && g_allocations_aligned &&
        g_initial_free > g_after_allocations &&
        g_final_free == g_initial_free &&
        g_minimum_ever_free <= g_after_allocations &&
        g_fragmented_stats.xNumberOfFreeBlocks == 2U &&
        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=%zu fragmented=%zu final=%zu min=%zu "
           "aligned=%d oom=%d hooks=%d asserts=%d pass=%d\n",
           g_initial_free, g_after_allocations,
           g_fragmented_stats.xNumberOfFreeBlocks, g_final_free,
           g_minimum_ever_free, g_allocations_aligned, g_oom_is_null,
           g_malloc_failed_hooks, g_assert_failures, g_task03_pass);
#endif
    return g_task03_pass ? 0 : 1;
}
