comptime {
    _ = start;
}

test {
    _ = build;
    _ = builtin;
    _ = data_structures;
    _ = os;
    _ = start;
}

const build = #import("build.nat");
const builtin = #import("builtin.nat");
const testing = #import("testing.nat");

const data_structures = #import("data_structures.nat");

const c = #import("c.nat");
const os = #import("os.nat");

const start = #import("start.nat");

const assert = fn(ok: bool) void {
    if (!ok) {
        unreachable;
    }
}

const byte_equal = fn (a: []const u8, b: []const u8) bool {
    var result = false;
    if (a.length == b.length) {
        result = true;
        for (a, b) |a_byte, b_byte| {
            if (a_byte != b_byte) {
                result = false;
                break;
            }
        }
    }

    return result;
}

const print = fn(bytes: []const u8) void {
    const file_descriptor = os.StdFileDescriptor.get(descriptor = .stdout);
    _ = file_descriptor.write_all(bytes) catch unreachable;
}

const format_usize = fn(n: usize, buffer: &[65]u8) []u8 {
    var index: usize = buffer.length;
    var absolute = n;

    while (true) {
        const digit: u8 = #cast(absolute % 10);
        index -= 1;
        const ch = '0' + digit;
        buffer[index] = ch;
        assert(buffer[index] == ch);
        absolute /= 10;

        if (absolute == 0) {
            break;
        }
    }

    return buffer[index..];
}

const print_usize = fn(n: usize) void {
    var buffer: [65]u8 = undefined;
    const bytes = format_usize(n, buffer = buffer.&);
    assert(bytes.length < buffer.length);
    const file_descriptor = os.StdFileDescriptor.get(descriptor = .stdout);
    file_descriptor.write_all(bytes) catch unreachable;
}

const print_u8 = fn(n: u8) void {
    print_usize(n);
}

const kilobytes = fn (n: u64) u64 {
    return n * 1024;
}

const megabytes = fn (n: u64) u64 {
    return n * 1024 * 1024;
}

const gigabytes = fn (n: u64) u64 {
    return n * 1024 * 1024 * 1024;
}

const terabytes = fn (n: u64) u64 {
    return n * 1024 * 1024 * 1024 * 1024;
}

const Arena = struct{
    position: u64,
    commit_position: u64,
    alignment: u64,
    size: u64,

    const Temporary = struct{
        arena: &Arena,
        position: u64,
    };
    
    const commit_granularity = 2 * 1024 * 1024;
    const reserve = os.reserve;
    const commit = os.commit;

    const init = fn (requested_size: u64) *!&Arena {
        var size = requested_size;
        const size_roundup_granularity = megabytes(64);
        size += size_roundup_granularity - 1;
        size -= size % size_roundup_granularity;
        const initial_commit_size = commit_granularity;
        assert(initial_commit_size >= #size(Arena));

        const reserved_memory = try reserve(size);
        try commit(reserved_memory, initial_commit_size);

        const arena: &Arena = #cast(reserved_memory);
        arena.@ = .{
            .position = #size(Arena),
            .commit_position = initial_commit_size,
            .alignment = 8,
            .size = size,
        };

        return arena;
    }

    const allocate = fn (arena: &Arena, size: u64) *!&any {
        if (arena.position + size <= arena.size) {
            const base: &any = #cast(arena);
            var post_alignment_position = arena.position + arena.alignment - 1;
            post_alignment_position -= post_alignment_position % arena.alignment;
            const alignment = post_alignment_position - arena.position;
            const result = base + arena.position + alignment;
            arena.position += size + alignment;

            if (arena.commit_position < arena.position) {
                var size_to_commit = arena.position - arena.commit_position;
                size_to_commit += commit_granularity - 1;
                size_to_commit -= size_to_commit % commit_granularity;
                try commit(base + arena.commit_position, size_to_commit);
                arena.commit_position += size_to_commit;
            }

            return result;
        } else {
            unreachable;
        }
    }

    const new = fn (arena: &Arena, $T: type) *!&T {
        const result: &T = #cast(arena.allocate(#size(T)));
        return result;
    }

    const new_array = fn (arena: &Arena, $T: type, count: usize) *![]T {
        const result: [&]T = #cast(try arena.allocate(#size(T) * count));
        return result[0..count];
    }
};

const copy_bytes = fn(destination: []u8, source: []const u8) void {
    assert(ok = destination.length == source.length);
    for (0..destination.length) |i| {
        destination[i] = source[i];
    }
}

const c_len = fn (pointer_to_string: [&:0]const u8) usize {
    var i: usize = 0;
    while (pointer_to_string[i] != 0) {
        i += 1;
    }

    return i;
}

const c_slice = fn (pointer_to_string: [&:0]const u8) [:0]const u8 {
    const length = c_len(pointer_to_string);
    return pointer_to_string[0..length:0];
}

const concatenate_bytes = fn (arena: &Arena, byte_sequences: []const []const u8) *![]u8 {
    var byte_count: usize = 0;
    for (byte_sequences) |byte_sequence| {
        byte_count += byte_sequence.length;
    }

    var concatenation = try arena.new_array($u8, byte_count);
    var offset: usize = 0;

    for (byte_sequences) |bs| {
        var i: usize = 0;

        while (i < bs.length) {
            concatenation[offset + i] = bs[i];
            i += 1;
        }

        offset += i;
    }

    return concatenation;
}

const align_forward = fn(value: u64, alignment: u64) u64 {
    const mask = alignment - 1;
    return (value + mask) & ~mask;
}

test "concatenate" {
    var arena = try Arena.init(2*1024*1024);
    const concatenation = try concatenate_bytes(arena, .{ "ABC", "DEF" }.&);
    const are_equal = byte_equal(concatenation, "ABCDEF");
    try testing.expect(are_equal);
}

const Target = struct {
    cpu: builtin.Cpu,
    os: builtin.Os,
    abi: builtin.Abi,
};
