// DISABLED: LDC
// PERMUTE_ARGS: -inline
// REQUIRED_ARGS: -verrors=simple -verrors=0
/* TEST_OUTPUT:
---
runnable/template10.d(89): Deprecation: function `template10.test1b.f0.f!(a).f` function requires a dual-context, which is deprecated
runnable/template10.d(94):        instantiated from here: `f!(a)`
runnable/template10.d(105): Deprecation: function `template10.test1c.f0.f1.v!(c).sum` function requires a dual-context, which is deprecated
runnable/template10.d(122):        instantiated from here: `v!(c)`
runnable/template10.d(170): Deprecation: function `template10.test3.exec!(set).exec` function requires a dual-context, which is deprecated
runnable/template10.d(182):        instantiated from here: `exec!(set)`
runnable/template10.d(201): Deprecation: function `template10.get4i.inner!(a).inner` function requires a dual-context, which is deprecated
runnable/template10.d(238):        instantiated from here: `inner!(a)`
runnable/template10.d(196): Deprecation: function `template10.test4.add!(a).add` function requires a dual-context, which is deprecated
runnable/template10.d(245):        instantiated from here: `add!(a)`
runnable/template10.d(211): Deprecation: function `template10.test4i!(I).test4i.add2!(b).add2` function requires a dual-context, which is deprecated
runnable/template10.d(231):        instantiated from here: `add2!(b)`
runnable/template10.d(251):        instantiated from here: `test4i!(I)`
runnable/template10.d(201): Deprecation: function `template10.test4.inner!(a).inner` function requires a dual-context, which is deprecated
runnable/template10.d(256):        instantiated from here: `inner!(a)`
runnable/template10.d(266): Deprecation: function `template10.test5.add!(fun).add` function requires a dual-context, which is deprecated
runnable/template10.d(282):        instantiated from here: `add!(fun)`
runnable/template10.d(271): Deprecation: function `template10.test5.add!(fun).add.exec2!(fun, add).exec2` function requires a dual-context, which is deprecated
runnable/template10.d(269):        instantiated from here: `exec2!(fun, add)`
runnable/template10.d(282):        instantiated from here: `add!(fun)`
runnable/template10.d(299): Deprecation: function `template10.test6a.makeR!(j).makeR` function requires a dual-context, which is deprecated
runnable/template10.d(321):        instantiated from here: `makeR!(j)`
runnable/template10.d(307): Deprecation: function `template10.test6a.inc!(k).inc` function requires a dual-context, which is deprecated
runnable/template10.d(322):        instantiated from here: `inc!(k)`
runnable/template10.d(333): Deprecation: function `template10.test6b.f0!(a).f0` function requires a dual-context, which is deprecated
runnable/template10.d(358):        instantiated from here: `f0!(a)`
runnable/template10.d(366): Deprecation: function `template10.test6c.f0.exec!(f).exec` function requires a dual-context, which is deprecated
runnable/template10.d(385):        instantiated from here: `exec!(f)`
runnable/template10.d(410): Deprecation: function `template10.test7.C!(c).sum` function requires a dual-context, which is deprecated
runnable/template10.d(437):        instantiated from here: `C!(c)`
runnable/template10.d(464): Deprecation: constructor `template10.test8.this!(a).this` function requires a dual-context, which is deprecated
runnable/template10.d(484):        instantiated from here: `this!(a)`
runnable/template10.d(468): Deprecation: function `template10.test8.add!(b).add` function requires a dual-context, which is deprecated
runnable/template10.d(486):        instantiated from here: `add!(b)`
runnable/template10.d(446): Deprecation: function `template10.test8.sub!(b).sub` function requires a dual-context, which is deprecated
runnable/template10.d(487):        instantiated from here: `sub!(b)`
runnable/template10.d(528): Deprecation: function `template10.test10.add!(fun).add` function requires a dual-context, which is deprecated
runnable/template10.d(544):        instantiated from here: `add!(fun)`
runnable/template10.d(533): Deprecation: function `template10.test10.add!(fun).add.exec2!(fun, add).exec2` function requires a dual-context, which is deprecated
runnable/template10.d(531):        instantiated from here: `exec2!(fun, add)`
runnable/template10.d(544):        instantiated from here: `add!(fun)`
runnable/template10.d(552): Deprecation: function `template10.test11.getVal!(a).getVal` function requires a dual-context, which is deprecated
runnable/template10.d(574):        instantiated from here: `getVal!(a)`
runnable/template10.d(556): Deprecation: function `template10.test11.getRef!(a).getRef` function requires a dual-context, which is deprecated
runnable/template10.d(578):        instantiated from here: `getRef!(a)`
runnable/template10.d(588): Deprecation: function `template10.N12.sum!(n).sum` function requires a dual-context, which is deprecated
runnable/template10.d(624):        instantiated from here: `sum!(n)`
runnable/template10.d(593): Deprecation: function `template10.N12.inner!(n).inner` function requires a dual-context, which is deprecated
runnable/template10.d(630):        instantiated from here: `inner!(n)`
runnable/template10.d(692): Deprecation: function `template10.test13a.getI!(a).getI` function requires a dual-context, which is deprecated
runnable/template10.d(719):        instantiated from here: `getI!(a)`
runnable/template10.d(731): Deprecation: function `template10.test13b.getC.C.fun!(n).fun` function requires a dual-context, which is deprecated
runnable/template10.d(741):        instantiated from here: `fun!(n)`
---
*/

/********************************************/
void test1a()
{
    int a = 1;

    auto func(alias b)()
    {
        ++a;
        ++b;
    }

    int b = 1;
    func!b;
    assert(a == 2);
    assert(b == 2);

    auto dg = &func!b;
    dg();
    assert(a == 3);
    assert(b == 3);

    template TA(alias i) { int v = i; }
    mixin TA!a;
    assert(v == 3);
}

auto test1b()
{
    auto f(alias a)() { return a; }

    auto f0()
    {
        int a = 10;
        return f!a();
    }
    assert(f0() == 10);
}

template t1c(alias a)
{
    template u(alias b)
    {
        template v(alias c)
        {
            auto sum()
            {
                return a + b + c;
            }
        }
    }
}

void test1c()
{
    int c = 3;
    auto f0()
    {
        int a = 1;
        auto f1()
        {
            int b = 2;
            auto r = t1c!a.u!b.v!c.sum();
            assert(r == 6);
        }
        f1();
    }
    f0();
}

void test1()
{
    test1a();
    test1b();
    test1c();
}

/********************************************/

class C2
{
    int m = 10;

    class K(alias a)
    {
        int n;
        this(int i) { n = i; }
        auto sum()
        {
            return a + m + n;
        }
    }
}

void test2()
{
    if (__ctfe)
        return; // nested classes not yet ctfeable

    int a = 20;
    auto o = new C2;
    auto k = o.new C2.K!a(30);
    assert(k.sum() == 10+20+30);
}

/********************************************/

struct S3
{
    int m;
    auto exec(alias f)()
    {
        f(m);
    }
}

void test3()
{
    int a = 1;

    auto set(alias b)(int c) { b = c; }

    S3(10).exec!(set!a)();
    assert(a == 10);

    auto dg = &S3(20).exec!(set!a);
    dg();
    assert(a == 20);
}

/********************************************/

struct S4
{
    int m;

    auto add(alias a)(int b)
    {
        return a + m + b;
    }

    auto inner(alias a)(int i)
    {
        class I
        {
            int n;
            this() { n = i; }
            auto add(int b)
            {
                return a + n + m + b;
            }
            auto add2(alias c)(int b)
            {
                return a + n + m + b + c;
            }
        }
        return new I;
    }
}

void test4i(T)(T i)
{
    if (__ctfe)
        return; // nested classes not yet ctfeable

    int b = 2;

    assert(i.add(7) == 5+1+6+7);
    auto dg = &i.add;
    assert(dg(8) == 5+1+6+8);

    assert(i.add2!b(7) == 5+1+6+7+2);
    auto dg2 = &i.add2!b;
    assert(dg2(8) == 5+1+6+8+2);
}

auto get4i(int a)
{
    return new S4(5).inner!a(6);
}

void test4()
{
    int a = 1;

    assert(S4(4).add!a(10) == 4+1+10);

    auto dg = &S4(4).add!a;
    assert(dg(11) == 4+1+11);

    auto i = get4i(a);
    test4i(i);

    // type checking
    auto o0 = S4(10);
    auto o1 = S4(11);
    alias T0 = typeof(o0.inner!a(1));
    alias T1 = typeof(o1.inner!a(1));
    static assert(is(T0 == T1));
}

/********************************************/

struct S5
{
    int m;
    auto add(alias f)(int a)
    {
        auto add(int b) { return a + b; }
        return exec2!(f, add)();
    }
    auto exec2(alias f, alias g)()
    {
        return g(f(m));
    }
}

void test5()
{
    int a = 10;
    auto o = S5(1);
    auto fun(int m) { return m + a; }
    assert(o.add!fun(20) == 1+10+20);
}

/********************************************/
// inline tests
// https://issues.dlang.org/show_bug.cgi?id=14730

void test6a()
{
    if (__ctfe)
        return; // nested classes not yet ctfeable

    int i = 10;
    int j, k;

    class A
    {
        auto makeR(alias a)()
        {
            int m = 1;
            class O
            {
                class R
                {
                    pragma(inline, true)
                    final auto inc(alias v)()
                    {
                        ++v;
                        ++m;
                        ++i;
                    }
                    auto getM() { return m; }
                }
            }
            return new O().new R();
        }
    }

    auto a = new A;
    auto r = a.makeR!j();
    r.inc!k();          // inlined
    assert(i == 11);
    assert(k == 1);
    assert(r.getM == 2);
}


auto get6b()
{
    struct S
    {
        auto f0(alias a)()
        {
            auto seta(int m) {
                pragma(inline, true);
                a = m;
            }

            struct T(alias f)
            {
                int m = 10;
                void exec()
                {
                    f(m); // inlined
                }
            }
            return T!seta();
        }
    }
    return S();
}

void test6b()
{
    int a = 1;
    auto s = get6b();
    auto t = s.f0!a();
    t.exec();
    assert(a == 10);
}

struct S6c
{
    int m;
    auto exec(alias f)()
    {
        return f(); // inlined
    }
}

void test6c()
{
    int a;

    auto f0()
    {
        auto f()
        {
            pragma(inline, true);
            return ++a;
        }

        auto s = S6c();
        s.exec!f();
        assert(a == 1);
    }

    f0();
}

void test6()
{
    test6a();
    test6b();
    test6c();
}

/********************************************/

class C7
{
    int m = 10;
    template A(alias a)
    {
        template B(alias b)
        {
            template C(alias c)
            {
                auto sum()
                {
                    return a + b + c + m;
                }
                class K
                {
                    int n;
                    this(int i) { n = i; }
                    auto sum()
                    {
                        return a + b + c + m + n;
                    }
                }
            }
        }
    }
}

void test7()
{
    if (__ctfe)
        return; // nested classes not yet ctfeable

    auto a = 1;
    auto b = 2;
    auto c = 3;

    assert(new C7().A!a.B!b.C!c.sum() == 1+2+3+10);
    assert(new C7().new C7.A!a.B!b.C!c.K(20).sum() == 1+2+3+10+20);
}

/********************************************/

interface I8
{
    int get();
    int sub(alias v)()
    {
        return get() - v;
    }
}

class A8
{
    int m;
    this(int m)
    {
        this.m = m;
    }
    this() {}
}

class B8 : A8, I8
{
    this(alias i)()
    {
        super(i);
    }
    auto add(alias v)()
    {
        return super.m + v;
    }
    int get()
    {
        return m;
    }
    this() {}
}

void test8()
{
    int a = 4;
    int b = 2;
    auto o = new B8;
    o.__ctor!a;
    assert(o.m == 4);
    assert(o.add!b == 4+2);
    assert(o.sub!b == 4-2);
}

/********************************************/

struct A9
{
    int m;
    auto add(alias a)()
    {
        m += a;
    }
}

struct B9
{
    int i;
    A9 a;
    alias a this;
}

struct S9
{
    int j;
    B9 b;
    alias b this;
}

void test9()
{
    int a = 9;
    auto o = S9(1, B9(1, A9(10)));
    o.add!9();
    assert(o.b.a.m == 10+9);
}

/********************************************/

struct S10
{
    int m;
    auto add(alias f)(int a)
    {
        auto add(int b) { return a + b; }
        return exec2!(f, add)();
    }
    auto exec2(alias f, alias g)()
    {
        return g(f(m));
    }
}

void test10()
{
    int a = 10;
    auto o = S10(1);
    auto fun(int m) { return m + a; }
    assert(o.add!fun(20) == 1+10+20);
}

/********************************************/

struct S11
{
    int m;
    S11 getVal(alias a)()
    {
        return this;
    }
    ref getRef(alias a)()
    {
        return this;
    }
}

void test11()
{
    int a;
    auto s = S11(1);

    if (__ctfe)
    {
        // 'this' is currently only
        // returned by ref in __ctfe
    }
    else
    {
        ++s.getVal!a().m;
        assert(s.m == 1);
    }

    ++s.getRef!a().m;
    assert(s.m == 2);
}

/********************************************/

class B12
{
    int m;

    auto sum(alias a)()
    {
        return a + m;
    }

    auto inner(alias a)()
    {
        class I
        {
            int i = 20;
            auto sum()
            {
                return i + a;
            }
        }
        return new I();
    }

    class R(alias a)
    {
        int i = 30;
        auto sum()
        {
            return i + a;
        }
    }
}

class N12
{
    int n;

    auto sum()
    {
        auto o = new B12();
        o.m = 10;
        return o.sum!n();
    }

    auto sumi()
    {
        auto o = new B12();
        return o.inner!n().sum();
    }

    auto sumr()
    {
        auto o = new B12().new B12.R!n;
        return o.sum();
    }
}

void test12a()
{
    int i = 10;
    class A(alias a)
    {
        int sum()
        {
            return a + i;
        }
    }

    class B
    {
        int n;
        int sum()
        {
            return new A!n().sum();
        }
    }

    auto b = new B();
    b.n = 1;
    assert(b.sum() == 11);
}

void test12()
{
    auto o = new N12();
    o.n = 1;
    assert(o.sum() == 11);

    if (!__ctfe) // nested classes not yet ctfeable
    {
        assert(o.sumi() == 21);
        assert(o.sumr() == 31);
        test12a();
    }
}

/********************************************/

auto getC13a()
{
    int i = 10;

    auto makeC() // should be a closure
    {
        int j;
        auto incJ() { ++j; }

        class C
        {
            auto getI(alias a)()
            {
                return i;
            }
        }

        return new C();
    }
    return makeC();
}

auto test13a()
{
    if (__ctfe)
        return; // nested classes not yet ctfeable

    int a;
    auto c = getC13a();

    function()
    {
        // clean stack
        pragma(inline, false);
        long[0x500] a = 0;
    }();

    assert(*cast(size_t*)c.outer != 0); // error: frame of getC.makeC wasn't made a closure
    auto i = c.getI!a(); // segfault
}

void test13b()
{
    if (__ctfe)
        return; // nested classes not yet ctfeable

    auto getC()
    {
        int i = 10; // error: value lost, no closure made

        auto fun(alias a)()
        {
            return i;
        }

        class C
        {
            int n;
            auto getI()
            {
                return fun!n();
            }
        }
        return new C();
    }

    auto c = getC();
    assert(c.getI() == 10);
}

void test13()
{
    test13a();
    test13b();
}

/********************************************/

void test14()
{
    int i;
    struct S(alias a)
    {
        auto get() { return a; }
        auto inc() { ++i; }
    }

    auto f0()
    {
        int a = 10;

        auto s = S!a();
        static assert(typeof(s).sizeof == size_t.sizeof * 2);

        assert(s.get() == 10);
        s.inc();
        assert(i == 1);
    }

    f0();
}

/********************************************/

int runTests()
{
    test1();
    test2();
    test3();
    test4();
    test5();
    test6();
    test7();
    test8();
    test9();
    test10();
    test11();
    test12();
    test13();
    test14();
    return 0;
}

void main()
{
    runTests();          // runtime
    enum _ = runTests(); // ctfe
}
