use v6.e.PREVIEW;
use Test;

plan 6;

my $ast;
my $deparsed;
my $raku;
sub ast(RakuAST::Node:D $node --> Nil) {
    $ast      := $node;
    $deparsed := $node.DEPARSE;
    $raku     := 'use experimental :rakuast; ' ~ $node.raku;
    diag $deparsed.chomp;
}

subtest 'creating an empty role' => {
    # role Frobnicator { }
    ast RakuAST::Role.new(
      scope => 'my',
      name  => RakuAST::Name.from-identifier('Frobnicator'),
    );
    is-deeply $deparsed, 'my role Frobnicator { }', 'deparse';

    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        my $role := EVAL($it);
        is $role.^name, 'Frobnicator', "$type: role gets correct name";
        isa-ok $role.new, $role, "$type: does the role auto-pun";

        my $class := RakuAST::Class.new(
          scope  => 'my',
          name   => RakuAST::Name.from-identifier('Zippo'),
          traits => (
            RakuAST::Trait::Does.new($ast),
          )
        ).EVAL;

        is $class.^name, 'Zippo', "$type: class gets correct name";
        my $object := $class.new;
        isa-ok $object, $class, "$type: can it be instantiated";
        todo "needs work on consuming the role" if $type eq 'Str' | 'Raku';
        does-ok $class, $role, "$type: did the role get consumed";
    }
}

subtest 'creating a role with an attribute' => {
    # my role A { has $.a = 42 }
    ast RakuAST::Role.new(
      scope => 'my',
      name  => RakuAST::Name.from-identifier('A'),
      body  => RakuAST::RoleBody.new(
        body => RakuAST::Blockoid.new(
          RakuAST::StatementList.new(
            RakuAST::Statement::Expression.new(
              expression => RakuAST::VarDeclaration::Simple.new(
                scope       => "has",
                sigil       => '$',
                twigil      => '.',
                desigilname => RakuAST::Name.from-identifier('a'),
                initializer => RakuAST::Initializer::Assign.new(
                  RakuAST::IntLiteral.new(42)
                )
              )
            )
          )
        )
      )
    );
    is-deeply $deparsed, q:to/CODE/.chomp, 'deparse';
my role A {
    has $.a = 42
}
CODE

    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        my $role := EVAL($it);
        is $role.^name, 'A', "$type: role gets correct name";
        my $none := $role.new;
        isa-ok $none, $role, "$type: does the role auto-pun (1)";
        is-deeply $none.a, 42, "$type: did the attribute get initialized (1)";

        my $one := $role.new(a => 666);
        isa-ok $one, $role, "$type: does the role auto-pun (2)";
        is-deeply $one.a, 666, "$type: did the attribute get initialized (2)";
    }
}

subtest 'creating a role that does another role' => {
    # my role B does Positional { }
    ast RakuAST::Role.new(
      scope  => 'my',
      name   => RakuAST::Name.from-identifier('B'),
      traits => (
        RakuAST::Trait::Does.new(
          RakuAST::Type::Simple.new(
            RakuAST::Name.from-identifier("Positional")
          )
        ),
      )
    );
    is-deeply $deparsed, 'my role B does Positional { }', 'deparse';

    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        my $role := EVAL($it);
        is $role.^name, 'B', "$type: role gets correct name";
        is-deeply $role.^roles, (Positional,),
          "$type: can we see the roles it does";
        my $none := $role.new;
        isa-ok $none, $role, "$type: does the role auto-pun";
    }
}

subtest 'creating a role that does a parameterized role' => {
    # my role C does Rational[Int,Int] { }
    ast RakuAST::Role.new(
      scope  => 'my',
      name   => RakuAST::Name.from-identifier('C'),
      traits => (
        RakuAST::Trait::Does.new(
          RakuAST::Type::Parameterized.new(
            base-type => RakuAST::Type::Simple.new(
              RakuAST::Name.from-identifier("Rational")
            ),
            args      => RakuAST::ArgList.new(
              RakuAST::Type::Simple.new(
                RakuAST::Name.from-identifier("Int")
              ),
              RakuAST::Type::Simple.new(
                RakuAST::Name.from-identifier("Int")
              )
            )
          )
        ),
      )
    );
    is-deeply $deparsed, 'my role C does Rational[Int, Int] { }', 'deparse';

    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        my $role := EVAL($it);
        is $role.^name, 'C', "$type: role gets correct name";
        is-deeply $role.^roles, (Rational[Int,Int],Real,Numeric),
          "$type: can we see the roles it does";
        my $zero := $role.new;
        isa-ok $zero, $role, "$type: does the role auto-pun";
        ok $zero == 0, "$type: did we get a rational 0";
        ok $role.new(1,1) == 1, "$type: did we get a rational 1";
        ok $role.new(1,3) == 1/3, "$type: did we get a rational 1/3";
    }
}

subtest 'creating a parameterized role' => {
    # my role D[$a = 42] { method a { $a } }
    ast RakuAST::Role.new(
      scope => 'my',
      name  => RakuAST::Name.from-identifier('D'),
      body  => RakuAST::RoleBody.new(
        body => RakuAST::Blockoid.new(
          RakuAST::StatementList.new(
            RakuAST::Statement::Expression.new(
              expression => RakuAST::Method.new(
                name => RakuAST::Name.from-identifier("a"),
                body => RakuAST::Blockoid.new(
                  RakuAST::StatementList.new(
                    RakuAST::Statement::Expression.new(
                      expression => RakuAST::Var::Lexical.new('$a')
                    )
                  )
                )
              )
            )
          )
        )
      ),
      parameterization => RakuAST::Signature.new(
        parameters => (
          RakuAST::Parameter.new(
            target  => RakuAST::ParameterTarget::Var.new(:name<$a>),
            default => RakuAST::IntLiteral.new(42)
          ),
        )
      )
    );

    is-deeply $deparsed, q:to/CODE/.chomp, 'deparse';
my role D[$a = 42] {
    method a {
        $a
    }
}
CODE

    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        my $role := EVAL($it);
        is $role.^name, 'D', "$type: role gets correct name";
        is-deeply $role.^roles, (), "$type: can we see the roles it does";
        my $none := $role.new;
        isa-ok $none, $role, "$type: does the role auto-pun (1)";
        is-deeply $none.a, 42, "$type: did the default value get set";
        my $one := $role.^parameterize(666).new;
        isa-ok $one, $role, "$type: does the role auto-pun (2)";
        is-deeply $one.a, 666, "$type: did the value get set";
    }
}

subtest 'creating a parameterized role with a type capture' => {
    # my role E[::T] { method a (T:D $a) { $a } }
    ast RakuAST::Role.new(
      scope => 'my',
      name  => RakuAST::Name.from-identifier('E'),
      body  => RakuAST::RoleBody.new(
        body => RakuAST::Blockoid.new(
          RakuAST::StatementList.new(
            RakuAST::Statement::Expression.new(
              expression => RakuAST::Method.new(
                name      => RakuAST::Name.from-identifier("a"),
                signature => RakuAST::Signature.new(
                  parameters => (
                    RakuAST::Parameter.new(
                      type => RakuAST::Type::Definedness.new(
                        base-type => RakuAST::Type::Simple.new(
                          RakuAST::Name.from-identifier("T")
                        ),
                        definite  => True
                      ),
                      target => RakuAST::ParameterTarget::Var.new(:name<$a>)
                    ),
                  )
                ),
                body      => RakuAST::Blockoid.new(
                  RakuAST::StatementList.new(
                    RakuAST::Statement::Expression.new(
                      expression => RakuAST::Var::Lexical.new("\$a")
                    )
                  )
                )
              )
            )
          )
        )
      ),
      parameterization => RakuAST::Signature.new(
        parameters => (
          RakuAST::Parameter.new(
            type-captures => (
              RakuAST::Type::Capture.new(
                RakuAST::Name.from-identifier("T")
              ),
            ),
            default       => RakuAST::Type::Simple.new(
              RakuAST::Name.from-identifier("Str")
            )
          ),
        )
      )
    );
    is-deeply $deparsed, q:to/CODE/.chomp, 'deparse';
my role E[::T = Str] {
    method a (T:D $a) {
        $a
    }
}
CODE

    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        my $role := EVAL($it);
        is $role.^name, 'E', "$type: role gets correct name";
        is-deeply $role.^roles, (), "$type: can we see the roles it does";
        my $none := $role.new;
        isa-ok $none, $role, "$type: does the role auto-pun (1)";
        is-deeply $none.a("foo"), "foo", "$type: value returned (1)";
        todo ":D not yet being applied in type check";
        dies-ok { $none.a(Str) }, "$type: does uninstantiated type die (1)";
        dies-ok { $none.a(666) }, "$type: does incorrect type die (1)";
        my $one := $role.^parameterize(Int).new;
        isa-ok $one, $role, "$type: does the role auto-pun (2)";
        is-deeply $one.a(42), 42, "$type: value returned (2)";
        todo ":D not yet being applied in type check";
        dies-ok { $one.a(Int) }, "$type: does uninstantiated type die (2)";
        dies-ok { $one.a("foo") }, "$type: does incorrect type die (2)";
    }
}

# vim: expandtab shiftwidth=4
