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

test "optional type" {
    const x: ?bool = true;

    if (x) |y| {
        if (y) {
            // OK
        } else {
            unreachable;
        }
    } else {
        unreachable;
    }

    const next_x: ?i32 = null;

    const z = next_x orelse 1234;

    try expect(z == 1234);

    const final_x: ?i32 = 13;

    const num = final_x orelse unreachable;

    try expect(num == 13);
}

test "test maybe object and get a pointer to the inner value" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
    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;

    var maybe_bool: ?bool = true;

    if (maybe_bool) |*b| {
        b.* = false;
    }

    try expect(maybe_bool.? == false);
}

test "rhs maybe unwrap return" {
    const x: ?bool = true;
    const y = x orelse return;
    _ = y;
}

test "maybe return" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    try maybeReturnImpl();
    try comptime maybeReturnImpl();
}

fn maybeReturnImpl() !void {
    try expect(foo(1235).?);
    if (foo(null) != null) unreachable;
    try expect(!foo(1234).?);
}

fn foo(x: ?i32) ?bool {
    const value = x orelse return null;
    return value > 1234;
}

test "test null runtime" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    try testTestNullRuntime(null);
}
fn testTestNullRuntime(x: ?i32) !void {
    try expect(x == null);
    try expect(!(x != null));
}

test "optional void" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    try optionalVoidImpl();
    try comptime optionalVoidImpl();
}

fn optionalVoidImpl() !void {
    try expect(bar(null) == null);
    try expect(bar({}) != null);
}

fn bar(x: ?void) ?void {
    if (x) |_| {
        return {};
    } else {
        return null;
    }
}

const Empty = struct {};

test "optional struct{}" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    _ = try optionalEmptyStructImpl();
    _ = try comptime optionalEmptyStructImpl();
}

fn optionalEmptyStructImpl() !void {
    try expect(baz(null) == null);
    try expect(baz(Empty{}) != null);
}

fn baz(x: ?Empty) ?Empty {
    if (x) |_| {
        return Empty{};
    } else {
        return null;
    }
}

test "null with default unwrap" {
    const x: i32 = null orelse 1;
    try expect(x == 1);
}

test "optional pointer to 0 bit type null value at runtime" {
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;

    const EmptyStruct = struct {};
    var x: ?*EmptyStruct = null;
    _ = &x;
    try expect(x == null);
}

test "if var maybe pointer" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
    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;

    try expect(shouldBeAPlus1(Particle{
        .a = 14,
        .b = 1,
        .c = 1,
        .d = 1,
    }) == 15);
}
fn shouldBeAPlus1(p: Particle) u64 {
    var maybe_particle: ?Particle = p;
    if (maybe_particle) |*particle| {
        particle.a += 1;
    }
    if (maybe_particle) |particle| {
        return particle.a;
    }
    return 0;
}
const Particle = struct {
    a: u64,
    b: u64,
    c: u64,
    d: u64,
};

test "null literal outside function" {
    const is_null = here_is_a_null_literal.context == null;
    try expect(is_null);

    const is_non_null = here_is_a_null_literal.context != null;
    try expect(!is_non_null);
}

const SillyStruct = struct {
    context: ?i32,
};

const here_is_a_null_literal = SillyStruct{ .context = null };

test "unwrap optional which is field of global var" {
    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

    struct_with_optional.field = null;
    if (struct_with_optional.field) |payload| {
        _ = payload;
        unreachable;
    }
    struct_with_optional.field = 1234;
    if (struct_with_optional.field) |payload| {
        try expect(payload == 1234);
    } else {
        unreachable;
    }
}
const StructWithOptional = struct {
    field: ?i32,
};

var struct_with_optional: StructWithOptional = undefined;

test "optional types" {
    comptime {
        const opt_type_struct = StructWithOptionalType{ .t = u8 };
        try expect(opt_type_struct.t != null and opt_type_struct.t.? == u8);
    }
}

const StructWithOptionalType = struct {
    t: ?type,
};
