const std = @import("std");
const builtin = @import("builtin");
const expect = std.testing.expect;
const assert = std.debug.assert;

test "memcpy and memset intrinsics" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    try testMemcpyMemset();
    try comptime testMemcpyMemset();
}

fn testMemcpyMemset() !void {
    var foo: [20]u8 = undefined;
    var bar: [20]u8 = undefined;

    @memset(&foo, 'A');
    @memcpy(&bar, &foo);

    try expect(bar[0] == 'A');
    try expect(bar[11] == 'A');
    try expect(bar[19] == 'A');
}

test "@memcpy with both operands single-ptr-to-array, one is null-terminated" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    try testMemcpyBothSinglePtrArrayOneIsNullTerminated();
    try comptime testMemcpyBothSinglePtrArrayOneIsNullTerminated();
}

fn testMemcpyBothSinglePtrArrayOneIsNullTerminated() !void {
    var buf: [100]u8 = undefined;
    const suffix = "hello";
    @memcpy(buf[buf.len - suffix.len ..], suffix);
    try expect(buf[95] == 'h');
    try expect(buf[96] == 'e');
    try expect(buf[97] == 'l');
    try expect(buf[98] == 'l');
    try expect(buf[99] == 'o');
}

test "@memcpy dest many pointer" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    try testMemcpyDestManyPtr();
    try comptime testMemcpyDestManyPtr();
}

fn testMemcpyDestManyPtr() !void {
    var str = "hello".*;
    var buf: [5]u8 = undefined;
    var len: usize = 5;
    _ = &len;
    @memcpy(@as([*]u8, @ptrCast(&buf)), @as([*]const u8, @ptrCast(&str))[0..len]);
    try expect(buf[0] == 'h');
    try expect(buf[1] == 'e');
    try expect(buf[2] == 'l');
    try expect(buf[3] == 'l');
    try expect(buf[4] == 'o');
}

test "@memcpy C pointer" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    try testMemcpyCPointer();
    try comptime testMemcpyCPointer();
}

fn testMemcpyCPointer() !void {
    const src = "hello";
    var buf: [5]u8 = undefined;
    @memcpy(@as([*c]u8, &buf), src);
    try expect(buf[0] == 'h');
    try expect(buf[1] == 'e');
    try expect(buf[2] == 'l');
    try expect(buf[3] == 'l');
    try expect(buf[4] == 'o');
}

test "@memcpy slice" {
    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    try testMemcpySlice();
    try comptime testMemcpySlice();
}

fn testMemcpySlice() !void {
    var buf: [5]u8 = undefined;
    const dst: []u8 = &buf;
    const src: []const u8 = "hello";
    @memcpy(dst, src);
    try expect(buf[0] == 'h');
    try expect(buf[1] == 'e');
    try expect(buf[2] == 'l');
    try expect(buf[3] == 'l');
    try expect(buf[4] == 'o');
}

comptime {
    const S = struct {
        buffer: [8]u8 = undefined,
        fn set(self: *@This(), items: []const u8) void {
            @memcpy(self.buffer[0..items.len], items);
        }
    };

    var s = S{};
    s.set("hello");
    if (!std.mem.eql(u8, s.buffer[0..5], "hello")) @compileError("bad");
}

test "@memcpy comptime-only type" {
    const in: [4]type = .{ u8, u16, u32, u64 };
    comptime var out: [4]type = undefined;
    @memcpy(&out, &in);

    comptime assert(out[0] == u8);
    comptime assert(out[1] == u16);
    comptime assert(out[2] == u32);
    comptime assert(out[3] == u64);
}

test "@memcpy zero-bit type with aliasing" {
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const S = struct {
        fn doTheTest() void {
            var buf: [3]void = @splat({});
            const slice: []void = &buf;
            // These two pointers are the same, but it's still not considered aliasing because
            // the input and output slices both correspond to zero bits of memory.
            @memcpy(slice, slice);
            comptime assert(buf[0] == {});
            comptime assert(buf[1] == {});
            comptime assert(buf[2] == {});
        }
    };
    S.doTheTest();
    comptime S.doTheTest();
}

test "@memcpy with sentinel" {
    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const S = struct {
        fn doTheTest() void {
            const field = @typeInfo(struct { a: u32 }).@"struct".fields[0];
            var buffer: [field.name.len]u8 = undefined;
            @memcpy(&buffer, field.name);
        }
    };

    S.doTheTest();
    comptime S.doTheTest();
}

test "@memcpy no sentinel source into sentinel destination" {
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const S = struct {
        fn doTheTest() void {
            const src: []const u8 = &.{ 1, 2, 3 };
            comptime var dest_buf: [3:0]u8 = @splat(0);
            const dest: [:0]u8 = &dest_buf;
            @memcpy(dest, src);
        }
    };

    S.doTheTest();
    comptime S.doTheTest();
}
