@safe unittest
{
    import std.checkedint;

    int[] concatAndAdd(int[] a, int[] b, int offset)
    {
        // Aborts on overflow on size computation
        auto r = new int[(checked(a.length) + b.length).get];
        // Aborts on overflow on element computation
        foreach (i; 0 .. a.length)
            r[i] = (a[i] + checked(offset)).get;
        foreach (i; 0 .. b.length)
            r[i + a.length] = (b[i] + checked(offset)).get;
        return r;
    }
    assert(concatAndAdd([1, 2, 3], [4, 5], -1) == [0, 1, 2, 3, 4]);
}

@safe unittest
{
    import std.checkedint;

    auto x = (cast(byte) 127).checked!Saturate;
    assert(x == 127);
    x++;
    assert(x == 127);
}

@safe unittest
{
    import std.checkedint;

    auto x = 100.checked!WithNaN;
    assert(x == 100);
    x /= 0;
    assert(x.isNaN);
}

@safe unittest
{
    import std.checkedint;

    uint x = 1;
    auto y = x.checked!ProperCompare;
    assert(x < -1); // built-in comparison
    assert(y > -1); // ProperCompare
}

@safe unittest
{
    import std.checkedint;

    import std.exception : assertThrown;
    auto x = -1.checked!Throw;
    assertThrown(x / 0);
    assertThrown(x + int.min);
    assertThrown(x == uint.max);
}

@safe unittest
{
    import std.checkedint;

        auto x = checked(ubyte(42));
        static assert(is(typeof(x.get()) == ubyte));
        assert(x.get == 42);
        const y = checked(ubyte(42));
        static assert(is(typeof(y.get()) == const ubyte));
        assert(y.get == 42);
    
}

@safe unittest
{
    import std.checkedint;

            assert(Checked!short.min == -32768);
            assert(Checked!(short, WithNaN).min == -32767);
            assert(Checked!(uint, WithNaN).max == uint.max - 1);
        
}

@safe unittest
{
    import std.checkedint;

        auto a = checked(42L);
        assert(a == 42);
        auto b = Checked!long(4242); // convert 4242 to long
        assert(b == 4242);
    
}

@safe unittest
{
    import std.checkedint;

        Checked!long a;
        a = 42L;
        assert(a == 42);
        a = 4242;
        assert(a == 4242);
    
}

@safe unittest
{
    import std.checkedint;

        Checked!long a, b;
        a = b = 3;
        assert(a == 3 && b == 3);
    
}

@system unittest
{
    import std.checkedint;

        import std.conv : to;

        const a = to!long("1234");
        const b = to!(Checked!long)("1234");
        assert(a == b);
    
}

@safe unittest
{
    import std.checkedint;

        assert(cast(uint) checked(42) == 42);
        assert(cast(uint) checked!WithNaN(-42) == uint.max);
    
}

@safe unittest
{
    import std.checkedint;

        import std.traits : isUnsigned;

        static struct MyHook
        {
            static bool thereWereErrors;
            static bool hookOpEquals(L, R)(L lhs, R rhs)
            {
                if (lhs != rhs) return false;
                static if (isUnsigned!L && !isUnsigned!R)
                {
                    if (lhs > 0 && rhs < 0) thereWereErrors = true;
                }
                else static if (isUnsigned!R && !isUnsigned!L)
                    if (lhs < 0 && rhs > 0) thereWereErrors = true;
                // Preserve built-in behavior.
                return true;
            }
        }
        auto a = checked!MyHook(-42);
        assert(a == uint(-42));
        assert(MyHook.thereWereErrors);
        MyHook.thereWereErrors = false;
        assert(checked!MyHook(uint(-42)) == -42);
        assert(MyHook.thereWereErrors);
        static struct MyHook2
        {
            static bool hookOpEquals(L, R)(L lhs, R rhs)
            {
                return lhs == rhs;
            }
        }
        MyHook.thereWereErrors = false;
        assert(checked!MyHook2(uint(-42)) == a);
        // Hook on left hand side takes precedence, so no errors
        assert(!MyHook.thereWereErrors);
    
}

@system unittest
{
    import std.checkedint;

        import std.format;

        assert(format("%04d", checked(15)) == "0015");
        assert(format("0x%02x", checked(15)) == "0x0f");
    
}

@safe unittest
{
    import std.checkedint;

        import std.traits : isUnsigned;

        static struct MyHook
        {
            static bool thereWereErrors;
            static int hookOpCmp(L, R)(L lhs, R rhs)
            {
                static if (isUnsigned!L && !isUnsigned!R)
                {
                    if (rhs < 0 && rhs >= lhs)
                        thereWereErrors = true;
                }
                else static if (isUnsigned!R && !isUnsigned!L)
                {
                    if (lhs < 0 && lhs >= rhs)
                        thereWereErrors = true;
                }
                // Preserve built-in behavior.
                return lhs < rhs ? -1 : lhs > rhs;
            }
        }
        auto a = checked!MyHook(-42);
        assert(a > uint(42));
        assert(MyHook.thereWereErrors);
        static struct MyHook2
        {
            static int hookOpCmp(L, R)(L lhs, R rhs)
            {
                // Default behavior
                return lhs < rhs ? -1 : lhs > rhs;
            }
        }
        MyHook.thereWereErrors = false;
        assert(Checked!(uint, MyHook2)(uint(-42)) <= a);
        //assert(Checked!(uint, MyHook2)(uint(-42)) >= a);
        // Hook on left hand side takes precedence, so no errors
        assert(!MyHook.thereWereErrors);
        assert(a <= Checked!(uint, MyHook2)(uint(-42)));
        assert(MyHook.thereWereErrors);
    
}

@safe unittest
{
    import std.checkedint;

        static struct MyHook
        {
            static bool thereWereErrors;
            static L hookOpUnary(string x, L)(L lhs)
            {
                if (x == "-" && lhs == -lhs) thereWereErrors = true;
                return -lhs;
            }
        }
        auto a = checked!MyHook(long.min);
        assert(a == -a);
        assert(MyHook.thereWereErrors);
        auto b = checked!void(42);
        assert(++b == 43);
    
}

@safe unittest
{
    import std.checkedint;

        static struct MyHook
        {
            static bool thereWereErrors;
            static T onLowerBound(Rhs, T)(Rhs rhs, T bound)
            {
                thereWereErrors = true;
                return bound;
            }
            static T onUpperBound(Rhs, T)(Rhs rhs, T bound)
            {
                thereWereErrors = true;
                return bound;
            }
        }
        auto x = checked!MyHook(byte.min);
        x -= 1;
        assert(MyHook.thereWereErrors);
        MyHook.thereWereErrors = false;
        x = byte.max;
        x += 1;
        assert(MyHook.thereWereErrors);
    
}

@safe @nogc pure nothrow unittest
{
    import std.checkedint;

    // Hook that ignores all problems.
    static struct Ignore
    {
        @nogc nothrow pure @safe static:
        Dst onBadCast(Dst, Src)(Src src) { return cast(Dst) src; }
        Lhs onLowerBound(Rhs, T)(Rhs rhs, T bound) { return cast(T) rhs; }
        T onUpperBound(Rhs, T)(Rhs rhs, T bound) { return cast(T) rhs; }
        bool hookOpEquals(Lhs, Rhs)(Lhs lhs, Rhs rhs) { return lhs == rhs; }
        int hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs) { return (lhs > rhs) - (lhs < rhs); }
        typeof(~Lhs()) onOverflow(string x, Lhs)(ref Lhs lhs) { return mixin(x ~ "lhs"); }
        typeof(Lhs() + Rhs()) onOverflow(string x, Lhs, Rhs)(Lhs lhs, Rhs rhs)
        {
            static if (x == "/")
                return typeof(lhs / rhs).min;
            else
                return mixin("lhs" ~ x ~ "rhs");
        }
    }

    auto x = Checked!(int, Ignore)(5) + 7;
}

@safe unittest
{
    import std.checkedint;

    static assert(is(typeof(checked(42)) == Checked!int));
    assert(checked(42) == Checked!int(42));
    static assert(is(typeof(checked!WithNaN(42)) == Checked!(int, WithNaN)));
    assert(checked!WithNaN(42) == Checked!(int, WithNaN)(42));
}

@safe unittest
{
    import std.checkedint;

    void test(T)()
    {
        Checked!(int, Abort) x;
        x = 42;
        auto x1 = cast(T) x;
        assert(x1 == 42);
        //x1 += long(int.max);
    }
    test!short;
    test!(const short);
    test!(immutable short);
}

@safe unittest
{
    import std.checkedint;

    void test(T)()
    {
        Checked!(int, Throw) x;
        x = 42;
        auto x1 = cast(T) x;
        assert(x1 == 42);
        x = T.max + 1;
        import std.exception : assertThrown, assertNotThrown;
        assertThrown(cast(T) x);
        x = x.max;
        assertThrown(x += 42);
        assertThrown(x += 42L);
        x = x.min;
        assertThrown(-x);
        assertThrown(x -= 42);
        assertThrown(x -= 42L);
        x = -1;
        assertNotThrown(x == -1);
        assertThrown(x == uint(-1));
        assertNotThrown(x <= -1);
        assertThrown(x <= uint(-1));
    }
    test!short;
    test!(const short);
    test!(immutable short);
}

@safe unittest
{
    import std.checkedint;

        auto x = checked!Warn(-42);
        // Passes
        assert(x == -42);
        // Passes but prints a warning
        // assert(x == uint(-42));
    
}

@safe unittest
{
    import std.checkedint;

        auto x = checked!Warn(-42);
        // Passes
        assert(x <= -42);
        // Passes but prints a warning
        // assert(x <= uint(-42));
    
}

@safe unittest
{
    import std.checkedint;

    auto x = checked!Warn(42);
    short x1 = cast(short) x;
    //x += long(int.max);
    auto y = checked!Warn(cast(const int) 42);
    short y1 = cast(const byte) y;
}

@safe unittest
{
    import std.checkedint;

    alias opEqualsProper = ProperCompare.hookOpEquals;
    assert(opEqualsProper(42, 42));
    assert(opEqualsProper(42.0, 42.0));
    assert(opEqualsProper(42u, 42));
    assert(opEqualsProper(42, 42u));
    assert(-1 == 4294967295u);
    assert(!opEqualsProper(-1, 4294967295u));
    assert(!opEqualsProper(const uint(-1), -1));
    assert(!opEqualsProper(uint(-1), -1.0));
    assert(3_000_000_000U == -1_294_967_296);
    assert(!opEqualsProper(3_000_000_000U, -1_294_967_296));
}

@safe unittest
{
    import std.checkedint;

        auto x = checked!WithNaN(422);
        assert((cast(ubyte) x) == 255);
        x = checked!WithNaN(-422);
        assert((cast(byte) x) == -128);
        assert(cast(short) x == -422);
        assert(cast(bool) x);
        x = x.init; // set back to NaN
        assert(x != true);
        assert(x != false);
    
}

@safe unittest
{
    import std.checkedint;

        Checked!(int, WithNaN) x;
        assert(!(x < 0) && !(x > 0) && !(x == 0));
        x = 1;
        assert(x > 0 && !(x < 0) && !(x == 0));
    
}

@safe unittest
{
    import std.checkedint;

        Checked!(int, WithNaN) x;
        ++x;
        assert(x.isNaN);
        x = 1;
        assert(!x.isNaN);
        x = -x;
        ++x;
        assert(!x.isNaN);
    
}

@safe unittest
{
    import std.checkedint;

        Checked!(int, WithNaN) x;
        assert((x + 1).isNaN);
        x = 100;
        assert(!(x + 1).isNaN);
    
}

@safe unittest
{
    import std.checkedint;

        Checked!(int, WithNaN) x;
        assert((1 + x).isNaN);
        x = 100;
        assert(!(1 + x).isNaN);
    
}

@safe unittest
{
    import std.checkedint;

        Checked!(int, WithNaN) x;
        x += 4;
        assert(x.isNaN);
        x = 0;
        x += 4;
        assert(!x.isNaN);
        x += int.max;
        assert(x.isNaN);
    
}

@safe unittest
{
    import std.checkedint;

    auto x1 = Checked!(int, WithNaN)();
    assert(x1.isNaN);
    assert(x1.get == int.min);
    assert(x1 != x1);
    assert(!(x1 < x1));
    assert(!(x1 > x1));
    assert(!(x1 == x1));
    ++x1;
    assert(x1.isNaN);
    assert(x1.get == int.min);
    --x1;
    assert(x1.isNaN);
    assert(x1.get == int.min);
    x1 = 42;
    assert(!x1.isNaN);
    assert(x1 == x1);
    assert(x1 <= x1);
    assert(x1 >= x1);
    static assert(x1.min == int.min + 1);
    x1 += long(int.max);
}

@safe unittest
{
    import std.checkedint;

    auto x1 = Checked!(int, WithNaN)();
    assert(x1.isNaN);
    x1 = 1;
    assert(!x1.isNaN);
    x1 = x1.init;
    assert(x1.isNaN);
}

@safe unittest
{
    import std.checkedint;

        auto x = checked!Saturate(short(100));
        x += 33000;
        assert(x == short.max);
        x -= 70000;
        assert(x == short.min);
    
}

@safe unittest
{
    import std.checkedint;

        assert(checked!Saturate(int.max) + 1 == int.max);
        assert(checked!Saturate(100) ^^ 10 == int.max);
        assert(checked!Saturate(-100) ^^ 10 == int.max);
        assert(checked!Saturate(100) / 0 == int.max);
        assert(checked!Saturate(100) << -1 == 0);
        assert(checked!Saturate(100) << 33 == int.max);
        assert(checked!Saturate(100) >> -1 == int.max);
        assert(checked!Saturate(100) >> 33 == 0);
    
}

@safe unittest
{
    import std.checkedint;

    auto x = checked!Saturate(int.max);
    ++x;
    assert(x == int.max);
    --x;
    assert(x == int.max - 1);
    x = int.min;
    assert(-x == int.max);
    x -= 42;
    assert(x == int.min);
    assert(x * -2 == int.max);
}

@safe unittest
{
    import std.checkedint;

    bool overflow;
    assert(opChecked!"+"(const short(1), short(1), overflow) == 2 && !overflow);
    assert(opChecked!"+"(1, 1, overflow) == 2 && !overflow);
    assert(opChecked!"+"(1, 1u, overflow) == 2 && !overflow);
    assert(opChecked!"+"(-1, 1u, overflow) == 0 && !overflow);
    assert(opChecked!"+"(1u, -1, overflow) == 0 && !overflow);
}

@safe unittest
{
    import std.checkedint;

    bool overflow;
    assert(opChecked!"-"(1, 1, overflow) == 0 && !overflow);
    assert(opChecked!"-"(1, 1u, overflow) == 0 && !overflow);
    assert(opChecked!"-"(1u, -1, overflow) == 2 && !overflow);
    assert(opChecked!"-"(-1, 1u, overflow) == 0 && overflow);
}

@safe unittest
{
    import std.checkedint;

    struct MyHook
    {
        static size_t hookToHash(T)(const T payload) nothrow @trusted
        {
            return .hashOf(payload);
        }
    }

    int[Checked!(int, MyHook)] aa;
    Checked!(int, MyHook) var = 42;
    aa[var] = 100;

    assert(aa[var] == 100);

    int[Checked!(int, Abort)] bb;
    Checked!(int, Abort) var2 = 42;
    bb[var2] = 100;

    assert(bb[var2] == 100);
}