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

test "super basic invocations" {
    const foo = struct {
        fn foo() i32 {
            return 1234;
        }
    }.foo;
    try expect(@call(.auto, foo, .{}) == 1234);
    comptime assert(@call(.always_inline, foo, .{}) == 1234);
    {
        // comptime call without comptime keyword
        const result = @call(.compile_time, foo, .{}) == 1234;
        comptime assert(result);
    }
}

test "basic invocations" {
    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
    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_c and builtin.os.tag == .windows) return error.SkipZigTest; // MSVC doesn't support tail call modifiers

    const foo = struct {
        fn foo(_: i32) i32 {
            return 1234;
        }
    }.foo;
    try expect(@call(.auto, foo, .{1}) == 1234);
    comptime {
        // comptime calls with supported modifiers
        try expect(@call(.auto, foo, .{2}) == 1234);
        try expect(@call(.no_suspend, foo, .{3}) == 1234);
        try expect(@call(.always_tail, foo, .{4}) == 1234);
        try expect(@call(.always_inline, foo, .{5}) == 1234);
    }
    // comptime call without comptime keyword
    const result = @call(.compile_time, foo, .{6}) == 1234;
    comptime assert(result);
    // runtime calls of comptime-known function
    try expect(@call(.no_suspend, foo, .{7}) == 1234);
    try expect(@call(.never_tail, foo, .{8}) == 1234);
    try expect(@call(.never_inline, foo, .{9}) == 1234);
    // CBE does not support attributes on runtime functions
    if (builtin.zig_backend != .stage2_c) {
        // runtime calls of non comptime-known function
        var alias_foo = &foo;
        _ = &alias_foo;
        try expect(@call(.no_suspend, alias_foo, .{10}) == 1234);
        try expect(@call(.never_tail, alias_foo, .{11}) == 1234);
        try expect(@call(.never_inline, alias_foo, .{12}) == 1234);
    }
}

test "tuple parameters" {
    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 add = struct {
        fn add(a: i32, b: i32) i32 {
            return a + b;
        }
    }.add;
    var a: i32 = 12;
    var b: i32 = 34;
    _ = .{ &a, &b };
    try expect(@call(.auto, add, .{ a, 34 }) == 46);
    try expect(@call(.auto, add, .{ 12, b }) == 46);
    try expect(@call(.auto, add, .{ a, b }) == 46);
    try expect(@call(.auto, add, .{ 12, 34 }) == 46);
    if (false) {
        comptime assert(@call(.auto, add, .{ 12, 34 }) == 46); // TODO
    }
    try expect(comptime @call(.auto, add, .{ 12, 34 }) == 46);
    {
        const separate_args0 = .{ a, b };
        const separate_args1 = .{ a, 34 };
        const separate_args2 = .{ 12, 34 };
        const separate_args3 = .{ 12, b };
        try expect(@call(.always_inline, add, separate_args0) == 46);
        try expect(@call(.always_inline, add, separate_args1) == 46);
        try expect(@call(.always_inline, add, separate_args2) == 46);
        try expect(@call(.always_inline, add, separate_args3) == 46);
    }
}

test "result location of function call argument through runtime condition and struct init" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    const E = enum { a, b };
    const S = struct {
        e: E,
    };
    const namespace = struct {
        fn foo(s: S) !void {
            try expect(s.e == .b);
        }
    };
    var runtime = true;
    _ = &runtime;
    try namespace.foo(.{
        .e = if (!runtime) .a else .b,
    });
}

test "function call with 40 arguments" {
    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;

    const S = struct {
        fn doTheTest(thirty_nine: i32) !void {
            const result = add(
                0,
                1,
                2,
                3,
                4,
                5,
                6,
                7,
                8,
                9,
                10,
                11,
                12,
                13,
                14,
                15,
                16,
                17,
                18,
                19,
                20,
                21,
                22,
                23,
                24,
                25,
                26,
                27,
                28,
                29,
                30,
                31,
                32,
                33,
                34,
                35,
                36,
                37,
                38,
                thirty_nine,
                40,
            );
            try expect(result == 820);
            try expect(thirty_nine == 39);
        }

        fn add(
            a0: i32,
            a1: i32,
            a2: i32,
            a3: i32,
            a4: i32,
            a5: i32,
            a6: i32,
            a7: i32,
            a8: i32,
            a9: i32,
            a10: i32,
            a11: i32,
            a12: i32,
            a13: i32,
            a14: i32,
            a15: i32,
            a16: i32,
            a17: i32,
            a18: i32,
            a19: i32,
            a20: i32,
            a21: i32,
            a22: i32,
            a23: i32,
            a24: i32,
            a25: i32,
            a26: i32,
            a27: i32,
            a28: i32,
            a29: i32,
            a30: i32,
            a31: i32,
            a32: i32,
            a33: i32,
            a34: i32,
            a35: i32,
            a36: i32,
            a37: i32,
            a38: i32,
            a39: i32,
            a40: i32,
        ) i32 {
            return a0 +
                a1 +
                a2 +
                a3 +
                a4 +
                a5 +
                a6 +
                a7 +
                a8 +
                a9 +
                a10 +
                a11 +
                a12 +
                a13 +
                a14 +
                a15 +
                a16 +
                a17 +
                a18 +
                a19 +
                a20 +
                a21 +
                a22 +
                a23 +
                a24 +
                a25 +
                a26 +
                a27 +
                a28 +
                a29 +
                a30 +
                a31 +
                a32 +
                a33 +
                a34 +
                a35 +
                a36 +
                a37 +
                a38 +
                a39 +
                a40;
        }
    };
    try S.doTheTest(39);
}

test "arguments to comptime parameters generated in comptime blocks" {
    const S = struct {
        fn fortyTwo() i32 {
            return 42;
        }

        fn foo(comptime x: i32) void {
            if (x != 42) @compileError("bad");
        }
    };
    S.foo(S.fortyTwo());
}

test "forced tail call" {
    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_x86_64) 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_riscv64) return error.SkipZigTest;

    if (builtin.zig_backend == .stage2_llvm) {
        if (builtin.cpu.arch.isMIPS() or builtin.cpu.arch.isPowerPC() or builtin.cpu.arch.isWasm()) {
            return error.SkipZigTest;
        }
    }

    if (builtin.zig_backend == .stage2_c and builtin.os.tag == .windows) return error.SkipZigTest; // MSVC doesn't support always tail calls

    const S = struct {
        fn fibonacciTailInternal(n: u16, a: u16, b: u16) u16 {
            if (n == 0) return a;
            if (n == 1) return b;
            return @call(
                .always_tail,
                fibonacciTailInternal,
                .{ n - 1, b, a + b },
            );
        }

        fn fibonacciTail(n: u16) u16 {
            return fibonacciTailInternal(n, 0, 1);
        }
    };
    try expect(S.fibonacciTail(10) == 55);
}

test "inline call preserves tail call" {
    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_x86_64) 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_riscv64) return error.SkipZigTest;

    if (builtin.zig_backend == .stage2_llvm) {
        if (builtin.cpu.arch.isMIPS() or builtin.cpu.arch.isPowerPC() or builtin.cpu.arch.isWasm()) {
            return error.SkipZigTest;
        }
    }

    if (builtin.zig_backend == .stage2_c and builtin.os.tag == .windows) return error.SkipZigTest; // MSVC doesn't support always tail calls

    const max = std.math.maxInt(u16);
    const S = struct {
        var a: u16 = 0;
        fn foo() void {
            return bar();
        }

        inline fn bar() void {
            if (a == max) return;
            // Stack overflow if not tail called
            var buf: [max]u16 = undefined;
            buf[a] = a;
            a += 1;
            return @call(.always_tail, foo, .{});
        }
    };
    S.foo();
    try expect(S.a == std.math.maxInt(u16));
}

test "inline call doesn't re-evaluate non generic struct" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const S = struct {
        fn foo(f: struct { a: u8, b: u8 }) !void {
            try expect(f.a == 123);
            try expect(f.b == 45);
        }
    };
    const ArgTuple = std.meta.ArgsTuple(@TypeOf(S.foo));
    try @call(.always_inline, S.foo, ArgTuple{.{ .a = 123, .b = 45 }});
    try comptime @call(.always_inline, S.foo, ArgTuple{.{ .a = 123, .b = 45 }});
}

test "Enum constructed by @Type passed as generic argument" {
    const S = struct {
        const E = std.meta.FieldEnum(struct {
            prev_pos: bool,
            pos: bool,
            vel: bool,
            damp_vel: bool,
            acc: bool,
            rgba: bool,
            prev_scale: bool,
            scale: bool,
            prev_rotation: bool,
            rotation: bool,
            angular_vel: bool,
            alive: bool,
        });
        fn foo(comptime a: E, b: u32) !void {
            try expect(@intFromEnum(a) == b);
        }
    };
    inline for (@typeInfo(S.E).@"enum".fields, 0..) |_, i| {
        try S.foo(@as(S.E, @enumFromInt(i)), i);
    }
}

test "generic function with generic function parameter" {
    const S = struct {
        fn f(comptime a: fn (anytype) anyerror!void, b: anytype) anyerror!void {
            try a(b);
        }
        fn g(a: anytype) anyerror!void {
            try expect(a == 123);
        }
    };
    try S.f(S.g, 123);
}

test "recursive inline call with comptime known argument" {
    const S = struct {
        inline fn foo(x: i32) i32 {
            if (x <= 0) {
                return 0;
            } else {
                return x * 2 + foo(x - 1);
            }
        }
    };

    try expect(S.foo(4) == 20);
}

test "inline while with @call" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO

    const S = struct {
        fn inc(a: *u32) void {
            a.* += 1;
        }
    };
    var a: u32 = 0;
    comptime var i = 0;
    inline while (i < 10) : (i += 1) {
        @call(.auto, S.inc, .{&a});
    }
    try expect(a == 10);
}

test "method call as parameter type" {
    const S = struct {
        fn foo(x: anytype, y: @TypeOf(x).Inner()) @TypeOf(y) {
            return y;
        }
        fn Inner() type {
            return u64;
        }
    };
    try expectEqual(@as(u64, 123), S.foo(S{}, 123));
    try expectEqual(@as(u64, 500), S.foo(S{}, 500));
}

test "non-anytype generic parameters provide result type" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    const S = struct {
        fn f(comptime T: type, y: T) !void {
            try expectEqual(@as(T, 123), y);
        }

        fn g(x: anytype, y: @TypeOf(x)) !void {
            try expectEqual(@as(@TypeOf(x), 0x222), y);
        }
    };

    var rt_u16: u16 = 123;
    var rt_u32: u32 = 0x10000222;
    _ = .{ &rt_u16, &rt_u32 };

    try S.f(u8, @intCast(rt_u16));
    try S.f(u8, @intCast(123));

    try S.g(rt_u16, @truncate(rt_u32));
    try S.g(rt_u16, @truncate(0x10000222));

    try comptime S.f(u8, @intCast(123));
    try comptime S.g(@as(u16, undefined), @truncate(0x99990222));
}

test "argument to generic function has correct result type" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    const S = struct {
        fn foo(_: anytype, e: enum { a, b }) bool {
            return e == .b;
        }

        fn doTheTest() !void {
            var t = true;
            _ = &t;

            // Since the enum literal passes through a runtime conditional here, these can only
            // compile if RLS provides the correct result type to the argument
            try expect(foo({}, if (!t) .a else .b));
            try expect(!foo("dummy", if (t) .a else .b));
            try expect(foo({}, if (t) .b else .a));
            try expect(!foo(123, if (t) .a else .a));
            try expect(foo(123, if (t) .b else .b));
        }
    };

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

test "call inline fn through pointer" {
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;

    const S = struct {
        inline fn foo(x: u8) !void {
            try expect(x == 123);
        }
    };
    const f = &S.foo;
    try f(123);
}

test "call function in comptime field" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO

    const S = struct {
        comptime capacity: fn () u64 = capacity_,
        fn capacity_() u64 {
            return 64;
        }
    };
    try std.testing.expect((S{}).capacity() == 64);
}

test "call function pointer in comptime field" {
    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 Auto = struct {
        auto: [max_len]u8 = undefined,
        offset: u64 = 0,

        comptime capacityFn: *const fn () u64 = capacity,

        const max_len: u64 = 32;

        fn capacity() u64 {
            return max_len;
        }
    };

    const a: Auto = .{ .offset = 16, .capacityFn = Auto.capacity };
    try std.testing.expect(a.capacityFn() == 32);
    try std.testing.expect((a.capacityFn)() == 32);
}

test "generic function pointer can be called" {
    const S = struct {
        var ok = false;
        fn foo(x: anytype) void {
            ok = x;
        }
    };
    const x = &S.foo;
    x(true);
    try expect(S.ok);
}

test "value returned from comptime function is comptime known" {
    const S = struct {
        fn fields(comptime T: type) switch (@typeInfo(T)) {
            .@"struct" => []const std.builtin.Type.StructField,
            else => unreachable,
        } {
            return switch (@typeInfo(T)) {
                .@"struct" => |info| info.fields,
                else => unreachable,
            };
        }
    };
    const fields_list = S.fields(@TypeOf(.{}));
    if (fields_list.len != 0)
        @compileError("Argument count mismatch");
}

test "registers get overwritten when ignoring return" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
    if (builtin.cpu.arch != .x86_64 or builtin.os.tag != .linux) return error.SkipZigTest;

    const S = struct {
        fn open() usize {
            return 42;
        }
        fn write(fd: usize, a: [*]const u8, len: usize) usize {
            return syscall4(.WRITE, fd, @intFromPtr(a), len);
        }
        fn syscall4(_: enum { WRITE }, _: usize, _: usize, _: usize) usize {
            return 23;
        }
        fn close(fd: usize) usize {
            if (fd != 42)
                unreachable;
            return 0;
        }
    };

    const fd = S.open();
    _ = S.write(fd, "a", 1);
    _ = S.close(fd);
}

test "call with union with zero sized field is not memorized incorrectly" {
    const U = union(enum) {
        T: type,
        N: void,
        fn S(comptime query: @This()) type {
            return struct {
                fn tag() type {
                    return query.T;
                }
            };
        }
    };
    const s1 = U.S(U{ .T = u32 }).tag();
    try std.testing.expectEqual(u32, s1);

    const s2 = U.S(U{ .T = u64 }).tag();
    try std.testing.expectEqual(u64, s2);
}

test "function call with cast to 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;

    const Foo = struct {
        y: u8,
        var foo: @This() = undefined;
        const t = &foo;

        fn bar(pointer: ?*anyopaque) void {
            _ = pointer;
        }
    };
    Foo.bar(Foo.t);
}

test "arguments pointed to on stack into tailcall" {
    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_c and builtin.os.tag == .windows) return error.SkipZigTest; // MSVC doesn't support always tail calls

    switch (builtin.cpu.arch) {
        .wasm32,
        .wasm64,
        .mips,
        .mipsel,
        .mips64,
        .mips64el,
        .powerpc,
        .powerpcle,
        .powerpc64,
        .powerpc64le,
        => return error.SkipZigTest,
        else => {},
    }

    const S = struct {
        var base: usize = undefined;
        var result_off: [7]usize = undefined;
        var result_len: [7]usize = undefined;
        var result_index: usize = 0;

        noinline fn insertionSort(data: []u64) void {
            result_off[result_index] = @intFromPtr(data.ptr) - base;
            result_len[result_index] = data.len;
            result_index += 1;
            if (data.len > 1) {
                var least_i: usize = 0;
                var i: usize = 1;
                while (i < data.len) : (i += 1) {
                    if (data[i] < data[least_i])
                        least_i = i;
                }
                std.mem.swap(u64, &data[0], &data[least_i]);

                // there used to be a bug where
                // `data[1..]` is created on the stack
                // and pointed to by the first argument register
                // then stack is invalidated by the tailcall and
                // overwritten by callee
                // https://github.com/ziglang/zig/issues/9703
                return @call(.always_tail, insertionSort, .{data[1..]});
            }
        }
    };

    var data = [_]u64{ 1, 6, 2, 7, 1, 9, 3 };
    S.base = @intFromPtr(&data);
    S.insertionSort(data[0..]);
    try expect(S.result_len[0] == 7);
    try expect(S.result_len[1] == 6);
    try expect(S.result_len[2] == 5);
    try expect(S.result_len[3] == 4);
    try expect(S.result_len[4] == 3);
    try expect(S.result_len[5] == 2);
    try expect(S.result_len[6] == 1);

    try expect(S.result_off[0] == 0);
    try expect(S.result_off[1] == 8);
    try expect(S.result_off[2] == 16);
    try expect(S.result_off[3] == 24);
    try expect(S.result_off[4] == 32);
    try expect(S.result_off[5] == 40);
    try expect(S.result_off[6] == 48);
}

test "tail call function pointer" {
    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_x86_64) 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_riscv64) return error.SkipZigTest;

    if (builtin.zig_backend == .stage2_llvm) {
        if (builtin.cpu.arch.isMIPS() or builtin.cpu.arch.isPowerPC() or builtin.cpu.arch.isWasm()) {
            return error.SkipZigTest;
        }
    }

    if (builtin.zig_backend == .stage2_c and builtin.os.tag == .windows) return error.SkipZigTest; // MSVC doesn't support always tail calls

    const S = struct {
        fn foo(n: u8) void {
            if (n == 0) return;
            const other: *const fn (u8) void = &bar;
            return @call(.always_tail, other, .{n - 1});
        }
        fn bar(n: u8) void {
            var other: *const fn (u8) void = undefined;
            other = &foo; // runtime-known pointer
            return @call(.always_tail, other, .{n});
        }
    };

    S.foo(100);
}
