#include "heap4_model.h"

enum { BLOCK_BYTES = 48 };

typedef struct {
    ModelBlock first;
    uint8_t first_payload[BLOCK_BYTES - sizeof(ModelBlock)];
    ModelBlock middle;
    uint8_t middle_payload[BLOCK_BYTES - sizeof(ModelBlock)];
    ModelBlock last;
    uint8_t last_payload[BLOCK_BYTES - sizeof(ModelBlock)];
} CoalesceArena;

typedef union {
    uint64_t force_alignment;
    CoalesceArena blocks;
} AlignedCoalesceArena;

AlignedCoalesceArena g_coalesce_arena;
ModelBlock *g_free_head;
volatile size_t g_before_nodes;
volatile size_t g_before_total;
volatile size_t g_before_largest;
volatile size_t g_after_nodes;
volatile size_t g_after_total;
volatile size_t g_after_largest;
volatile int g_task02_pass;

static void insert_and_coalesce(ModelBlock *block)
{
    ModelBlock *previous = NULL;
    ModelBlock *current = g_free_head;

    while (current != NULL && (uintptr_t)current < (uintptr_t)block)
    {
        previous = current;
        current = current->next;
    }

    block->next = current;
    if (current != NULL &&
        (uint8_t *)block + block->size == (uint8_t *)current)
    {
        block->size += current->size;
        block->next = current->next;
    }

    if (previous != NULL &&
        (uint8_t *)previous + previous->size == (uint8_t *)block)
    {
        previous->size += block->size;
        previous->next = block->next;
    }
    else if (previous != NULL)
    {
        previous->next = block;
    }
    else
    {
        g_free_head = block;
    }
}

static void measure(size_t *nodes, size_t *total, size_t *largest)
{
    ModelBlock *block;

    *nodes = 0U;
    *total = 0U;
    *largest = 0U;
    for (block = g_free_head; block != NULL; block = block->next)
    {
        (*nodes)++;
        *total += block->size;
        if (block->size > *largest)
        {
            *largest = block->size;
        }
    }
}

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

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

int main(void)
{
    CoalesceArena *arena = &g_coalesce_arena.blocks;
    size_t nodes;
    size_t total;
    size_t largest;

    arena->first.size = BLOCK_BYTES;
    arena->middle.size = BLOCK_BYTES;
    arena->last.size = BLOCK_BYTES;
    g_free_head = NULL;

    insert_and_coalesce(&arena->first);
    insert_and_coalesce(&arena->last);
    measure(&nodes, &total, &largest);
    g_before_nodes = nodes;
    g_before_total = total;
    g_before_largest = largest;
    task02_fragmented_checkpoint();

    insert_and_coalesce(&arena->middle);
    measure(&nodes, &total, &largest);
    g_after_nodes = nodes;
    g_after_total = total;
    g_after_largest = largest;
    g_task02_pass =
        g_before_nodes == 2U && g_before_total == 96U &&
        g_before_largest == 48U && g_after_nodes == 1U &&
        g_after_total == 144U && g_after_largest == 144U &&
        g_free_head == &arena->first && g_free_head->next == NULL;

    task02_debug_checkpoint();

#if defined(HOST_PRINTF)
    printf("before_nodes=%zu before_total=%zu before_largest=%zu "
           "after_nodes=%zu after_total=%zu after_largest=%zu pass=%d\n",
           g_before_nodes, g_before_total, g_before_largest,
           g_after_nodes, g_after_total, g_after_largest, g_task02_pass);
#endif
    return g_task02_pass ? 0 : 1;
}
