//! returns: 0
//! Test slice .append() method with inline allocator.

/// Simple bump allocator for testing.
record Arena {
    data: *mut [u8],
    offset: u32,
}

fn newArena(data: *mut [u8]) -> Arena {
    return Arena { data, offset: 0 };
}

unsafe fn arenaAlloc(arena: *unsafe mut Arena, size: u32, al: u32) -> *mut opaque {
    let aligned = (arena.offset + al - 1) / al * al;
    let newOffset = aligned + size;

    assert newOffset <= arena.data.len as u32;

    let base: *mut u8 = &mut arena.data[aligned];
    set arena.offset = newOffset;

    return base as *mut opaque;
}

/// Allocator record matching the compiler's expected layout.
record Allocator: Copy {
    func: unsafe fn(*unsafe mut opaque, u32, u32) -> *mut opaque,
    ctx: *unsafe mut opaque,
}

unsafe fn arenaAllocFn(ctx: *unsafe mut opaque, size: u32, al: u32) -> *mut opaque {
    let arena = ctx as *unsafe mut Arena;
    return arenaAlloc(&mut *arena, size, al);
}

unsafe fn arenaAllocator(arena: &mut Arena) -> Allocator {
    return Allocator {
        func: arenaAllocFn,
        ctx: (arena as *unsafe mut Arena) as *unsafe mut opaque,
    };
}

unsafe static BUF: [u8; 4096] = undefined;

@default unsafe fn main() -> i32 {
    static arena: Arena = undefined;
    set arena = newArena(&mut BUF[..]);
    let a = arenaAllocator(&mut arena);

    // Allocate initial capacity of 4.
    let ptr = arenaAlloc(&mut arena, @sizeOf(i32) * 4, @alignOf(i32));
    let mut nums = @sliceOf(ptr as *mut i32, 0, 4);

    // Append within capacity.
    nums.append(10, a);
    nums.append(20, a);
    nums.append(30, a);

    if nums.len <> 3 {
        return 1;
    }
    if nums.cap <> 4 {
        return 2;
    }
    if nums[0] <> 10 {
        return 3;
    }
    if nums[1] <> 20 {
        return 4;
    }
    if nums[2] <> 30 {
        return 5;
    }

    // Append to fill capacity.
    nums.append(40, a);

    if nums.len <> 4 {
        return 6;
    }
    if nums.cap <> 4 {
        return 7;
    }

    // Append past capacity -- triggers growth.
    nums.append(50, a);

    if nums.len <> 5 {
        return 8;
    }
    // Cap should have grown (cap * 2 | 1 = 4 * 2 | 1 = 9).
    if nums.cap <> 9 {
        return 9;
    }
    if nums[4] <> 50 {
        return 10;
    }

    // Verify old elements survived the copy.
    if nums[0] <> 10 {
        return 11;
    }
    if nums[3] <> 40 {
        return 12;
    }

    // Append from empty (cap == 0 triggers growth with cap = 1).
    let mut empty: *mut [i32] = &mut [];
    empty.append(99, a);

    if empty.len <> 1 {
        return 13;
    }
    if empty.cap <> 1 {
        return 14;
    }
    if empty[0] <> 99 {
        return 15;
    }

    // Sum all elements in nums.
    let mut sum: i32 = 0;
    for n in nums {
        set sum += n;
    }
    if sum <> 150 {
        return 16;
    }

    // Test append with u8 elements (stride = 1).
    let bytePtr = arenaAlloc(&mut arena, 2, 1);
    let mut bytes = @sliceOf(bytePtr as *mut u8, 0, 2);
    bytes.append(0xAB, a);
    bytes.append(0xCD, a);

    if bytes.len <> 2 {
        return 17;
    }
    if bytes[0] <> 0xAB {
        return 18;
    }
    if bytes[1] <> 0xCD {
        return 19;
    }

    // Trigger growth on u8 slice.
    bytes.append(0xEF, a);

    if bytes.len <> 3 {
        return 20;
    }
    if bytes.cap <> 5 {
        return 21;
    }
    if bytes[2] <> 0xEF {
        return 22;
    }
    // Verify old u8 elements survived.
    if bytes[0] <> 0xAB {
        return 23;
    }

    // Test that .append() returns the slice, allowing rebinding.
    let ptr2 = arenaAlloc(&mut arena, @sizeOf(i32) * 2, @alignOf(i32));
    let mut vals = @sliceOf(ptr2 as *mut i32, 0, 2);

    // Append within capacity, rebind via return value.
    set vals = vals.append(42, a);
    assert vals.len == 1;
    assert vals[0] == 42;

    set vals = vals.append(43, a);
    assert vals.len == 2;

    // Append past capacity -- triggers growth and returns updated slice.
    let oldPtr = &vals[0] as *opaque;
    set vals = vals.append(44, a);
    assert vals.len == 3;
    assert vals.cap == 5;

    // After growth, the data pointer should have changed.
    let newPtr = &vals[0] as *opaque;
    assert oldPtr <> newPtr;

    // Verify all elements survived.
    assert vals[0] == 42;
    assert vals[1] == 43;
    assert vals[2] == 44;

    return 0;
}
