import "std_types";
import libc "libc";

PAGE_SIZE :: 4096;

Arena :: struct {
    data:     *u8;
    len:       usize;
    commit:    usize;
    reserve:   usize;
    alignment: usize;
}

GetAlignOffset :: proc(size: usize, align: usize): usize {
    mask: usize = align - 1;
    val:  usize = size & mask;
    if val {
        val = align - val;
    }
    return val;
}

AlignUp :: proc(size: usize, align: usize): usize {
    result := size + GetAlignOffset(size, align);
    return result;
}

InitArena :: proc(arena: *Arena, reserve: usize) {
    aligned_reserve := AlignUp(reserve, PAGE_SIZE);
    data := VReserve(aligned_reserve);
    libc.assert(data != nil);

    if data {
        arena.data = data;
        arena.reserve = aligned_reserve;
        arena.alignment = 8;
    }
}

PushSize :: proc(arena: *Arena, size: usize): *void {
    libc.assert(arena.data != nil);

    pointer := :usize(arena.data) + arena.len + size;
    align := GetAlignOffset(pointer, arena.alignment);
    aligned_size := size + align;

    a := arena.len + aligned_size;
    if a > arena.commit {
        diff := a - arena.commit;
        commit_size := AlignUp(diff, PAGE_SIZE*4);
        libc.assert(commit_size + arena.commit <= arena.reserve);

        if VCommit(&arena.data[arena.commit], commit_size) {
            arena.commit += commit_size;
        } else {
            libc.assert(false && "commit failed"[0]);
        }
    }

    arena.len += align;
    result: *void = &arena.data[arena.len];
    arena.len += size;
    return result;
}

TestArena :: proc() {
    arena: Arena;
}