#include "lib/memory.h"
#include "memory/mm.h"

#define KHEAP_START 0xE0000000

typedef struct __attribute__((packed)) {
    size_t size;
    uint8_t free;
    void* next_blk;
} alloc_header_t;

void memcpy(const void* src, void* dst, size_t sz) {
    for (size_t i = 0; i < sz; i++) {
        ((uint8_t*)dst)[i] = ((uint8_t*)src)[i];
    }
}

void memset(uint8_t* dst, uint8_t val, size_t sz) {
    for (size_t i = 0; i < sz; i++) {
        ((uint8_t*)dst)[i] = val;
    }
}

int strcmp(const char* s1, const char* s2) {
    while (*s1 && (*s1 == *s2)) {
        s1++;
        s2++;
    }
    return *(const uint8_t*)s1 - *(const uint8_t*)s2;
}

int strlen(const char* str) {
    int len = 0;
    while(*str != 0) {
        str++;
        len++;
    }
    return len;
}

int strncmp(const char *s1, const char *s2, size_t n) {
    // If n is 0, we don't compare anything and strings are "equal"
    while (n > 0) {
        // If characters don't match, or we hit the null-terminator of s1
        if (*s1 != *s2) {
            return *(unsigned char *)s1 - *(unsigned char *)s2;
        }
        
        // If we reached the end of the strings simultaneously
        if (*s1 == '\0') {
            break;
        }
        
        s1++;
        s2++;
        n--;
    }
    return 0;
}

char* strcpy(char* dest, const char* src) {
    char* saved_dest = dest;

    while ((*dest++ = *src++) != '\0') {}

    return saved_dest;
}

static void* kernel_heap_start = (void*)KHEAP_START;
static void* kernel_heap_end = (void*)KHEAP_START;
alloc_header_t* blk_list = NULL;

void kgrow(size_t pages) {
    for (size_t p = 0; p < pages; p++) {
        void* frame = p_alloc_frame();
        map_page((uint32_t)kernel_heap_end, (uint32_t)frame, PAGE_PRESENT | PAGE_WRITABLE);
        kernel_heap_end = (void*)((uint32_t)kernel_heap_end + PAGE_SIZE); 
    }
}

#define ALIGNMENT 4
#define ALIGN(size) (((size) + (ALIGNMENT - 1)) & ~(ALIGNMENT - 1))
void* kalloc(size_t sz) {
    sz = ALIGN(sz);

    if (kernel_heap_end == kernel_heap_start) {
        // probaably not great, but if we don't have a heap yet, allocate the first (or only) frame requested.
        // Then initialize the first block header for the allocator
        kgrow(4);
        
        blk_list = (alloc_header_t*)kernel_heap_start;
        blk_list->size = ((uint32_t)kernel_heap_end - (uint32_t)kernel_heap_start) - sizeof(alloc_header_t);
        blk_list->free = 1;
        blk_list->next_blk = NULL;
    }

    alloc_header_t* curr = blk_list;
    alloc_header_t* prev = NULL;

    while (curr != NULL) {
        if (curr->free && curr->size >= sz) {
            if (curr->size >= sz + sizeof(alloc_header_t) + ALIGNMENT) {
                alloc_header_t* new_blk = (alloc_header_t*)((uint32_t)curr + sizeof(alloc_header_t) + sz);
                
                new_blk->size = curr->size - sz - sizeof(alloc_header_t);
                new_blk->free = 1;
                new_blk->next_blk = curr->next_blk;
                
                curr->size = sz;
                curr->next_blk = new_blk;
            }
            
            curr->free = 0; 
            return (void*)((uint32_t)curr + sizeof(alloc_header_t));
        }
        prev = curr;
        curr = curr->next_blk;
    }

    // no more space, grow!!
    size_t required_space = sz + sizeof(alloc_header_t);
    size_t pages_to_grow = (required_space + PAGE_SIZE - 1) / PAGE_SIZE; 
    
    alloc_header_t* new_heap_blk = (alloc_header_t*)kernel_heap_end;
    
    kgrow(pages_to_grow);
    
    new_heap_blk->size = (pages_to_grow * PAGE_SIZE) - sizeof(alloc_header_t);
    new_heap_blk->free = 1;
    new_heap_blk->next_blk = NULL;
    
    if (prev != NULL) {
        prev->next_blk = new_heap_blk;
    }
    
    return kalloc(sz);
}

void kfree(void* ptr) {
    
}