// SPDX-License-Identifier: MIT
// Copyright (c) 2015-2021 Zig Contributors
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
// The MIT license requires this copyright notice to be included in all copies
// and substantial portions of the software.
const std = @import("../std.zig");
const builtin = std.builtin;
const mem = std.mem;
const debug = std.debug;
const testing = std.testing;
const warn = debug.warn;

const meta = @import("../meta.zig");

pub const TraitFn = fn (type) bool;

pub fn multiTrait(comptime traits: anytype) TraitFn {
    const Closure = struct {
        pub fn trait(comptime T: type) bool {
            inline for (traits) |t|
                if (!t(T)) return false;
            return true;
        }
    };
    return Closure.trait;
}

test "std.meta.trait.multiTrait" {
    const Vector2 = struct {
        const MyType = @This();

        x: u8,
        y: u8,

        pub fn add(self: MyType, other: MyType) MyType {
            return MyType{
                .x = self.x + other.x,
                .y = self.y + other.y,
            };
        }
    };

    const isVector = multiTrait(.{
        hasFn("add"),
        hasField("x"),
        hasField("y"),
    });
    testing.expect(isVector(Vector2));
    testing.expect(!isVector(u8));
}

pub fn hasFn(comptime name: []const u8) TraitFn {
    const Closure = struct {
        pub fn trait(comptime T: type) bool {
            if (!comptime isContainer(T)) return false;
            if (!comptime @hasDecl(T, name)) return false;
            const DeclType = @TypeOf(@field(T, name));
            return @typeInfo(DeclType) == .Fn;
        }
    };
    return Closure.trait;
}

test "std.meta.trait.hasFn" {
    const TestStruct = struct {
        pub fn useless() void {}
    };

    testing.expect(hasFn("useless")(TestStruct));
    testing.expect(!hasFn("append")(TestStruct));
    testing.expect(!hasFn("useless")(u8));
}

pub fn hasField(comptime name: []const u8) TraitFn {
    const Closure = struct {
        pub fn trait(comptime T: type) bool {
            const fields = switch (@typeInfo(T)) {
                .Struct => |s| s.fields,
                .Union => |u| u.fields,
                .Enum => |e| e.fields,
                else => return false,
            };

            inline for (fields) |field| {
                if (mem.eql(u8, field.name, name)) return true;
            }

            return false;
        }
    };
    return Closure.trait;
}

test "std.meta.trait.hasField" {
    const TestStruct = struct {
        value: u32,
    };

    testing.expect(hasField("value")(TestStruct));
    testing.expect(!hasField("value")(*TestStruct));
    testing.expect(!hasField("x")(TestStruct));
    testing.expect(!hasField("x")(**TestStruct));
    testing.expect(!hasField("value")(u8));
}

pub fn is(comptime id: builtin.TypeId) TraitFn {
    const Closure = struct {
        pub fn trait(comptime T: type) bool {
            return id == @typeInfo(T);
        }
    };
    return Closure.trait;
}

test "std.meta.trait.is" {
    testing.expect(is(.Int)(u8));
    testing.expect(!is(.Int)(f32));
    testing.expect(is(.Pointer)(*u8));
    testing.expect(is(.Void)(void));
    testing.expect(!is(.Optional)(anyerror));
}

pub fn isPtrTo(comptime id: builtin.TypeId) TraitFn {
    const Closure = struct {
        pub fn trait(comptime T: type) bool {
            if (!comptime isSingleItemPtr(T)) return false;
            return id == @typeInfo(meta.Child(T));
        }
    };
    return Closure.trait;
}

test "std.meta.trait.isPtrTo" {
    testing.expect(!isPtrTo(.Struct)(struct {}));
    testing.expect(isPtrTo(.Struct)(*struct {}));
    testing.expect(!isPtrTo(.Struct)(**struct {}));
}

pub fn isSliceOf(comptime id: builtin.TypeId) TraitFn {
    const Closure = struct {
        pub fn trait(comptime T: type) bool {
            if (!comptime isSlice(T)) return false;
            return id == @typeInfo(meta.Child(T));
        }
    };
    return Closure.trait;
}

test "std.meta.trait.isSliceOf" {
    testing.expect(!isSliceOf(.Struct)(struct {}));
    testing.expect(isSliceOf(.Struct)([]struct {}));
    testing.expect(!isSliceOf(.Struct)([][]struct {}));
}

///////////Strait trait Fns

//@TODO:
// Somewhat limited since we can't apply this logic to normal variables, fields, or
//  Fns yet. Should be isExternType?
pub fn isExtern(comptime T: type) bool {
    return switch (@typeInfo(T)) {
        .Struct => |s| s.layout == .Extern,
        .Union => |u| u.layout == .Extern,
        .Enum => |e| e.layout == .Extern,
        else => false,
    };
}

test "std.meta.trait.isExtern" {
    const TestExStruct = extern struct {};
    const TestStruct = struct {};

    testing.expect(isExtern(TestExStruct));
    testing.expect(!isExtern(TestStruct));
    testing.expect(!isExtern(u8));
}

pub fn isPacked(comptime T: type) bool {
    return switch (@typeInfo(T)) {
        .Struct => |s| s.layout == .Packed,
        .Union => |u| u.layout == .Packed,
        .Enum => |e| e.layout == .Packed,
        else => false,
    };
}

test "std.meta.trait.isPacked" {
    const TestPStruct = packed struct {};
    const TestStruct = struct {};

    testing.expect(isPacked(TestPStruct));
    testing.expect(!isPacked(TestStruct));
    testing.expect(!isPacked(u8));
}

pub fn isUnsignedInt(comptime T: type) bool {
    return switch (@typeInfo(T)) {
        .Int => |i| i.signedness == .unsigned,
        else => false,
    };
}

test "isUnsignedInt" {
    testing.expect(isUnsignedInt(u32) == true);
    testing.expect(isUnsignedInt(comptime_int) == false);
    testing.expect(isUnsignedInt(i64) == false);
    testing.expect(isUnsignedInt(f64) == false);
}

pub fn isSignedInt(comptime T: type) bool {
    return switch (@typeInfo(T)) {
        .ComptimeInt => true,
        .Int => |i| i.signedness == .signed,
        else => false,
    };
}

test "isSignedInt" {
    testing.expect(isSignedInt(u32) == false);
    testing.expect(isSignedInt(comptime_int) == true);
    testing.expect(isSignedInt(i64) == true);
    testing.expect(isSignedInt(f64) == false);
}

pub fn isSingleItemPtr(comptime T: type) bool {
    if (comptime is(.Pointer)(T)) {
        return @typeInfo(T).Pointer.size == .One;
    }
    return false;
}

test "std.meta.trait.isSingleItemPtr" {
    const array = [_]u8{0} ** 10;
    comptime testing.expect(isSingleItemPtr(@TypeOf(&array[0])));
    comptime testing.expect(!isSingleItemPtr(@TypeOf(array)));
    var runtime_zero: usize = 0;
    testing.expect(!isSingleItemPtr(@TypeOf(array[runtime_zero..1])));
}

pub fn isManyItemPtr(comptime T: type) bool {
    if (comptime is(.Pointer)(T)) {
        return @typeInfo(T).Pointer.size == .Many;
    }
    return false;
}

test "std.meta.trait.isManyItemPtr" {
    const array = [_]u8{0} ** 10;
    const mip = @ptrCast([*]const u8, &array[0]);
    testing.expect(isManyItemPtr(@TypeOf(mip)));
    testing.expect(!isManyItemPtr(@TypeOf(array)));
    testing.expect(!isManyItemPtr(@TypeOf(array[0..1])));
}

pub fn isSlice(comptime T: type) bool {
    if (comptime is(.Pointer)(T)) {
        return @typeInfo(T).Pointer.size == .Slice;
    }
    return false;
}

test "std.meta.trait.isSlice" {
    const array = [_]u8{0} ** 10;
    var runtime_zero: usize = 0;
    testing.expect(isSlice(@TypeOf(array[runtime_zero..])));
    testing.expect(!isSlice(@TypeOf(array)));
    testing.expect(!isSlice(@TypeOf(&array[0])));
}

pub fn isIndexable(comptime T: type) bool {
    if (comptime is(.Pointer)(T)) {
        if (@typeInfo(T).Pointer.size == .One) {
            return (comptime is(.Array)(meta.Child(T)));
        }
        return true;
    }
    return comptime is(.Array)(T) or is(.Vector)(T) or isTuple(T);
}

test "std.meta.trait.isIndexable" {
    const array = [_]u8{0} ** 10;
    const slice = @as([]const u8, &array);
    const vector: meta.Vector(2, u32) = [_]u32{0} ** 2;
    const tuple = .{ 1, 2, 3 };

    testing.expect(isIndexable(@TypeOf(array)));
    testing.expect(isIndexable(@TypeOf(&array)));
    testing.expect(isIndexable(@TypeOf(slice)));
    testing.expect(!isIndexable(meta.Child(@TypeOf(slice))));
    testing.expect(isIndexable(@TypeOf(vector)));
    testing.expect(isIndexable(@TypeOf(tuple)));
}

pub fn isNumber(comptime T: type) bool {
    return switch (@typeInfo(T)) {
        .Int, .Float, .ComptimeInt, .ComptimeFloat => true,
        else => false,
    };
}

pub fn isIntegerNumber(comptime T: type) bool {
    return switch (@typeInfo(T)) {
        .Int, .ComptimeInt => true,
        else => false,
    };
}

pub fn isFloatingNumber(comptime T: type) bool {
    return switch (@typeInfo(T)) {
        .Float, .ComptimeFloat => true,
        else => false,
    };
}

test "std.meta.trait.isNumber" {
    const NotANumber = struct {
        number: u8,
    };

    testing.expect(isNumber(u32));
    testing.expect(isNumber(f32));
    testing.expect(isNumber(u64));
    testing.expect(isNumber(@TypeOf(102)));
    testing.expect(isNumber(@TypeOf(102.123)));
    testing.expect(!isNumber([]u8));
    testing.expect(!isNumber(NotANumber));
}

pub fn isIntegral(comptime T: type) bool {
    return switch (@typeInfo(T)) {
        .Int, .ComptimeInt => true,
        else => false,
    };
}

test "isIntegral" {
    testing.expect(isIntegral(u32));
    testing.expect(!isIntegral(f32));
    testing.expect(isIntegral(@TypeOf(102)));
    testing.expect(!isIntegral(@TypeOf(102.123)));
    testing.expect(!isIntegral(*u8));
    testing.expect(!isIntegral([]u8));
}

pub fn isFloat(comptime T: type) bool {
    return switch (@typeInfo(T)) {
        .Float, .ComptimeFloat => true,
        else => false,
    };
}

test "isFloat" {
    testing.expect(!isFloat(u32));
    testing.expect(isFloat(f32));
    testing.expect(!isFloat(@TypeOf(102)));
    testing.expect(isFloat(@TypeOf(102.123)));
    testing.expect(!isFloat(*f64));
    testing.expect(!isFloat([]f32));
}

pub fn isConstPtr(comptime T: type) bool {
    if (!comptime is(.Pointer)(T)) return false;
    return @typeInfo(T).Pointer.is_const;
}

test "std.meta.trait.isConstPtr" {
    var t = @as(u8, 0);
    const c = @as(u8, 0);
    testing.expect(isConstPtr(*const @TypeOf(t)));
    testing.expect(isConstPtr(@TypeOf(&c)));
    testing.expect(!isConstPtr(*@TypeOf(t)));
    testing.expect(!isConstPtr(@TypeOf(6)));
}

pub fn isContainer(comptime T: type) bool {
    return switch (@typeInfo(T)) {
        .Struct, .Union, .Enum => true,
        else => false,
    };
}

test "std.meta.trait.isContainer" {
    const TestStruct = struct {};
    const TestUnion = union {
        a: void,
    };
    const TestEnum = enum {
        A,
        B,
    };

    testing.expect(isContainer(TestStruct));
    testing.expect(isContainer(TestUnion));
    testing.expect(isContainer(TestEnum));
    testing.expect(!isContainer(u8));
}

pub fn isTuple(comptime T: type) bool {
    return is(.Struct)(T) and @typeInfo(T).Struct.is_tuple;
}

test "std.meta.trait.isTuple" {
    const t1 = struct {};
    const t2 = .{ .a = 0 };
    const t3 = .{ 1, 2, 3 };
    testing.expect(!isTuple(t1));
    testing.expect(!isTuple(@TypeOf(t2)));
    testing.expect(isTuple(@TypeOf(t3)));
}

pub fn hasDecls(comptime T: type, comptime names: anytype) bool {
    inline for (names) |name| {
        if (!@hasDecl(T, name))
            return false;
    }
    return true;
}

test "std.meta.trait.hasDecls" {
    const TestStruct1 = struct {};
    const TestStruct2 = struct {
        pub var a: u32;
        pub var b: u32;
        c: bool,
        pub fn useless() void {}
    };

    const tuple = .{ "a", "b", "c" };

    testing.expect(!hasDecls(TestStruct1, .{"a"}));
    testing.expect(hasDecls(TestStruct2, .{ "a", "b" }));
    testing.expect(hasDecls(TestStruct2, .{ "a", "b", "useless" }));
    testing.expect(!hasDecls(TestStruct2, .{ "a", "b", "c" }));
    testing.expect(!hasDecls(TestStruct2, tuple));
}

pub fn hasFields(comptime T: type, comptime names: anytype) bool {
    inline for (names) |name| {
        if (!@hasField(T, name))
            return false;
    }
    return true;
}

test "std.meta.trait.hasFields" {
    const TestStruct1 = struct {};
    const TestStruct2 = struct {
        a: u32,
        b: u32,
        c: bool,
        pub fn useless() void {}
    };

    const tuple = .{ "a", "b", "c" };

    testing.expect(!hasFields(TestStruct1, .{"a"}));
    testing.expect(hasFields(TestStruct2, .{ "a", "b" }));
    testing.expect(hasFields(TestStruct2, .{ "a", "b", "c" }));
    testing.expect(hasFields(TestStruct2, tuple));
    testing.expect(!hasFields(TestStruct2, .{ "a", "b", "useless" }));
}

pub fn hasFunctions(comptime T: type, comptime names: anytype) bool {
    inline for (names) |name| {
        if (!hasFn(name)(T))
            return false;
    }
    return true;
}

test "std.meta.trait.hasFunctions" {
    const TestStruct1 = struct {};
    const TestStruct2 = struct {
        pub fn a() void {}
        fn b() void {}
    };

    const tuple = .{ "a", "b", "c" };

    testing.expect(!hasFunctions(TestStruct1, .{"a"}));
    testing.expect(hasFunctions(TestStruct2, .{ "a", "b" }));
    testing.expect(!hasFunctions(TestStruct2, .{ "a", "b", "c" }));
    testing.expect(!hasFunctions(TestStruct2, tuple));
}

/// True if every value of the type `T` has a unique bit pattern representing it.
/// In other words, `T` has no unused bits and no padding.
pub fn hasUniqueRepresentation(comptime T: type) bool {
    switch (@typeInfo(T)) {
        else => return false, // TODO can we know if it's true for some of these types ?

        .AnyFrame,
        .BoundFn,
        .Enum,
        .ErrorSet,
        .Fn,
        => return true,

        .Bool => return false,

        // The padding bits are undefined.
        .Int => |info| return (info.bits % 8) == 0 and
            (info.bits == 0 or std.math.isPowerOfTwo(info.bits)),

        .Pointer => |info| return info.size != .Slice,

        .Array => |info| return comptime hasUniqueRepresentation(info.child),

        .Struct => |info| {
            var sum_size = @as(usize, 0);

            inline for (info.fields) |field| {
                const FieldType = field.field_type;
                if (comptime !hasUniqueRepresentation(FieldType)) return false;
                sum_size += @sizeOf(FieldType);
            }

            return @sizeOf(T) == sum_size;
        },

        .Vector => |info| return comptime hasUniqueRepresentation(info.child),
    }
}

test "std.meta.trait.hasUniqueRepresentation" {
    const TestStruct1 = struct {
        a: u32,
        b: u32,
    };

    testing.expect(hasUniqueRepresentation(TestStruct1));

    const TestStruct2 = struct {
        a: u32,
        b: u16,
    };

    testing.expect(!hasUniqueRepresentation(TestStruct2));

    const TestStruct3 = struct {
        a: u32,
        b: u32,
    };

    testing.expect(hasUniqueRepresentation(TestStruct3));

    const TestStruct4 = struct {
        a: []const u8
    };

    testing.expect(!hasUniqueRepresentation(TestStruct4));

    const TestStruct5 = struct {
        a: TestStruct4
    };

    testing.expect(!hasUniqueRepresentation(TestStruct5));

    const TestUnion1 = packed union {
        a: u32,
        b: u16,
    };

    testing.expect(!hasUniqueRepresentation(TestUnion1));

    const TestUnion2 = extern union {
        a: u32,
        b: u16,
    };

    testing.expect(!hasUniqueRepresentation(TestUnion2));

    const TestUnion3 = union {
        a: u32,
        b: u16,
    };

    testing.expect(!hasUniqueRepresentation(TestUnion3));

    const TestUnion4 = union(enum) {
        a: u32,
        b: u16,
    };

    testing.expect(!hasUniqueRepresentation(TestUnion4));

    inline for ([_]type{ i0, u8, i16, u32, i64 }) |T| {
        testing.expect(hasUniqueRepresentation(T));
    }
    inline for ([_]type{ i1, u9, i17, u33, i24 }) |T| {
        testing.expect(!hasUniqueRepresentation(T));
    }

    testing.expect(!hasUniqueRepresentation([]u8));
    testing.expect(!hasUniqueRepresentation([]const u8));
}
