const std = @import("std");
const TestContext = @import("../../src/test.zig").TestContext;

const linux_arm = std.zig.CrossTarget{
    .cpu_arch = .arm,
    .os_tag = .linux,
};

pub fn addCases(ctx: *TestContext) !void {
    {
        var case = ctx.exe("linux_arm hello world", linux_arm);
        // Hello world using _start and inline asm.
        case.addCompareOutput(
            \\pub export fn _start() noreturn {
            \\    print();
            \\    exit();
            \\}
            \\
            \\fn print() void {
            \\    asm volatile ("svc #0"
            \\        :
            \\        : [number] "{r7}" (4),
            \\          [arg1] "{r0}" (1),
            \\          [arg2] "{r1}" (@ptrToInt("Hello, World!\n")),
            \\          [arg3] "{r2}" (14)
            \\        : "memory"
            \\    );
            \\    return;
            \\}
            \\
            \\fn exit() noreturn {
            \\    asm volatile ("svc #0"
            \\        :
            \\        : [number] "{r7}" (1),
            \\          [arg1] "{r0}" (0)
            \\        : "memory"
            \\    );
            \\    unreachable;
            \\}
        ,
            "Hello, World!\n",
        );
    }

    {
        var case = ctx.exe("parameters and return values", linux_arm);
        // Testing simple parameters and return values
        //
        // TODO: The parameters to the asm statement in print() had to
        // be in a specific order because otherwise the write to r0
        // would overwrite the len parameter which resides in r0
        case.addCompareOutput(
            \\pub fn main() void {
            \\    print(id(14));
            \\}
            \\
            \\fn id(x: u32) u32 {
            \\    return x;
            \\}
            \\
            \\fn print(len: u32) void {
            \\    asm volatile ("svc #0"
            \\        :
            \\        : [number] "{r7}" (4),
            \\          [arg3] "{r2}" (len),
            \\          [arg1] "{r0}" (1),
            \\          [arg2] "{r1}" (@ptrToInt("Hello, World!\n"))
            \\        : "memory"
            \\    );
            \\    return;
            \\}
        ,
            "Hello, World!\n",
        );

        case.addCompareOutput(
            \\pub fn main() void {
            \\    assert(add(1, 2, 3, 4, 5, 6) == 21);
            \\}
            \\
            \\fn add(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) u32 {
            \\    return a + b + c + d + e + f;
            \\}
            \\
            \\pub fn assert(ok: bool) void {
            \\    if (!ok) unreachable; // assertion failure
            \\}
        ,
            "",
        );
    }

    {
        var case = ctx.exe("non-leaf functions", linux_arm);
        // Testing non-leaf functions
        case.addCompareOutput(
            \\pub fn main() void {
            \\    foo();
            \\}
            \\
            \\fn foo() void {
            \\    bar();
            \\}
            \\
            \\fn bar() void {}
        ,
            "",
        );
    }

    {
        var case = ctx.exe("arithmetic operations", linux_arm);

        // Add two numbers
        case.addCompareOutput(
            \\pub fn main() void {
            \\    print(2, 4);
            \\    print(1, 7);
            \\}
            \\
            \\fn print(a: u32, b: u32) void {
            \\    asm volatile ("svc #0"
            \\        :
            \\        : [number] "{r7}" (4),
            \\          [arg3] "{r2}" (a + b),
            \\          [arg1] "{r0}" (1),
            \\          [arg2] "{r1}" (@ptrToInt("123456789"))
            \\        : "memory"
            \\    );
            \\    return;
            \\}
        ,
            "12345612345678",
        );

        // Subtract two numbers
        case.addCompareOutput(
            \\pub fn main() void {
            \\    print(10, 5);
            \\    print(4, 3);
            \\}
            \\
            \\fn print(a: u32, b: u32) void {
            \\    asm volatile ("svc #0"
            \\        :
            \\        : [number] "{r7}" (4),
            \\          [arg3] "{r2}" (a - b),
            \\          [arg1] "{r0}" (1),
            \\          [arg2] "{r1}" (@ptrToInt("123456789"))
            \\        : "memory"
            \\    );
            \\    return;
            \\}
        ,
            "123451",
        );

        // Bitwise And
        case.addCompareOutput(
            \\pub fn main() void {
            \\    print(8, 9);
            \\    print(3, 7);
            \\}
            \\
            \\fn print(a: u32, b: u32) void {
            \\    asm volatile ("svc #0"
            \\        :
            \\        : [number] "{r7}" (4),
            \\          [arg3] "{r2}" (a & b),
            \\          [arg1] "{r0}" (1),
            \\          [arg2] "{r1}" (@ptrToInt("123456789"))
            \\        : "memory"
            \\    );
            \\    return;
            \\}
        ,
            "12345678123",
        );

        // Bitwise Or
        case.addCompareOutput(
            \\pub fn main() void {
            \\    print(4, 2);
            \\    print(3, 7);
            \\}
            \\
            \\fn print(a: u32, b: u32) void {
            \\    asm volatile ("svc #0"
            \\        :
            \\        : [number] "{r7}" (4),
            \\          [arg3] "{r2}" (a | b),
            \\          [arg1] "{r0}" (1),
            \\          [arg2] "{r1}" (@ptrToInt("123456789"))
            \\        : "memory"
            \\    );
            \\    return;
            \\}
        ,
            "1234561234567",
        );

        // Bitwise Xor
        case.addCompareOutput(
            \\pub fn main() void {
            \\    print(42, 42);
            \\    print(3, 5);
            \\}
            \\
            \\fn print(a: u32, b: u32) void {
            \\    asm volatile ("svc #0"
            \\        :
            \\        : [number] "{r7}" (4),
            \\          [arg3] "{r2}" (a ^ b),
            \\          [arg1] "{r0}" (1),
            \\          [arg2] "{r1}" (@ptrToInt("123456789"))
            \\        : "memory"
            \\    );
            \\    return;
            \\}
        ,
            "123456",
        );

        // Bit Shift Left
        case.addCompareOutput(
            \\pub fn main() void {
            \\    var x: u32 = 1;
            \\    assert(x << 1 == 2);
            \\
            \\    x <<= 1;
            \\    assert(x << 2 == 8);
            \\    assert(x << 3 == 16);
            \\}
            \\
            \\pub fn assert(ok: bool) void {
            \\    if (!ok) unreachable; // assertion failure
            \\}
        ,
            "",
        );

        // Bit Shift Right
        case.addCompareOutput(
            \\pub fn main() void {
            \\    var a: u32 = 1024;
            \\    assert(a >> 1 == 512);
            \\
            \\    a >>= 1;
            \\    assert(a >> 2 == 128);
            \\    assert(a >> 3 == 64);
            \\    assert(a >> 4 == 32);
            \\    assert(a >> 5 == 16);
            \\    assert(a >> 6 == 8);
            \\    assert(a >> 7 == 4);
            \\    assert(a >> 8 == 2);
            \\    assert(a >> 9 == 1);
            \\}
            \\
            \\pub fn assert(ok: bool) void {
            \\    if (!ok) unreachable; // assertion failure
            \\}
        ,
            "",
        );
    }

    {
        var case = ctx.exe("if statements", linux_arm);
        // Simple if statement in assert
        case.addCompareOutput(
            \\pub fn main() void {
            \\    var x: u32 = 123;
            \\    var y: u32 = 42;
            \\    assert(x > y);
            \\}
            \\
            \\fn assert(ok: bool) void {
            \\    if (!ok) unreachable;
            \\}
        ,
            "",
        );
    }

    {
        var case = ctx.exe("while loops", linux_arm);
        // Simple while loop with assert
        case.addCompareOutput(
            \\pub fn main() void {
            \\    var x: u32 = 2020;
            \\    var i: u32 = 0;
            \\    while (x > 0) {
            \\        x -= 2;
            \\        i += 1;
            \\    }
            \\    assert(i == 1010);
            \\}
            \\
            \\fn assert(ok: bool) void {
            \\    if (!ok) unreachable;
            \\}
        ,
            "",
        );
    }

    {
        var case = ctx.exe("integer multiplication", linux_arm);
        // Simple u32 integer multiplication
        case.addCompareOutput(
            \\pub fn main() void {
            \\    assert(mul(1, 1) == 1);
            \\    assert(mul(42, 1) == 42);
            \\    assert(mul(1, 42) == 42);
            \\    assert(mul(123, 42) == 5166);
            \\}
            \\
            \\fn mul(x: u32, y: u32) u32 {
            \\    return x * y;
            \\}
            \\
            \\fn assert(ok: bool) void {
            \\    if (!ok) unreachable;
            \\}
        ,
            "",
        );
    }

    {
        var case = ctx.exe("save function return values in callee preserved register", linux_arm);
        // Here, it is necessary to save the result of bar() into a
        // callee preserved register, otherwise it will be overwritten
        // by the first parameter to baz.
        case.addCompareOutput(
            \\pub fn main() void {
            \\    assert(foo() == 43);
            \\}
            \\
            \\fn foo() u32 {
            \\    return bar() + baz(42);
            \\}
            \\
            \\fn bar() u32 {
            \\    return 1;
            \\}
            \\
            \\fn baz(x: u32) u32 {
            \\    return x;
            \\}
            \\
            \\fn assert(ok: bool) void {
            \\    if (!ok) unreachable;
            \\}
        ,
            "",
        );
    }

    {
        var case = ctx.exe("enums", linux_arm);
        case.addCompareOutput(
            \\const Number = enum { one, two, three };
            \\
            \\pub fn main() void {
            \\    var x: Number = .one;
            \\    var y = Number.two;
            \\    var z = @intToEnum(Number, 2);
            \\    assert(@enumToInt(x) == 0);
            \\    assert(@enumToInt(y) == 1);
            \\    assert(@enumToInt(z) == 2);
            \\}
            \\
            \\fn assert(ok: bool) void {
            \\    if (!ok) unreachable; // assertion failure
            \\}
        ,
            "",
        );

        case.addCompareOutput(
            \\const Number = enum { one, two, three };
            \\
            \\pub fn main() void {
            \\    var x: Number = .one;
            \\    var y = Number.two;
            \\    assert(@enumToInt(x) < @enumToInt(y));
            \\}
            \\
            \\fn assert(ok: bool) void {
            \\    if (!ok) unreachable; // assertion failure
            \\}
        ,
            "",
        );
    }

    {
        var case = ctx.exe("recursive fibonacci", linux_arm);
        case.addCompareOutput(
            \\pub fn main() void {
            \\    assert(fib(0) == 0);
            \\    assert(fib(1) == 1);
            \\    assert(fib(2) == 1);
            \\    assert(fib(3) == 2);
            \\    assert(fib(10) == 55);
            \\    assert(fib(20) == 6765);
            \\}
            \\
            \\fn fib(n: u32) u32 {
            \\    if (n < 2) {
            \\        return n;
            \\    } else {
            \\        return fib(n - 2) + fib(n - 1);
            \\    }
            \\}
            \\
            \\fn assert(ok: bool) void {
            \\    if (!ok) unreachable;
            \\}
        ,
            "",
        );
    }

    {
        var case = ctx.exe("spilling registers", linux_arm);
        case.addCompareOutput(
            \\pub fn main() void {
            \\    assert(add(3, 4) == 791);
            \\}
            \\
            \\fn add(a: u32, b: u32) u32 {
            \\    const x: u32 = blk: {
            \\        const c = a + b; // 7
            \\        const d = a + c; // 10
            \\        const e = d + b; // 14
            \\        const f = d + e; // 24
            \\        const g = e + f; // 38
            \\        const h = f + g; // 62
            \\        const i = g + h; // 100
            \\        const j = i + d; // 110
            \\        const k = i + j; // 210
            \\        const l = k + c; // 217
            \\        const m = l + d; // 227
            \\        const n = m + e; // 241
            \\        const o = n + f; // 265
            \\        const p = o + g; // 303
            \\        const q = p + h; // 365
            \\        const r = q + i; // 465
            \\        const s = r + j; // 575
            \\        const t = s + k; // 785
            \\        break :blk t;
            \\    };
            \\    const y = x + a; // 788
            \\    const z = y + a; // 791
            \\    return z;
            \\}
            \\
            \\fn assert(ok: bool) void {
            \\    if (!ok) unreachable;
            \\}
        ,
            "",
        );

        case.addCompareOutput(
            \\pub fn main() void {
            \\    assert(addMul(3, 4) == 357747496);
            \\}
            \\
            \\fn addMul(a: u32, b: u32) u32 {
            \\    const x: u32 = blk: {
            \\        const c = a + b; // 7
            \\        const d = a + c; // 10
            \\        const e = d + b; // 14
            \\        const f = d + e; // 24
            \\        const g = e + f; // 38
            \\        const h = f + g; // 62
            \\        const i = g + h; // 100
            \\        const j = i + d; // 110
            \\        const k = i + j; // 210
            \\        const l = k + c; // 217
            \\        const m = l * d; // 2170
            \\        const n = m + e; // 2184
            \\        const o = n * f; // 52416
            \\        const p = o + g; // 52454
            \\        const q = p * h; // 3252148
            \\        const r = q + i; // 3252248
            \\        const s = r * j; // 357747280
            \\        const t = s + k; // 357747490
            \\        break :blk t;
            \\    };
            \\    const y = x + a; // 357747493
            \\    const z = y + a; // 357747496
            \\    return z;
            \\}
            \\
            \\fn assert(ok: bool) void {
            \\    if (!ok) unreachable;
            \\}
        ,
            "",
        );
    }

    {
        var case = ctx.exe("print u32s", linux_arm);
        case.addCompareOutput(
            \\pub fn main() void {
            \\    printNumberHex(0x00000000);
            \\    printNumberHex(0xaaaaaaaa);
            \\    printNumberHex(0xdeadbeef);
            \\    printNumberHex(0x31415926);
            \\}
            \\
            \\fn printNumberHex(x: u32) void {
            \\    var i: u5 = 28;
            \\    while (true) : (i -= 4) {
            \\        const digit = (x >> i) & 0xf;
            \\        asm volatile ("svc #0"
            \\            :
            \\            : [number] "{r7}" (4),
            \\              [arg1] "{r0}" (1),
            \\              [arg2] "{r1}" (@ptrToInt("0123456789abcdef") + digit),
            \\              [arg3] "{r2}" (1)
            \\            : "memory"
            \\        );
            \\
            \\        if (i == 0) break;
            \\    }
            \\    asm volatile ("svc #0"
            \\        :
            \\        : [number] "{r7}" (4),
            \\          [arg1] "{r0}" (1),
            \\          [arg2] "{r1}" (@ptrToInt("\n")),
            \\          [arg3] "{r2}" (1)
            \\        : "memory"
            \\    );
            \\}
        ,
            \\00000000
            \\aaaaaaaa
            \\deadbeef
            \\31415926
            \\
            ,
        );
    }

    {
        var case = ctx.exe("save compare flags", linux_arm);
        case.addCompareOutput(
            \\pub fn main() void {
            \\    foo(2, 1);
            \\}
            \\
            \\fn foo(x: u32, y: u32) void {
            \\    const b = x > y;
            \\    assert(b);
            \\    assert(b);
            \\}
            \\
            \\pub fn assert(ok: bool) void {
            \\    if (!ok) unreachable; // assertion failure
            \\}
        ,
            "",
        );
    }

    {
        var case = ctx.exe("optionals", linux_arm);
        case.addCompareOutput(
            \\var x: u32 = 42;
            \\
            \\pub fn main() void {
            \\    var p: ?*u32 = null;
            \\    assert(p == null);
            \\    p = &x;
            \\    assert(p != null);
            \\}
            \\
            \\fn assert(ok: bool) void {
            \\    if (!ok) unreachable;
            \\}
        ,
            "",
        );
    }

    {
        var case = ctx.exe("errors", linux_arm);
        case.addCompareOutput(
            \\pub fn main() void {
            \\    foo() catch print();
            \\}
            \\
            \\fn foo() anyerror!void {}
            \\
            \\fn print() void {
            \\    asm volatile ("svc #0"
            \\        :
            \\        : [number] "{r7}" (4),
            \\          [arg1] "{r0}" (1),
            \\          [arg2] "{r1}" (@ptrToInt("Hello, World!\n")),
            \\          [arg3] "{r2}" ("Hello, World!\n".len),
            \\        : "memory"
            \\    );
            \\    return;
            \\}
        ,
            "",
        );

        case.addCompareOutput(
            \\pub fn main() void {
            \\    foo() catch print();
            \\}
            \\
            \\fn foo() anyerror!void {
            \\    return error.Test;
            \\}
            \\
            \\fn print() void {
            \\    asm volatile ("svc #0"
            \\        :
            \\        : [number] "{r7}" (4),
            \\          [arg1] "{r0}" (1),
            \\          [arg2] "{r1}" (@ptrToInt("Hello, World!\n")),
            \\          [arg3] "{r2}" ("Hello, World!\n".len),
            \\        : "memory"
            \\    );
            \\    return;
            \\}
        ,
            "Hello, World!\n",
        );

        case.addCompareOutput(
            \\pub fn main() void {
            \\    foo() catch |err| {
            \\        assert(err == error.Foo);
            \\        assert(err != error.Bar);
            \\        assert(err != error.Baz);
            \\    };
            \\    bar() catch |err| {
            \\        assert(err != error.Foo);
            \\        assert(err == error.Bar);
            \\        assert(err != error.Baz);
            \\    };
            \\}
            \\
            \\fn assert(ok: bool) void {
            \\    if (!ok) unreachable;
            \\}
            \\
            \\fn foo() anyerror!void {
            \\    return error.Foo;
            \\}
            \\
            \\fn bar() anyerror!void {
            \\    return error.Bar;
            \\}
        ,
            "",
        );

        case.addCompareOutput(
            \\pub fn main() void {
            \\    foo() catch unreachable;
            \\}
            \\
            \\fn foo() anyerror!void {
            \\    try bar();
            \\}
            \\
            \\fn bar() anyerror!void {}
        ,
            "",
        );
    }

    {
        var case = ctx.exe("slices", linux_arm);
        case.addCompareOutput(
            \\var array = [_]u32{ 0, 42, 123, 69 };
            \\var s: []const u32 = &array;
            \\
            \\pub fn main() void {
            \\    assert(s[0] == 0);
            \\    assert(s[1] == 42);
            \\    assert(s[2] == 123);
            \\    assert(s[3] == 69);
            \\}
            \\
            \\fn assert(ok: bool) void {
            \\    if (!ok) unreachable;
            \\}
        ,
            "",
        );
    }

    {
        var case = ctx.exe("structs", linux_arm);
        case.addCompareOutput(
            \\var array = [_]SomeStruct{
            \\    .{ .a = 0, .b = 42, .c = 69 },
            \\    .{ .a = 1, .b = 2, .c = 3 },
            \\    .{ .a = 123, .b = 456, .c = 789 },
            \\};
            \\var s: []const SomeStruct = &array;
            \\
            \\var some_struct: SomeStruct = .{
            \\    .a = 0,
            \\    .b = 42,
            \\    .c = 69,
            \\};
            \\
            \\const SomeStruct = struct {
            \\    a: u32,
            \\    b: u32,
            \\    c: u32,
            \\};
            \\
            \\pub fn main() void {
            \\    assert(some_struct.a == 0);
            \\    assert(some_struct.b == 42);
            \\    assert(some_struct.c == 69);
            \\
            \\    assert(s[0].a == 0);
            \\    assert(s[0].b == 42);
            \\    assert(s[0].c == 69);
            \\    assert(s[1].a == 1);
            \\    assert(s[1].b == 2);
            \\    assert(s[1].c == 3);
            \\    assert(s[2].a == 123);
            \\    assert(s[2].b == 456);
            \\    assert(s[2].c == 789);
            \\}
            \\
            \\fn assert(ok: bool) void {
            \\    if (!ok) unreachable;
            \\}
        ,
            "",
        );
    }

    {
        var case = ctx.exe("function pointers", linux_arm);
        case.addCompareOutput(
            \\const PrintFn = *const fn () void;
            \\
            \\pub fn main() void {
            \\    var printFn: PrintFn = stopSayingThat;
            \\    var i: u32 = 0;
            \\    while (i < 4) : (i += 1) printFn();
            \\
            \\    printFn = moveEveryZig;
            \\    printFn();
            \\}
            \\
            \\fn stopSayingThat() void {
            \\    asm volatile ("svc #0"
            \\        :
            \\        : [number] "{r7}" (4),
            \\          [arg1] "{r0}" (1),
            \\          [arg2] "{r1}" (@ptrToInt("Hello, my name is Inigo Montoya; you killed my father, prepare to die.\n")),
            \\          [arg3] "{r2}" ("Hello, my name is Inigo Montoya; you killed my father, prepare to die.\n".len),
            \\        : "memory"
            \\    );
            \\    return;
            \\}
            \\
            \\fn moveEveryZig() void {
            \\    asm volatile ("svc #0"
            \\        :
            \\        : [number] "{r7}" (4),
            \\          [arg1] "{r0}" (1),
            \\          [arg2] "{r1}" (@ptrToInt("All your codebase are belong to us\n")),
            \\          [arg3] "{r2}" ("All your codebase are belong to us\n".len),
            \\        : "memory"
            \\    );
            \\    return;
            \\}
        ,
            \\Hello, my name is Inigo Montoya; you killed my father, prepare to die.
            \\Hello, my name is Inigo Montoya; you killed my father, prepare to die.
            \\Hello, my name is Inigo Montoya; you killed my father, prepare to die.
            \\Hello, my name is Inigo Montoya; you killed my father, prepare to die.
            \\All your codebase are belong to us
            \\
            ,
        );
    }
}
