const builtin = @import("builtin");
const std = @import("std");
const common = @import("common.zig");

comptime {
    if (builtin.target.isMuslLibC() or builtin.target.isWasiLibC()) {
        // memcpy implemented in compiler_rt
        // memmove implemented in compiler_rt
        // memset implemented in compiler_rt
        // memcmp implemented in compiler_rt
        @export(&memchr, .{ .name = "memchr", .linkage = common.linkage, .visibility = common.visibility });
        @export(&strcpy, .{ .name = "strcpy", .linkage = common.linkage, .visibility = common.visibility });
        @export(&strncpy, .{ .name = "strncpy", .linkage = common.linkage, .visibility = common.visibility });
        @export(&strcat, .{ .name = "strcat", .linkage = common.linkage, .visibility = common.visibility });
        @export(&strncat, .{ .name = "strncat", .linkage = common.linkage, .visibility = common.visibility });
        @export(&strcmp, .{ .name = "strcmp", .linkage = common.linkage, .visibility = common.visibility });
        @export(&strncmp, .{ .name = "strncmp", .linkage = common.linkage, .visibility = common.visibility });
        @export(&strcoll, .{ .name = "strcoll", .linkage = common.linkage, .visibility = common.visibility });
        @export(&strxfrm, .{ .name = "strxfrm", .linkage = common.linkage, .visibility = common.visibility });
        @export(&strchr, .{ .name = "strchr", .linkage = common.linkage, .visibility = common.visibility });
        @export(&strrchr, .{ .name = "strrchr", .linkage = common.linkage, .visibility = common.visibility });
        @export(&strcspn, .{ .name = "strcspn", .linkage = common.linkage, .visibility = common.visibility });
        @export(&strspn, .{ .name = "strspn", .linkage = common.linkage, .visibility = common.visibility });
        @export(&strpbrk, .{ .name = "strpbrk", .linkage = common.linkage, .visibility = common.visibility });
        @export(&strstr, .{ .name = "strstr", .linkage = common.linkage, .visibility = common.visibility });
        @export(&strtok, .{ .name = "strtok", .linkage = common.linkage, .visibility = common.visibility });
        // strlen is in compiler_rt

        @export(&strtok_r, .{ .name = "strtok_r", .linkage = common.linkage, .visibility = common.visibility });
        @export(&stpcpy, .{ .name = "stpcpy", .linkage = common.linkage, .visibility = common.visibility });
        @export(&stpncpy, .{ .name = "stpncpy", .linkage = common.linkage, .visibility = common.visibility });
        @export(&strnlen, .{ .name = "strnlen", .linkage = common.linkage, .visibility = common.visibility });
        @export(&memmem, .{ .name = "memmem", .linkage = common.linkage, .visibility = common.visibility });

        @export(&memccpy, .{ .name = "memccpy", .linkage = common.linkage, .visibility = common.visibility });

        @export(&strsep, .{ .name = "strsep", .linkage = common.linkage, .visibility = common.visibility });
        @export(&strlcat, .{ .name = "strlcat", .linkage = common.linkage, .visibility = common.visibility });
        @export(&strlcpy, .{ .name = "strlcpy", .linkage = common.linkage, .visibility = common.visibility });
        @export(&explicit_bzero, .{ .name = "explicit_bzero", .linkage = common.linkage, .visibility = common.visibility });

        @export(&strchrnul, .{ .name = "strchrnul", .linkage = common.linkage, .visibility = common.visibility });
        @export(&strcasestr, .{ .name = "strcasestr", .linkage = common.linkage, .visibility = common.visibility });
        @export(&memrchr, .{ .name = "memrchr", .linkage = common.linkage, .visibility = common.visibility });
        @export(&mempcpy, .{ .name = "mempcpy", .linkage = common.linkage, .visibility = common.visibility });

        @export(&__strcoll_l, .{ .name = "__strcoll_l", .linkage = common.linkage, .visibility = common.visibility });
        @export(&__strxfrm_l, .{ .name = "__strxfrm_l", .linkage = common.linkage, .visibility = common.visibility });
        @export(&__strcoll_l, .{ .name = "strcoll_l", .linkage = common.linkage, .visibility = common.visibility });
        @export(&__strxfrm_l, .{ .name = "strxfrm_l", .linkage = common.linkage, .visibility = common.visibility });

        // These symbols are not in the public ABI of musl/wasi. However they depend on these exports internally.
        @export(&stpcpy, .{ .name = "__stpcpy", .linkage = common.linkage, .visibility = common.visibility });
        @export(&stpncpy, .{ .name = "__stpncpy", .linkage = common.linkage, .visibility = common.visibility });
        @export(&strchrnul, .{ .name = "__strchrnul", .linkage = common.linkage, .visibility = common.visibility });
        @export(&memrchr, .{ .name = "__memrchr", .linkage = common.linkage, .visibility = common.visibility });
    }

    if (builtin.target.isMinGW()) {
        @export(&strnlen, .{ .name = "strnlen", .linkage = common.linkage, .visibility = common.visibility });
        @export(&mempcpy, .{ .name = "mempcpy", .linkage = common.linkage, .visibility = common.visibility });
        @export(&strtok_r, .{ .name = "strtok_r", .linkage = common.linkage, .visibility = common.visibility });
    }
}

fn memchr(ptr: *const anyopaque, value: c_int, len: usize) callconv(.c) ?*anyopaque {
    const bytes: [*]const u8 = @ptrCast(ptr);
    return @constCast(bytes[std.mem.findScalar(u8, bytes[0..len], @truncate(@as(c_uint, @bitCast(value)))) orelse return null ..]);
}

fn strcpy(noalias dst: [*]c_char, noalias src: [*:0]const c_char) callconv(.c) [*]c_char {
    _ = stpcpy(dst, src);
    return dst;
}

fn strncpy(noalias dst: [*]c_char, noalias src: [*:0]const c_char, max: usize) callconv(.c) [*]c_char {
    _ = stpncpy(dst, src, max);
    return dst;
}

fn strcat(noalias dst: [*:0]c_char, noalias src: [*:0]const c_char) callconv(.c) [*:0]c_char {
    return strncat(dst, src, std.math.maxInt(usize));
}

fn strncat(noalias dst: [*:0]c_char, noalias src: [*:0]const c_char, max: usize) callconv(.c) [*:0]c_char {
    const dst_len = std.mem.len(@as([*:0]u8, @ptrCast(dst)));
    const src_len = strnlen(src, max);

    @memcpy(dst[dst_len..][0..src_len], src[0..src_len]);
    dst[dst_len + src_len] = 0;
    return dst[0..(dst_len + src_len) :0].ptr;
}

fn strcmp(a: [*:0]const c_char, b: [*:0]const c_char) callconv(.c) c_int {
    return strncmp(a, b, std.math.maxInt(usize));
}

fn strncmp(a: [*:0]const c_char, b: [*:0]const c_char, max: usize) callconv(.c) c_int {
    return switch (std.mem.boundedOrderZ(u8, @ptrCast(a), @ptrCast(b), max)) {
        .eq => 0,
        .gt => 1,
        .lt => -1,
    };
}

fn strcoll(a: [*:0]const c_char, b: [*:0]const c_char) callconv(.c) c_int {
    return strcmp(a, b);
}

fn __strcoll_l(a: [*:0]const c_char, b: [*:0]const c_char, locale: *anyopaque) callconv(.c) c_int {
    _ = locale;
    return strcoll(a, b);
}

// NOTE: If 'max' is 0, 'dst' is allowed to be a null pointer
fn strxfrm(noalias dst: ?[*]c_char, noalias src: [*:0]const c_char, max: usize) callconv(.c) usize {
    const src_len = std.mem.len(@as([*:0]const u8, @ptrCast(src)));
    if (src_len < max) @memcpy(dst.?[0 .. src_len + 1], src[0 .. src_len + 1]);
    return src_len;
}

fn __strxfrm_l(noalias dst: ?[*]c_char, noalias src: [*:0]const c_char, max: usize, locale: *anyopaque) callconv(.c) usize {
    _ = locale;
    return strxfrm(dst, src, max);
}

fn strchr(str: [*:0]const c_char, value: c_int) callconv(.c) ?[*:0]c_char {
    const str_u8: [*:0]const u8 = @ptrCast(str);
    const len = std.mem.len(str_u8);

    if (value == 0) return @constCast(str + len);
    return @constCast(str[std.mem.findScalar(u8, str_u8[0..len], @truncate(@as(c_uint, @bitCast(value)))) orelse return null ..]);
}

fn strrchr(str: [*:0]const c_char, value: c_int) callconv(.c) ?[*:0]c_char {
    const str_u8: [*:0]const u8 = @ptrCast(str);
    // std.mem.len(str) + 1 to not special case '\0'
    return @constCast(str[std.mem.findScalarLast(u8, str_u8[0 .. std.mem.len(str_u8) + 1], @truncate(@as(c_uint, @bitCast(value)))) orelse return null ..]);
}

fn strcspn(dst: [*:0]const c_char, values: [*:0]const c_char) callconv(.c) usize {
    const dst_slice = std.mem.span(@as([*:0]const u8, @ptrCast(dst)));
    return std.mem.findAny(u8, dst_slice, std.mem.span(@as([*:0]const u8, @ptrCast(values)))) orelse dst_slice.len;
}

fn strspn(dst: [*:0]const c_char, values: [*:0]const c_char) callconv(.c) usize {
    const dst_slice = std.mem.span(@as([*:0]const u8, @ptrCast(dst)));
    return std.mem.findNone(u8, dst_slice, std.mem.span(@as([*:0]const u8, @ptrCast(values)))) orelse dst_slice.len;
}

fn strpbrk(haystack: [*:0]const c_char, needle: [*:0]const c_char) callconv(.c) ?[*:0]c_char {
    return @constCast(haystack[std.mem.findAny(u8, std.mem.span(@as([*:0]const u8, @ptrCast(haystack))), std.mem.span(@as([*:0]const u8, @ptrCast(needle)))) orelse return null ..]);
}

fn strstr(haystack: [*:0]const c_char, needle: [*:0]const c_char) callconv(.c) ?[*:0]c_char {
    return @constCast(haystack[std.mem.find(u8, std.mem.span(@as([*:0]const u8, @ptrCast(haystack))), std.mem.span(@as([*:0]const u8, @ptrCast(needle)))) orelse return null ..]);
}

fn strtok(noalias maybe_str: ?[*:0]c_char, noalias values: [*:0]const c_char) callconv(.c) ?[*:0]c_char {
    const state = struct {
        var str: ?[*:0]c_char = null;
    };

    return strtok_r(maybe_str, values, &state.str);
}

// strlen is in compiler_rt

fn strtok_r(noalias maybe_str: ?[*:0]c_char, noalias values: [*:0]const c_char, noalias state: *?[*:0]c_char) callconv(.c) ?[*:0]c_char {
    const str = if (maybe_str) |str|
        str
    else if (state.*) |state_str|
        state_str
    else
        return null;

    const str_bytes = std.mem.span(@as([*:0]u8, @ptrCast(str)));
    const values_bytes = std.mem.span(@as([*:0]const u8, @ptrCast(values)));
    const tok_start = std.mem.findNone(u8, str_bytes, values_bytes) orelse return null;

    if (std.mem.findAnyPos(u8, str_bytes, tok_start, values_bytes)) |tok_end| {
        str[tok_end] = 0;
        state.* = str[tok_end + 1 ..];
    } else {
        state.* = str[str_bytes.len..];
    }

    return str[tok_start..];
}

fn stpcpy(noalias dst: [*]c_char, noalias src: [*:0]const c_char) callconv(.c) [*]c_char {
    const src_len = std.mem.len(@as([*:0]const u8, @ptrCast(src)));
    @memcpy(dst[0 .. src_len + 1], src[0 .. src_len + 1]);
    return dst + src_len;
}

fn stpncpy(noalias dst: [*]c_char, noalias src: [*:0]const c_char, max: usize) callconv(.c) [*]c_char {
    const src_len = strnlen(src, max);
    const copying_len = @min(max, src_len);
    @memcpy(dst[0..copying_len], src[0..copying_len]);
    @memset(dst[copying_len..][0 .. max - copying_len], 0x00);
    return dst + copying_len;
}

fn strnlen(str: [*:0]const c_char, max: usize) callconv(.c) usize {
    return std.mem.findScalar(u8, @ptrCast(str[0..max]), 0) orelse max;
}

fn memmem(haystack: *const anyopaque, haystack_len: usize, needle: *const anyopaque, needle_len: usize) callconv(.c) ?*anyopaque {
    const haystack_bytes: [*:0]const u8 = @ptrCast(haystack);
    const needle_bytes: [*:0]const u8 = @ptrCast(needle);

    return @constCast(haystack_bytes[std.mem.find(u8, haystack_bytes[0..haystack_len], needle_bytes[0..needle_len]) orelse return null ..]);
}

fn strsep(maybe_str: *?[*:0]c_char, values: [*:0]const c_char) callconv(.c) ?[*]c_char {
    if (maybe_str.*) |str| {
        const values_bytes = std.mem.span(@as([*:0]const u8, @ptrCast(values)));
        const str_bytes = std.mem.span(@as([*:0]u8, @ptrCast(str)));
        const found = std.mem.findAny(u8, str_bytes, values_bytes) orelse {
            maybe_str.* = null;
            return str;
        };

        str[found] = 0;
        maybe_str.* = str[found + 1 ..];
        return str;
    }

    return null;
}

fn strlcat(dst: [*:0]c_char, src: [*:0]const c_char, dst_total_len: usize) callconv(.c) usize {
    const dst_len = strnlen(dst, dst_total_len);
    const src_bytes = std.mem.span(@as([*:0]const u8, @ptrCast(src)));

    if (dst_total_len == dst_len) return dst_len + src_bytes.len;

    const copying_len = @min(dst_total_len - (dst_len + 1), src_bytes.len);

    @memcpy(dst[dst_len..][0..copying_len], src[0..copying_len]);
    dst[dst_len + copying_len] = 0;
    return dst_len + src_bytes.len;
}

fn strlcpy(dst: [*]c_char, src: [*:0]const c_char, dst_total_len: usize) callconv(.c) usize {
    const src_bytes = std.mem.span(@as([*:0]const u8, @ptrCast(src)));
    if (dst_total_len != 0) {
        const copying_len = @min(src_bytes.len, dst_total_len - 1);
        @memcpy(dst[0..copying_len], src[0..copying_len]);
        dst[copying_len] = 0;
    }
    return src_bytes.len;
}

fn memccpy(noalias dst: *anyopaque, noalias src: *const anyopaque, value: c_int, len: usize) callconv(.c) *anyopaque {
    const dst_bytes: [*]u8 = @ptrCast(dst);
    const src_bytes: [*]const u8 = @ptrCast(src);
    const value_u8: u8 = @truncate(@as(c_uint, @bitCast(value)));
    const copying_len = std.mem.findScalar(u8, src_bytes[0..len], value_u8) orelse len;
    @memcpy(dst_bytes[0..copying_len], src_bytes[0..copying_len]);
    return dst_bytes + copying_len;
}

fn explicit_bzero(ptr: *anyopaque, len: usize) callconv(.c) void {
    const bytes: [*]u8 = @ptrCast(ptr);
    std.crypto.secureZero(u8, bytes[0..len]);
}

fn strchrnul(str: [*:0]const c_char, value: c_int) callconv(.c) [*:0]c_char {
    const str_u8: [*:0]const u8 = @ptrCast(str);
    const len = std.mem.len(str_u8);

    if (value == 0) return @constCast(str + len);
    return @constCast(str[std.mem.findScalar(u8, str_u8[0..len], @truncate(@as(c_uint, @bitCast(value)))) orelse len ..]);
}

fn strcasestr(haystack: [*:0]const c_char, needle: [*:0]const c_char) callconv(.c) ?[*:0]c_char {
    return @constCast(haystack[std.ascii.findIgnoreCase(std.mem.span(@as([*:0]const u8, @ptrCast(haystack))), std.mem.span(@as([*:0]const u8, @ptrCast(needle)))) orelse return null ..]);
}

fn memrchr(ptr: *const anyopaque, value: c_int, len: usize) callconv(.c) ?*anyopaque {
    const bytes: [*]const u8 = @ptrCast(ptr);
    return @constCast(bytes[std.mem.findScalarLast(u8, bytes[0..len], @truncate(@as(c_uint, @bitCast(value)))) orelse return null ..]);
}

fn mempcpy(noalias dst: *anyopaque, noalias src: *const anyopaque, len: usize) callconv(.c) *anyopaque {
    const dst_bytes: [*]u8 = @ptrCast(dst);
    const src_bytes: [*]const u8 = @ptrCast(src);
    @memcpy(dst_bytes[0..len], src_bytes[0..len]);
    return dst_bytes + len;
}

test strncmp {
    try std.testing.expect(strncmp(@ptrCast("a"), @ptrCast("b"), 1) < 0);
    try std.testing.expect(strncmp(@ptrCast("a"), @ptrCast("c"), 1) < 0);
    try std.testing.expect(strncmp(@ptrCast("b"), @ptrCast("a"), 1) > 0);
    try std.testing.expect(strncmp(@ptrCast("\xff"), @ptrCast("\x02"), 1) > 0);
}
