#import "std.sx";

// --- Allocator protocol (inline: ctx + fn-ptrs, no vtable indirection) ---

Allocator :: protocol #inline {
    alloc :: (size: s64) -> *void;
    dealloc :: (ptr: *void);
}

// --- GPA: general purpose allocator (malloc/free wrapper) ---

GPA :: struct {
    alloc_count: s64;

    create :: (gpa: *GPA) -> Allocator {
        xx gpa;
    }
}

impl Allocator for GPA {
    alloc :: (self: *GPA, size: s64) -> *void {
        self.alloc_count += 1;
        malloc(size);
    }
    dealloc :: (self: *GPA, ptr: *void) {
        self.alloc_count -= 1;
        free(ptr);
    }
}

// --- Arena: multi-chunk bump allocator ---

ArenaChunk :: struct {
    next: *ArenaChunk;
    cap: s64;
}

Arena :: struct {
    first: *ArenaChunk;
    end_index: s64;
    parent: Allocator;

    add_chunk :: (a: *Arena, min_size: s64) {
        prev_cap := if a.first != null then a.first.cap else 0;
        needed := min_size + 16 + 16;
        len := (prev_cap + needed) * 3 / 2;
        raw := a.parent.alloc(len);
        chunk : *ArenaChunk = xx raw;
        chunk.next = a.first;
        chunk.cap = len;
        a.first = chunk;
        a.end_index = 0;
    }

    create :: (a: *Arena, parent: Allocator, size: s64) -> Allocator {
        a.first = null;
        a.end_index = 0;
        a.parent = parent;
        a.add_chunk(size);
        xx a;
    }

    reset :: (a: *Arena) {
        if a.first != null {
            it := a.first.next;
            while it != null {
                next := it.next;
                a.parent.dealloc(it);
                it = next;
            }
            a.first.next = null;
        }
        a.end_index = 0;
    }

    deinit :: (a: *Arena) {
        it := a.first;
        while it != null {
            next := it.next;
            a.parent.dealloc(it);
            it = next;
        }
        a.first = null;
        a.end_index = 0;
    }
}

impl Allocator for Arena {
    alloc :: (self: *Arena, size: s64) -> *void {
        aligned := (size + 7) & (0 - 8);
        if self.first != null {
            usable := self.first.cap - 16;
            if self.end_index + aligned <= usable {
                buf : [*]u8 = xx self.first;
                ptr := @buf[16 + self.end_index];
                self.end_index = self.end_index + aligned;
                return ptr;
            }
        }
        self.add_chunk(aligned);
        buf : [*]u8 = xx self.first;
        ptr := @buf[16 + self.end_index];
        self.end_index = self.end_index + aligned;
        ptr;
    }
    dealloc :: (self: *Arena, ptr: *void) {}
}

// --- BufAlloc: bump allocator backed by a user-provided slice ---

BufAlloc :: struct {
    buf: [*]u8;
    len: s64;
    pos: s64;

    create :: (b: *BufAlloc, buf: [*]u8, len: s64) -> Allocator {
        b.buf = buf;
        b.len = len;
        b.pos = 0;
        xx b;
    }

    reset :: (b: *BufAlloc) {
        b.pos = 0;
    }
}

impl Allocator for BufAlloc {
    alloc :: (self: *BufAlloc, size: s64) -> *void {
        aligned := (size + 7) & (0 - 8);
        if self.pos + aligned > self.len {
            return null;
        }
        ptr := @self.buf[self.pos];
        self.pos = self.pos + aligned;
        ptr;
    }
    dealloc :: (self: *BufAlloc, ptr: *void) {}
}
