const std = @import("std");
const os = std.os;
const tests = @import("tests.zig");

pub fn addCases(cases: *tests.CompareOutputContext) void {
    cases.addC("hello world with libc",
        \\const c = @cImport({
        \\    // See https://github.com/ziglang/zig/issues/515
        \\    @cDefine("_NO_CRT_STDIO_INLINE", "1");
        \\    @cInclude("stdio.h");
        \\});
        \\pub export fn main(argc: c_int, argv: [*][*]u8) c_int {
        \\    _ = argc;
        \\    _ = argv;
        \\    _ = c.puts("Hello, world!");
        \\    return 0;
        \\}
    , "Hello, world!" ++ if (@import("builtin").os.tag == .windows) "\r\n" else "\n");

    cases.add("hello world without libc",
        \\const io = @import("std").io;
        \\
        \\pub fn main() void {
        \\    const stdout = io.getStdOut().writer();
        \\    stdout.print("Hello, world!\n{d:4} {x:3} {c}\n", .{@as(u32, 12), @as(u16, 0x12), @as(u8, 'a')}) catch unreachable;
        \\}
    , "Hello, world!\n  12  12 a\n");

    cases.addC("number literals",
        \\const std = @import("std");
        \\const builtin = @import("builtin");
        \\const is_windows = builtin.os.tag == .windows;
        \\const c = @cImport({
        \\    if (is_windows) {
        \\        // See https://github.com/ziglang/zig/issues/515
        \\        @cDefine("_NO_CRT_STDIO_INLINE", "1");
        \\        @cInclude("io.h");
        \\        @cInclude("fcntl.h");
        \\    }
        \\    @cInclude("stdio.h");
        \\});
        \\
        \\pub export fn main(argc: c_int, argv: [*][*]u8) c_int {
        \\    _ = argc;
        \\    _ = argv;
        \\    if (is_windows) {
        \\        // we want actual \n, not \r\n
        \\        _ = c._setmode(1, c._O_BINARY);
        \\    }
        \\    _ = c.printf("0: %llu\n",
        \\             @as(u64, 0));
        \\    _ = c.printf("320402575052271: %llu\n",
        \\         @as(u64, 320402575052271));
        \\    _ = c.printf("0x01236789abcdef: %llu\n",
        \\         @as(u64, 0x01236789abcdef));
        \\    _ = c.printf("0xffffffffffffffff: %llu\n",
        \\         @as(u64, 0xffffffffffffffff));
        \\    _ = c.printf("0x000000ffffffffffffffff: %llu\n",
        \\         @as(u64, 0x000000ffffffffffffffff));
        \\    _ = c.printf("0o1777777777777777777777: %llu\n",
        \\         @as(u64, 0o1777777777777777777777));
        \\    _ = c.printf("0o0000001777777777777777777777: %llu\n",
        \\         @as(u64, 0o0000001777777777777777777777));
        \\    _ = c.printf("0b1111111111111111111111111111111111111111111111111111111111111111: %llu\n",
        \\         @as(u64, 0b1111111111111111111111111111111111111111111111111111111111111111));
        \\    _ = c.printf("0b0000001111111111111111111111111111111111111111111111111111111111111111: %llu\n",
        \\         @as(u64, 0b0000001111111111111111111111111111111111111111111111111111111111111111));
        \\
        \\    _ = c.printf("\n");
        \\
        \\    _ = c.printf("0.0: %.013a\n",
        \\         @as(f64, 0.0));
        \\    _ = c.printf("0e0: %.013a\n",
        \\         @as(f64, 0e0));
        \\    _ = c.printf("0.0e0: %.013a\n",
        \\         @as(f64, 0.0e0));
        \\    _ = c.printf("000000000000000000000000000000000000000000000000000000000.0e0: %.013a\n",
        \\         @as(f64, 0.0e0));
        \\    _ = c.printf("0.000000000000000000000000000000000000000000000000000000000e0: %.013a\n",
        \\         @as(f64, 0.000000000000000000000000000000000000000000000000000000000e0));
        \\    _ = c.printf("0.0e000000000000000000000000000000000000000000000000000000000: %.013a\n",
        \\         @as(f64, 0.0e000000000000000000000000000000000000000000000000000000000));
        \\    _ = c.printf("1.0: %.013a\n",
        \\         @as(f64, 1.0));
        \\    _ = c.printf("10.0: %.013a\n",
        \\         @as(f64, 10.0));
        \\    _ = c.printf("10.5: %.013a\n",
        \\         @as(f64, 10.5));
        \\    _ = c.printf("10.5e5: %.013a\n",
        \\         @as(f64, 10.5e5));
        \\    _ = c.printf("10.5e+5: %.013a\n",
        \\         @as(f64, 10.5e+5));
        \\    _ = c.printf("50.0e-2: %.013a\n",
        \\         @as(f64, 50.0e-2));
        \\    _ = c.printf("50e-2: %.013a\n",
        \\         @as(f64, 50e-2));
        \\
        \\    _ = c.printf("\n");
        \\
        \\    _ = c.printf("0x1.0: %.013a\n",
        \\         @as(f64, 0x1.0));
        \\    _ = c.printf("0x10.0: %.013a\n",
        \\         @as(f64, 0x10.0));
        \\    _ = c.printf("0x100.0: %.013a\n",
        \\         @as(f64, 0x100.0));
        \\    _ = c.printf("0x103.0: %.013a\n",
        \\         @as(f64, 0x103.0));
        \\    _ = c.printf("0x103.7: %.013a\n",
        \\         @as(f64, 0x103.7));
        \\    _ = c.printf("0x103.70: %.013a\n",
        \\         @as(f64, 0x103.70));
        \\    _ = c.printf("0x103.70p4: %.013a\n",
        \\         @as(f64, 0x103.70p4));
        \\    _ = c.printf("0x103.70p5: %.013a\n",
        \\         @as(f64, 0x103.70p5));
        \\    _ = c.printf("0x103.70p+5: %.013a\n",
        \\         @as(f64, 0x103.70p+5));
        \\    _ = c.printf("0x103.70p-5: %.013a\n",
        \\         @as(f64, 0x103.70p-5));
        \\
        \\    return 0;
        \\}
    ,
        \\0: 0
        \\320402575052271: 320402575052271
        \\0x01236789abcdef: 320402575052271
        \\0xffffffffffffffff: 18446744073709551615
        \\0x000000ffffffffffffffff: 18446744073709551615
        \\0o1777777777777777777777: 18446744073709551615
        \\0o0000001777777777777777777777: 18446744073709551615
        \\0b1111111111111111111111111111111111111111111111111111111111111111: 18446744073709551615
        \\0b0000001111111111111111111111111111111111111111111111111111111111111111: 18446744073709551615
        \\
        \\0.0: 0x0.0000000000000p+0
        \\0e0: 0x0.0000000000000p+0
        \\0.0e0: 0x0.0000000000000p+0
        \\000000000000000000000000000000000000000000000000000000000.0e0: 0x0.0000000000000p+0
        \\0.000000000000000000000000000000000000000000000000000000000e0: 0x0.0000000000000p+0
        \\0.0e000000000000000000000000000000000000000000000000000000000: 0x0.0000000000000p+0
        \\1.0: 0x1.0000000000000p+0
        \\10.0: 0x1.4000000000000p+3
        \\10.5: 0x1.5000000000000p+3
        \\10.5e5: 0x1.0059000000000p+20
        \\10.5e+5: 0x1.0059000000000p+20
        \\50.0e-2: 0x1.0000000000000p-1
        \\50e-2: 0x1.0000000000000p-1
        \\
        \\0x1.0: 0x1.0000000000000p+0
        \\0x10.0: 0x1.0000000000000p+4
        \\0x100.0: 0x1.0000000000000p+8
        \\0x103.0: 0x1.0300000000000p+8
        \\0x103.7: 0x1.0370000000000p+8
        \\0x103.70: 0x1.0370000000000p+8
        \\0x103.70p4: 0x1.0370000000000p+12
        \\0x103.70p5: 0x1.0370000000000p+13
        \\0x103.70p+5: 0x1.0370000000000p+13
        \\0x103.70p-5: 0x1.0370000000000p+3
        \\
    );

    cases.add("order-independent declarations",
        \\const io = @import("std").io;
        \\const z = io.stdin_fileno;
        \\const x : @TypeOf(y) = 1234;
        \\const y : u16 = 5678;
        \\pub fn main() void {
        \\    var x_local : i32 = print_ok(x);
        \\    _ = x_local;
        \\}
        \\fn print_ok(val: @TypeOf(x)) @TypeOf(foo) {
        \\    _ = val;
        \\    const stdout = io.getStdOut().writer();
        \\    stdout.print("OK\n", .{}) catch unreachable;
        \\    return 0;
        \\}
        \\const foo : i32 = 0;
    , "OK\n");

    cases.addC("expose function pointer to C land",
        \\const c = @cImport(@cInclude("stdlib.h"));
        \\
        \\export fn compare_fn(a: ?*const anyopaque, b: ?*const anyopaque) c_int {
        \\    const a_int: *const i32 = @ptrCast(@alignCast(a));
        \\    const b_int: *const i32 = @ptrCast(@alignCast(b));
        \\    if (a_int.* < b_int.*) {
        \\        return -1;
        \\    } else if (a_int.* > b_int.*) {
        \\        return 1;
        \\    } else {
        \\        return 0;
        \\    }
        \\}
        \\
        \\pub export fn main() c_int {
        \\    var array = [_]u32{ 1, 7, 3, 2, 0, 9, 4, 8, 6, 5 };
        \\
        \\    c.qsort(@ptrCast(&array), @intCast(array.len), @sizeOf(i32), compare_fn);
        \\
        \\    for (array, 0..) |item, i| {
        \\        if (item != i) {
        \\            c.abort();
        \\        }
        \\    }
        \\
        \\    return 0;
        \\}
    , "");

    cases.addC("casting between float and integer types",
        \\const std = @import("std");
        \\const builtin = @import("builtin");
        \\const is_windows = builtin.os.tag == .windows;
        \\const c = @cImport({
        \\    if (is_windows) {
        \\        // See https://github.com/ziglang/zig/issues/515
        \\        @cDefine("_NO_CRT_STDIO_INLINE", "1");
        \\        @cInclude("io.h");
        \\        @cInclude("fcntl.h");
        \\    }
        \\    @cInclude("stdio.h");
        \\});
        \\
        \\pub export fn main(argc: c_int, argv: [*][*]u8) c_int {
        \\    _ = argc;
        \\    _ = argv;
        \\    if (is_windows) {
        \\        // we want actual \n, not \r\n
        \\        _ = c._setmode(1, c._O_BINARY);
        \\    }
        \\    const small: f32 = 3.25;
        \\    const x: f64 = small;
        \\    const y: i32 = @intFromFloat(x);
        \\    const z: f64 = @floatFromInt(y);
        \\    _ = c.printf("%.2f\n%d\n%.2f\n%.2f\n", x, y, z, @as(f64, -0.4));
        \\    return 0;
        \\}
    , "3.25\n3\n3.00\n-0.40\n");

    cases.add("same named methods in incomplete struct",
        \\const io = @import("std").io;
        \\
        \\const Foo = struct {
        \\    field1: Bar,
        \\
        \\    fn method(a: *const Foo) bool {
        \\        _ = a;
        \\        return true;
        \\    }
        \\};
        \\
        \\const Bar = struct {
        \\    field2: i32,
        \\
        \\    fn method(b: *const Bar) bool {
        \\        _ = b;
        \\        return true;
        \\    }
        \\};
        \\
        \\pub fn main() void {
        \\    const bar = Bar {.field2 = 13,};
        \\    const foo = Foo {.field1 = bar,};
        \\    const stdout = io.getStdOut().writer();
        \\    if (!foo.method()) {
        \\        stdout.print("BAD\n", .{}) catch unreachable;
        \\    }
        \\    if (!bar.method()) {
        \\        stdout.print("BAD\n", .{}) catch unreachable;
        \\    }
        \\    stdout.print("OK\n", .{}) catch unreachable;
        \\}
    , "OK\n");

    cases.add("defer with only fallthrough",
        \\const io = @import("std").io;
        \\pub fn main() void {
        \\    const stdout = io.getStdOut().writer();
        \\    stdout.print("before\n", .{}) catch unreachable;
        \\    defer stdout.print("defer1\n", .{}) catch unreachable;
        \\    defer stdout.print("defer2\n", .{}) catch unreachable;
        \\    defer stdout.print("defer3\n", .{}) catch unreachable;
        \\    stdout.print("after\n", .{}) catch unreachable;
        \\}
    , "before\nafter\ndefer3\ndefer2\ndefer1\n");

    cases.add("defer with return",
        \\const io = @import("std").io;
        \\const os = @import("std").os;
        \\pub fn main() void {
        \\    const stdout = io.getStdOut().writer();
        \\    stdout.print("before\n", .{}) catch unreachable;
        \\    defer stdout.print("defer1\n", .{}) catch unreachable;
        \\    defer stdout.print("defer2\n", .{}) catch unreachable;
        \\    var gpa = @import("std").heap.GeneralPurposeAllocator(.{}){};
        \\    defer _ = gpa.deinit();
        \\    var arena = @import("std").heap.ArenaAllocator.init(gpa.allocator());
        \\    defer arena.deinit();
        \\    var args_it = @import("std").process.argsWithAllocator(arena.allocator()) catch unreachable;
        \\    if (args_it.skip() and !args_it.skip()) return;
        \\    defer stdout.print("defer3\n", .{}) catch unreachable;
        \\    stdout.print("after\n", .{}) catch unreachable;
        \\}
    , "before\ndefer2\ndefer1\n");

    cases.add("errdefer and it fails",
        \\const io = @import("std").io;
        \\pub fn main() void {
        \\    do_test() catch return;
        \\}
        \\fn do_test() !void {
        \\    const stdout = io.getStdOut().writer();
        \\    stdout.print("before\n", .{}) catch unreachable;
        \\    defer stdout.print("defer1\n", .{}) catch unreachable;
        \\    errdefer stdout.print("deferErr\n", .{}) catch unreachable;
        \\    try its_gonna_fail();
        \\    defer stdout.print("defer3\n", .{}) catch unreachable;
        \\    stdout.print("after\n", .{}) catch unreachable;
        \\}
        \\fn its_gonna_fail() !void {
        \\    return error.IToldYouItWouldFail;
        \\}
    , "before\ndeferErr\ndefer1\n");

    cases.add("errdefer and it passes",
        \\const io = @import("std").io;
        \\pub fn main() void {
        \\    do_test() catch return;
        \\}
        \\fn do_test() !void {
        \\    const stdout = io.getStdOut().writer();
        \\    stdout.print("before\n", .{}) catch unreachable;
        \\    defer stdout.print("defer1\n", .{}) catch unreachable;
        \\    errdefer stdout.print("deferErr\n", .{}) catch unreachable;
        \\    try its_gonna_pass();
        \\    defer stdout.print("defer3\n", .{}) catch unreachable;
        \\    stdout.print("after\n", .{}) catch unreachable;
        \\}
        \\fn its_gonna_pass() anyerror!void { }
    , "before\nafter\ndefer3\ndefer1\n");

    cases.addCase(x: {
        var tc = cases.create("@embedFile",
            \\const foo_txt = @embedFile("foo.txt");
            \\const io = @import("std").io;
            \\
            \\pub fn main() void {
            \\    const stdout = io.getStdOut().writer();
            \\    stdout.print(foo_txt, .{}) catch unreachable;
            \\}
        , "1234\nabcd\n");

        tc.addSourceFile("foo.txt", "1234\nabcd\n");

        break :x tc;
    });

    cases.addCase(x: {
        var tc = cases.create("parsing args",
            \\const std = @import("std");
            \\const io = std.io;
            \\const os = std.os;
            \\
            \\pub fn main() !void {
            \\    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
            \\    defer _ = gpa.deinit();
            \\    var arena = std.heap.ArenaAllocator.init(gpa.allocator());
            \\    defer arena.deinit();
            \\    var args_it = try std.process.argsWithAllocator(arena.allocator());
            \\    const stdout = io.getStdOut().writer();
            \\    var index: usize = 0;
            \\    _ = args_it.skip();
            \\    while (args_it.next()) |arg| : (index += 1) {
            \\        try stdout.print("{}: {s}\n", .{index, arg});
            \\    }
            \\}
        ,
            \\0: first arg
            \\1: 'a' 'b' \
            \\2: bare
            \\3: ba""re
            \\4: "
            \\5: last arg
            \\
        );

        tc.setCommandLineArgs(&[_][]const u8{
            "first arg",
            "'a' 'b' \\",
            "bare",
            "ba\"\"re",
            "\"",
            "last arg",
        });

        break :x tc;
    });

    cases.addCase(x: {
        var tc = cases.create("parsing args new API",
            \\const std = @import("std");
            \\const io = std.io;
            \\const os = std.os;
            \\
            \\pub fn main() !void {
            \\    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
            \\    defer _ = gpa.deinit();
            \\    var arena = std.heap.ArenaAllocator.init(gpa.allocator());
            \\    defer arena.deinit();
            \\    var args_it = try std.process.argsWithAllocator(arena.allocator());
            \\    const stdout = io.getStdOut().writer();
            \\    var index: usize = 0;
            \\    _ = args_it.skip();
            \\    while (args_it.next()) |arg| : (index += 1) {
            \\        try stdout.print("{}: {s}\n", .{index, arg});
            \\    }
            \\}
        ,
            \\0: first arg
            \\1: 'a' 'b' \
            \\2: bare
            \\3: ba""re
            \\4: "
            \\5: last arg
            \\
        );

        tc.setCommandLineArgs(&[_][]const u8{
            "first arg",
            "'a' 'b' \\",
            "bare",
            "ba\"\"re",
            "\"",
            "last arg",
        });

        break :x tc;
    });

    // It is required to override the log function in order to print to stdout instead of stderr
    cases.add("std.log per scope log level override",
        \\const std = @import("std");
        \\
        \\pub const std_options = struct {
        \\    pub const log_level: std.log.Level = .debug;
        \\    
        \\    pub const log_scope_levels = &[_]std.log.ScopeLevel{
        \\        .{ .scope = .a, .level = .warn },
        \\        .{ .scope = .c, .level = .err },
        \\    };
        \\    pub const logFn = log;
        \\};
        \\
        \\const loga = std.log.scoped(.a);
        \\const logb = std.log.scoped(.b);
        \\const logc = std.log.scoped(.c);
        \\
        \\pub fn main() !void {
        \\    loga.debug("", .{});
        \\    logb.debug("", .{});
        \\    logc.debug("", .{});
        \\
        \\    loga.info("", .{});
        \\    logb.info("", .{});
        \\    logc.info("", .{});
        \\
        \\    loga.warn("", .{});
        \\    logb.warn("", .{});
        \\    logc.warn("", .{});
        \\
        \\    loga.err("", .{});
        \\    logb.err("", .{});
        \\    logc.err("", .{});
        \\}
        \\pub fn log(
        \\    comptime level: std.log.Level,
        \\    comptime scope: @TypeOf(.EnumLiteral),
        \\    comptime format: []const u8,
        \\    args: anytype,
        \\) void {
        \\    const level_txt = comptime level.asText();
        \\    const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "):";
        \\    const stdout = std.io.getStdOut().writer();
        \\    nosuspend stdout.print(level_txt ++ prefix2 ++ format ++ "\n", args) catch return;
        \\}
    ,
        \\debug(b):
        \\info(b):
        \\warning(a):
        \\warning(b):
        \\error(a):
        \\error(b):
        \\error(c):
        \\
    );

    // It is required to override the log function in order to print to stdout instead of stderr
    cases.add("std.heap.LoggingAllocator logs to std.log",
        \\const std = @import("std");
        \\
        \\pub const std_options = struct {
        \\    pub const log_level: std.log.Level = .debug;
        \\    pub const logFn = log;
        \\};
        \\
        \\pub fn main() !void {
        \\    var allocator_buf: [10]u8 = undefined;
        \\    var fba = std.heap.FixedBufferAllocator.init(&allocator_buf);
        \\    var fba_wrapped = std.mem.validationWrap(fba);
        \\    var logging_allocator = std.heap.loggingAllocator(fba_wrapped.allocator());
        \\    const allocator = logging_allocator.allocator();
        \\
        \\    var a = try allocator.alloc(u8, 10);
        \\    try std.testing.expect(allocator.resize(a, 5));
        \\    a = a[0..5];
        \\    try std.testing.expect(a.len == 5);
        \\    try std.testing.expect(!allocator.resize(a, 20));
        \\    allocator.free(a);
        \\}
        \\
        \\pub fn log(
        \\    comptime level: std.log.Level,
        \\    comptime scope: @TypeOf(.EnumLiteral),
        \\    comptime format: []const u8,
        \\    args: anytype,
        \\) void {
        \\    const level_txt = comptime level.asText();
        \\    const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
        \\    const stdout = std.io.getStdOut().writer();
        \\    nosuspend stdout.print(level_txt ++ prefix2 ++ format ++ "\n", args) catch return;
        \\}
    ,
        \\debug: alloc - success - len: 10, ptr_align: 0
        \\debug: shrink - success - 10 to 5, buf_align: 0
        \\error: expand - failure - 5 to 20, buf_align: 0
        \\debug: free - len: 5
        \\
    );

    cases.add("valid carriage return example", "const io = @import(\"std\").io;\r\n" ++ // Testing CRLF line endings are valid
        "\r\n" ++
        "pub \r fn main() void {\r\n" ++ // Testing isolated carriage return as whitespace is valid
        "    const stdout = io.getStdOut().writer();\r\n" ++
        "    stdout.print(\\\\A Multiline\r\n" ++ // testing CRLF at end of multiline string line is valid and normalises to \n in the output
        "                 \\\\String\r\n" ++
        "                 , .{}) catch unreachable;\r\n" ++
        "}\r\n", "A Multiline\nString");
}
