const std = @import("index.zig");
const builtin = @import("builtin");
const debug = std.debug;
const mem = std.mem;
const assert = debug.assert;

pub const line_sep = switch (builtin.os) {
    builtin.Os.windows => "\r\n",
    else => "\n",
};

/// Deprecated, use mem.len
pub fn len(ptr: [*]const u8) usize {
    return mem.len(u8, ptr);
}

pub fn cmp(a: [*]const u8, b: [*]const u8) i8 {
    var index: usize = 0;
    while (a[index] == b[index] and a[index] != 0) : (index += 1) {}
    if (a[index] > b[index]) {
        return 1;
    } else if (a[index] < b[index]) {
        return -1;
    } else {
        return 0;
    }
}

/// Deprecated, use mem.toSliceConst
pub fn toSliceConst(str: [*]const u8) []const u8 {
    return mem.toSliceConst(u8, str);
}

/// Deprecated, use mem.toSlice
pub fn toSlice(str: [*]u8) []u8 {
    return mem.toSlice(u8, str);
}

test "cstr fns" {
    comptime testCStrFnsImpl();
    testCStrFnsImpl();
}

fn testCStrFnsImpl() void {
    assert(cmp(c"aoeu", c"aoez") == -1);
    assert(len(c"123456789") == 9);
}

/// Returns a mutable slice with 1 more byte of length which is a null byte.
/// Caller owns the returned memory.
pub fn addNullByte(allocator: *mem.Allocator, slice: []const u8) ![]u8 {
    const result = try allocator.alloc(u8, slice.len + 1);
    mem.copy(u8, result, slice);
    result[slice.len] = 0;
    return result;
}

pub const NullTerminated2DArray = struct {
    allocator: *mem.Allocator,
    byte_count: usize,
    ptr: ?[*]?[*]u8,

    /// Takes N lists of strings, concatenates the lists together, and adds a null terminator
    /// Caller must deinit result
    pub fn fromSlices(allocator: *mem.Allocator, slices: []const []const []const u8) !NullTerminated2DArray {
        var new_len: usize = 1; // 1 for the list null
        var byte_count: usize = 0;
        for (slices) |slice| {
            new_len += slice.len;
            for (slice) |inner| {
                byte_count += inner.len;
            }
            byte_count += slice.len; // for the null terminators of inner
        }

        const index_size = @sizeOf(usize) * new_len; // size of the ptrs
        byte_count += index_size;

        const buf = try allocator.alignedAlloc(u8, @alignOf(?*u8), byte_count);
        errdefer allocator.free(buf);

        var write_index = index_size;
        const index_buf = @bytesToSlice(?[*]u8, buf);

        var i: usize = 0;
        for (slices) |slice| {
            for (slice) |inner| {
                index_buf[i] = buf.ptr + write_index;
                i += 1;
                mem.copy(u8, buf[write_index..], inner);
                write_index += inner.len;
                buf[write_index] = 0;
                write_index += 1;
            }
        }
        index_buf[i] = null;

        return NullTerminated2DArray{
            .allocator = allocator,
            .byte_count = byte_count,
            .ptr = @ptrCast(?[*]?[*]u8, buf.ptr),
        };
    }

    pub fn deinit(self: *NullTerminated2DArray) void {
        const buf = @ptrCast([*]u8, self.ptr);
        self.allocator.free(buf[0..self.byte_count]);
    }
};
