/*
 * heap.c
 *
 * Copyright (C) 2013 Aleksandar Andrejevic <theflash@sdf.lonestar.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <heap.h>
#include <common.h>
#include <exception.h>
#include <memory.h>

void *__crt_heap_realloc(__crt_heap_t *heap, void *ptr, size_t alignment, size_t size);
extern __crt_heap_t *__crt_default_heap;

heap_t system_heap;
heap_t evictable_heap;

static void heap_problem(int problem)
{
    switch (problem)
    {
    case __CRT_HEAP_CORRUPTED:
        KERNEL_CRASH("Heap corrupted");

    case __CRT_HEAP_DOUBLE_FREE:
        KERNEL_CRASH("Attempt to free an already released region");

    case __CRT_HEAP_BAD_POINTER:
        KERNEL_CRASH("Bad pointer passed to heap function");
    }
}

void *heap_realloc(heap_t *heap, void *ptr, uintptr_t size)
{
    return __crt_heap_realloc(&heap->crt, ptr, 1, size);
}

void *heap_alloc(heap_t *heap, uintptr_t size)
{
    return __crt_heap_realloc(&heap->crt, NULL, 1, size);
}

void heap_free(heap_t *heap, void *ptr)
{
    __crt_heap_realloc(&heap->crt, ptr, 1, 0);
}

dword_t heap_create(heap_t *heap, uintptr_t start, uintptr_t end, dword_t flags, dword_t magic)
{
    start = PAGE_ALIGN(start);
    end = PAGE_ALIGN(end);

    ASSERT(start < end);

    heap->crt.magic = magic;
    heap->crt.base = (void*)start;
    heap->crt.size = end - start;
    heap->crt.next_offset = 0;
    heap->crt.flags = 0;
    heap->crt.mutex = (void*)&heap->lock;
    heap->crt.problem = heap_problem;
    heap->crt.lock_mutex_proc = (void (*)(void*))&lock_acquire;
    heap->crt.unlock_mutex_proc = (void (*)(void*))&lock_release;
    lock_init(&heap->lock);

    if (flags & HEAP_ZEROFILL) heap->crt.flags |= __CRT_HEAP_FLAG_ZEROFILL;

    dword_t block_flags = MEMORY_BLOCK_ACCESSIBLE | MEMORY_BLOCK_WRITABLE;
    if (flags & HEAP_EVICTABLE) block_flags |= MEMORY_BLOCK_EVICTABLE;

    void *address = alloc_pool(heap->crt.base, heap->crt.size, block_flags);
    ASSERT(address == heap->crt.base);

    return ERR_SUCCESS;
}

dword_t heap_destroy(heap_t *heap)
{
    free_pool(heap->crt.base);
    return ERR_SUCCESS;
}

void heap_init(void)
{
    heap_create(&system_heap, SYSTEM_HEAP_START, SYSTEM_HEAP_END, 0, SYSTEM_HEAP_MAGIC);
    heap_create(&evictable_heap, EVICTABLE_HEAP_START, EVICTABLE_HEAP_END, HEAP_EVICTABLE, EVICTABLE_HEAP_MAGIC);

    __crt_default_heap = &system_heap.crt;
}
