const std = @import("std");
const builtin = @import("builtin");
const expect = std.testing.expect;
const assert = std.debug.assert;
const native_endian = builtin.target.cpu.arch.endian();

test "reinterpret bytes as integer with nonzero offset" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    try testReinterpretBytesAsInteger();
    try comptime testReinterpretBytesAsInteger();
}

fn testReinterpretBytesAsInteger() !void {
    const bytes = "\x12\x34\x56\x78\xab";
    const expected = switch (native_endian) {
        .little => 0xab785634,
        .big => 0x345678ab,
    };
    try expect(@as(*align(1) const u32, @ptrCast(bytes[1..5])).* == expected);
}

test "reinterpret an array over multiple elements, with no well-defined layout" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    try testReinterpretWithOffsetAndNoWellDefinedLayout();
    try comptime testReinterpretWithOffsetAndNoWellDefinedLayout();
}

fn testReinterpretWithOffsetAndNoWellDefinedLayout() !void {
    const bytes: ?[5]?u8 = [5]?u8{ 0x12, 0x34, 0x56, 0x78, 0x9a };
    const ptr = &bytes.?[1];
    const copy: [4]?u8 = @as(*const [4]?u8, @ptrCast(ptr)).*;
    _ = copy;
    //try expect(@ptrCast(*align(1)?u8, bytes[1..5]).* == );
}

test "reinterpret bytes inside auto-layout struct as integer with nonzero offset" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    try testReinterpretStructWrappedBytesAsInteger();
    try comptime testReinterpretStructWrappedBytesAsInteger();
}

fn testReinterpretStructWrappedBytesAsInteger() !void {
    const S = struct { bytes: [5:0]u8 };
    const obj = S{ .bytes = "\x12\x34\x56\x78\xab".* };
    const expected = switch (native_endian) {
        .little => 0xab785634,
        .big => 0x345678ab,
    };
    try expect(@as(*align(1) const u32, @ptrCast(obj.bytes[1..5])).* == expected);
}

test "reinterpret bytes of an array into an extern struct" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    try testReinterpretBytesAsExternStruct();
    try comptime testReinterpretBytesAsExternStruct();
}

fn testReinterpretBytesAsExternStruct() !void {
    var bytes align(2) = [_]u8{ 1, 2, 3, 4, 5, 6 };

    const S = extern struct {
        a: u8,
        b: u16,
        c: u8,
    };

    const ptr: *const S = @ptrCast(&bytes);
    const val = ptr.c;
    try expect(val == 5);
}

test "reinterpret bytes of an extern struct (with under-aligned fields) into another" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    try testReinterpretExternStructAsExternStruct();
    try comptime testReinterpretExternStructAsExternStruct();
}

fn testReinterpretExternStructAsExternStruct() !void {
    const S1 = extern struct {
        a: u8,
        b: u16,
        c: u8,
    };
    comptime var bytes align(2) = S1{ .a = 0, .b = 0, .c = 5 };

    const S2 = extern struct {
        a: u32 align(2),
        c: u8,
    };
    const ptr: *const S2 = @ptrCast(&bytes);
    const val = ptr.c;
    try expect(val == 5);
}

test "reinterpret bytes of an extern struct into another" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    try testReinterpretOverAlignedExternStructAsExternStruct();
    try comptime testReinterpretOverAlignedExternStructAsExternStruct();
}

fn testReinterpretOverAlignedExternStructAsExternStruct() !void {
    const S1 = extern struct {
        a: u32,
        b: u32,
        c: u8,
    };
    comptime var bytes: S1 = .{ .a = 0, .b = 0, .c = 5 };

    const S2 = extern struct {
        a0: u32,
        a1: u16,
        a2: u16,
        c: u8,
    };
    const ptr: *const S2 = @ptrCast(&bytes);
    const val = ptr.c;
    try expect(val == 5);
}

test "lower reinterpreted comptime field ptr (with under-aligned fields)" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    // Test lowering a field ptr
    comptime var bytes align(2) = [_]u8{ 1, 2, 3, 4, 5, 6 };
    const S = extern struct {
        a: u32 align(2),
        c: u8,
    };
    comptime var ptr = @as(*const S, @ptrCast(&bytes));
    const val = &ptr.c;
    try expect(val.* == 5);

    // Test lowering an elem ptr
    comptime var src_value = S{ .a = 15, .c = 5 };
    comptime var ptr2 = @as(*[@sizeOf(S)]u8, @ptrCast(&src_value));
    const val2 = &ptr2[4];
    try expect(val2.* == 5);
}

test "lower reinterpreted comptime field ptr" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    // Test lowering a field ptr
    comptime var bytes align(4) = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8 };
    const S = extern struct {
        a: u32,
        c: u8,
    };
    comptime var ptr = @as(*const S, @ptrCast(&bytes));
    const val = &ptr.c;
    try expect(val.* == 5);

    // Test lowering an elem ptr
    comptime var src_value = S{ .a = 15, .c = 5 };
    comptime var ptr2 = @as(*[@sizeOf(S)]u8, @ptrCast(&src_value));
    const val2 = &ptr2[4];
    try expect(val2.* == 5);
}

test "reinterpret struct field at comptime" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const numNative = comptime Bytes.init(0x12345678);
    if (native_endian != .little) {
        try expect(std.mem.eql(u8, &[_]u8{ 0x12, 0x34, 0x56, 0x78 }, &numNative.bytes));
    } else {
        try expect(std.mem.eql(u8, &[_]u8{ 0x78, 0x56, 0x34, 0x12 }, &numNative.bytes));
    }
}

const Bytes = struct {
    bytes: [4]u8,

    pub fn init(v: u32) Bytes {
        var res: Bytes = undefined;
        @as(*align(1) u32, @ptrCast(&res.bytes)).* = v;

        return res;
    }
};

test "ptrcast of const integer has the correct object size" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    const is_value = ~@as(isize, @intCast(std.math.minInt(isize)));
    const is_bytes = @as([*]const u8, @ptrCast(&is_value))[0..@sizeOf(isize)];
    if (@sizeOf(isize) == 8) {
        switch (native_endian) {
            .little => {
                try expect(is_bytes[0] == 0xff);
                try expect(is_bytes[1] == 0xff);
                try expect(is_bytes[2] == 0xff);
                try expect(is_bytes[3] == 0xff);

                try expect(is_bytes[4] == 0xff);
                try expect(is_bytes[5] == 0xff);
                try expect(is_bytes[6] == 0xff);
                try expect(is_bytes[7] == 0x7f);
            },
            .big => {
                try expect(is_bytes[0] == 0x7f);
                try expect(is_bytes[1] == 0xff);
                try expect(is_bytes[2] == 0xff);
                try expect(is_bytes[3] == 0xff);

                try expect(is_bytes[4] == 0xff);
                try expect(is_bytes[5] == 0xff);
                try expect(is_bytes[6] == 0xff);
                try expect(is_bytes[7] == 0xff);
            },
        }
    }
}

test "implicit optional pointer to optional anyopaque pointer" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    var buf: [4]u8 = "aoeu".*;
    const x: ?[*]u8 = &buf;
    const y: ?*anyopaque = x;
    const z: *[4]u8 = @ptrCast(y);
    try expect(std.mem.eql(u8, z, "aoeu"));
}

test "@ptrCast slice to slice" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const S = struct {
        fn foo(slice: []u32) []i32 {
            return @as([]i32, @ptrCast(slice));
        }
    };
    var buf: [4]u32 = .{ 0, 0, 0, 0 };
    const alias = S.foo(&buf);
    alias[1] = 42;
    try expect(buf[1] == 42);
    try expect(alias.len == 4);
}

test "comptime @ptrCast a subset of an array, then write through it" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    comptime {
        var buff: [16]u8 align(4) = undefined;
        const len_bytes = @as(*u32, @ptrCast(&buff));
        len_bytes.* = 16;
        const source = "abcdef";
        @memcpy(buff[4 .. 4 + source.len], source);
    }
}

test "@ptrCast undefined value at comptime" {
    const S = struct {
        fn transmute(comptime T: type, comptime U: type, value: T) U {
            return @as(*const U, @ptrCast(&value)).*;
        }
    };
    comptime {
        const x = S.transmute(u64, i32, undefined);
        _ = x;
    }
}

test "comptime @ptrCast with packed struct leaves value unmodified" {
    const S = packed struct { three: u3 };
    const st: S = .{ .three = 6 };
    try expect(st.three == 6);
    const p: *const [1]u3 = @ptrCast(&st);
    try expect(p.*[0] == 6);
    try expect(st.three == 6);
}

test "@ptrCast restructures comptime-only array" {
    {
        const a3a2: [3][2]comptime_int = .{
            .{ 1, 2 },
            .{ 3, 4 },
            .{ 5, 6 },
        };
        const a2a3: *const [2][3]comptime_int = @ptrCast(&a3a2);
        comptime assert(a2a3[0][0] == 1);
        comptime assert(a2a3[0][1] == 2);
        comptime assert(a2a3[0][2] == 3);
        comptime assert(a2a3[1][0] == 4);
        comptime assert(a2a3[1][1] == 5);
        comptime assert(a2a3[1][2] == 6);
    }

    {
        const a6a1: [6][1]comptime_int = .{
            .{1}, .{2}, .{3}, .{4}, .{5}, .{6},
        };
        const a1a2a3: *const [1][2][3]comptime_int = @ptrCast(&a6a1);
        comptime assert(a1a2a3[0][0][0] == 1);
        comptime assert(a1a2a3[0][0][1] == 2);
        comptime assert(a1a2a3[0][0][2] == 3);
        comptime assert(a1a2a3[0][1][0] == 4);
        comptime assert(a1a2a3[0][1][1] == 5);
        comptime assert(a1a2a3[0][1][2] == 6);
    }

    {
        const a1: [1]comptime_int = .{123};
        const raw: *const comptime_int = @ptrCast(&a1);
        comptime assert(raw.* == 123);
    }

    {
        const raw: comptime_int = 123;
        const a1: *const [1]comptime_int = @ptrCast(&raw);
        comptime assert(a1[0] == 123);
    }
}

test "@ptrCast restructures sliced comptime-only array" {
    const a3a2: [4][2]comptime_int = .{
        .{ 1, 2 },
        .{ 3, 4 },
        .{ 5, 6 },
        .{ 7, 8 },
    };

    const sub: *const [4]comptime_int = @ptrCast(a3a2[1..]);
    comptime assert(sub[0] == 3);
    comptime assert(sub[1] == 4);
    comptime assert(sub[2] == 5);
    comptime assert(sub[3] == 6);
}

test "@ptrCast slice multiplying length" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const S = struct {
        fn doTheTest(zero: u32) !void {
            const in: []const u32 = &.{ zero, zero };
            const out: []const u8 = @ptrCast(in);
            try expect(out.len == 8);
            try expect(@as([*]const u8, @ptrCast(in.ptr)) == out.ptr);
        }
    };
    try S.doTheTest(0);
    try comptime S.doTheTest(0);
}

test "@ptrCast array pointer to slice multiplying length" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const S = struct {
        fn doTheTest(zero: u32) !void {
            const in: *const [2]u32 = &.{ zero, zero };
            const out: []const u8 = @ptrCast(in);
            try expect(out.len == 8);
            try expect(out.ptr == @as([*]const u8, @ptrCast(in.ptr)));
        }
    };
    try S.doTheTest(0);
    try comptime S.doTheTest(0);
}

test "@ptrCast slice dividing length" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const S = struct {
        fn doTheTest(zero: u8) !void {
            const in: []const u8 = &.{ zero, zero, zero, zero, zero, zero, zero, zero };
            const out: []align(1) const u32 = @ptrCast(in);
            try expect(out.len == 2);
            try expect(out.ptr == @as([*]align(1) const u32, @ptrCast(in.ptr)));
        }
    };
    try S.doTheTest(0);
    try comptime S.doTheTest(0);
}

test "@ptrCast array pointer to slice dividing length" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const S = struct {
        fn doTheTest(zero: u8) !void {
            const in: *const [8]u8 = &.{ zero, zero, zero, zero, zero, zero, zero, zero };
            const out: []align(1) const u32 = @ptrCast(in);
            try expect(out.len == 2);
            try expect(out.ptr == @as([*]align(1) const u32, @ptrCast(in.ptr)));
        }
    };
    try S.doTheTest(0);
    try comptime S.doTheTest(0);
}

test "@ptrCast slice with complex length increase" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const TwoBytes = [2]u8;
    const ThreeBytes = [3]u8;

    const S = struct {
        fn doTheTest(zero: ThreeBytes) !void {
            const in: []const ThreeBytes = &.{ zero, zero };
            const out: []const TwoBytes = @ptrCast(in);
            try expect(out.len == 3);
            try expect(out.ptr == @as([*]const TwoBytes, @ptrCast(in.ptr)));
        }
    };
    try S.doTheTest(@splat(0));
    try comptime S.doTheTest(@splat(0));
}

test "@ptrCast array pointer to slice with complex length increase" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const TwoBytes = [2]u8;
    const ThreeBytes = [3]u8;

    const S = struct {
        fn doTheTest(zero: ThreeBytes) !void {
            const in: *const [2]ThreeBytes = &.{ zero, zero };
            const out: []const TwoBytes = @ptrCast(in);
            try expect(out.len == 3);
            try expect(out.ptr == @as([*]const TwoBytes, @ptrCast(in.ptr)));
        }
    };
    try S.doTheTest(@splat(0));
    try comptime S.doTheTest(@splat(0));
}

test "@ptrCast slice with complex length decrease" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const TwoBytes = [2]u8;
    const ThreeBytes = [3]u8;

    const S = struct {
        fn doTheTest(zero: TwoBytes) !void {
            const in: []const TwoBytes = &.{ zero, zero, zero };
            const out: []const ThreeBytes = @ptrCast(in);
            try expect(out.len == 2);
            try expect(out.ptr == @as([*]const ThreeBytes, @ptrCast(in.ptr)));
        }
    };
    try S.doTheTest(@splat(0));
    try comptime S.doTheTest(@splat(0));
}

test "@ptrCast array pointer to slice with complex length decrease" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const TwoBytes = [2]u8;
    const ThreeBytes = [3]u8;

    const S = struct {
        fn doTheTest(zero: TwoBytes) !void {
            const in: *const [3]TwoBytes = &.{ zero, zero, zero };
            const out: []const ThreeBytes = @ptrCast(in);
            try expect(out.len == 2);
            try expect(out.ptr == @as([*]const ThreeBytes, @ptrCast(in.ptr)));
        }
    };
    try S.doTheTest(@splat(0));
    try comptime S.doTheTest(@splat(0));
}

test "@ptrCast slice of zero-bit type to different slice" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const S = struct {
        fn doTheTest(comptime T: type, zero_bits: []const T) !void {
            const out: []const u8 = @ptrCast(zero_bits);
            try expect(out.len == 0);
        }
    };
    try S.doTheTest(void, &.{ {}, {}, {} });
    try S.doTheTest(u0, &.{ 0, 0, 0, 0 });
    try S.doTheTest(packed struct(u0) {}, &.{ .{}, .{} });
    try comptime S.doTheTest(void, &.{ {}, {}, {} });
    try comptime S.doTheTest(u0, &.{ 0, 0, 0, 0 });
    try comptime S.doTheTest(packed struct(u0) {}, &.{ .{}, .{} });
}

test "@ptrCast single-item pointer to slice with length 1" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO

    const S = struct {
        fn doTheTest(comptime T: type, ptr: *const T) !void {
            const slice: []const T = @ptrCast(ptr);
            try expect(slice.len == 1);
            try expect(&slice[0] == ptr);
        }
    };
    try S.doTheTest(u8, &123);
    try S.doTheTest(void, &{});
    try S.doTheTest(struct { x: u32 }, &.{ .x = 123 });
    try comptime S.doTheTest(u8, &123);
    try comptime S.doTheTest(void, &{});
    try comptime S.doTheTest(struct { x: u32 }, &.{ .x = 123 });
}

test "@ptrCast single-item pointer to slice of bytes" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO

    const S = struct {
        fn doTheTest(comptime T: type, ptr: *const T) !void {
            const slice: []const u8 = @ptrCast(ptr);
            try expect(slice.len == @sizeOf(T));
            try expect(slice.ptr == @as([*]const u8, @ptrCast(ptr)));
        }
    };
    try S.doTheTest(u16, &123);
    try S.doTheTest(void, &{});
    try S.doTheTest(struct { x: u32 }, &.{ .x = 123 });
    try comptime S.doTheTest(u16, &123);
    try comptime S.doTheTest(void, &{});
    try comptime S.doTheTest(struct { x: u32 }, &.{ .x = 123 });
}

test "@ptrCast array pointer removing sentinel" {
    const in: *const [4:0]u8 = &.{ 1, 2, 3, 4 };
    const out: []const i8 = @ptrCast(in);
    comptime assert(out.len == 4);
    comptime assert(out[0] == 1);
    comptime assert(out[1] == 2);
    comptime assert(out[2] == 3);
    comptime assert(out[3] == 4);
}
