#!/usr/bin/perl

use strict;
use warnings;

use Perl6::Code;
# use Data::Dumper;
use PadWalker;

use Test::More tests => 21;

%Perl6::MultiSub::SUBS = ();
%Perl6::NamedSub::SUBS = ();

sub body (&) { @_ }
sub params {
    [ map { Perl6::Param->new( 'type' => undef, 'name' => $_ ) } @_ ]  
}
sub mksub {
    my ($params, $body) = @_;
    return Sub->new( '$.body' => $body, '$.params' => $params);
}
sub mk_named_sub {
    my ($name, $params, $body) = @_;
    my $sub = Sub->new( '$.name' => $name, '$.body' => $body, '$.params' => $params);
    $Perl6::NamedSub::SUBS{$name} = $sub;
}
sub call_named_sub {
    my ($name, @args) = @_;
    (exists $Perl6::NamedSub::SUBS{$name})
        || die "No sub found called '$name'";
    my $sub = $Perl6::NamedSub::SUBS{$name};
    $sub->do(@args);
    # $sub->return_value;
}
sub mk_multi_sub {
    my ($name, @subs) = @_;
    my $sub = MultiSub->new( '$.name' => $name, '@.subs' => \@subs);
    $Perl6::MultiSub::SUBS{$name} = $sub;
}
sub call_multi_sub {
    my ($name, @args) = @_;
    (exists $Perl6::MultiSub::SUBS{$name})
        || die "No sub found called '$name'";
    my $sub = $Perl6::MultiSub::SUBS{$name};
    $sub->do(@args);
    # $sub->return_value;
}

# see t/80_Code.t
#    sub bind_params { ...

{
    # un-named Sub

    my $sub = mksub params('$name'), body {
            my $sub = shift;
            my %param = $sub->bind_params( @_ );  
            "Hello from $param{'$name'}";        
    };
    isa_ok($sub, 'Sub', 'un-named Sub');
    isa_ok($sub, 'Code', '... Code');
    is( $sub->perl->unboxed, 'sub (Any $name) {...}', '... $sub.perl' );
    is( $sub->arity, 1, '... $sub.arity' );
    is( $sub->name, undef, '... $sub.name' );

    # $sub->do('Stevan');
    is( $sub->do('Stevan'), 'Hello from Stevan', '... got the right return value');
}

{
    # un-named Sub with default parameter value

    my $sub = mksub 
        [ 
            Perl6::Param->new( 'type' => undef, 'name' => '?$name', 'default' => sub{'Flavio'} ),
        ], 
        sub {
            my $sub = shift;
            my %param = $sub->bind_params( @_ );  
            # my $name; bind_params();
            "Hello from $param{'$name'}";        
        };
    isa_ok($sub, 'Sub', 'Sub with default parameter value');
    isa_ok($sub, 'Code', '... Code');
    is( $sub->perl->unboxed, 'sub (Any ?$name = Flavio) {...}', '... $sub.perl' );
    is( $sub->arity, 1, '... $sub.arity' );
    is( $sub->name, undef, '... $sub.name' );

    # $sub->do();
    is($sub->do(), 'Hello from Flavio', '... got the right return value');
}

{
    # un-named Sub with a calculated return value

    my $sub = mksub params('$name', '@others'), body {
            my $sub = shift;
            my %param = $sub->bind_params( @_ );  
            # my ($name, @others); bind_params;
            "Hello from $param{'$name'} and " . (join ", " => @{$param{'@others'}} );
    };
    isa_ok($sub, 'Sub', 'Sub with calculated return value');

    is ($sub->do('Stevan', [ 'autrijus', 'iblech', 'putter' ]),
        'Hello from Stevan and autrijus, iblech, putter', '... got the right return value');
}

{
    my $sub = mksub params('%more'), body {
            my $sub = shift;
            my %param = $sub->bind_params( @_ );  
            # my %more; bind_params;
            join ", " => sort keys %{$param{'%more'}};
    };
    isa_ok($sub, 'Sub');

    is( $sub->do({ foo => 1, bar => 2 }),
        'bar, foo', '... got the right return value');
}

=pod

Can't yet handle &sub params

{
    my $sub = mksub params('$code', '@rest'), body {
        my ($code, @rest); bind_params;
        $code->(@rest);
    };
    isa_ok($sub, 'Perl6::Sub');

    $sub->do(sub { join "|" => @_ }, [ 1, 2, 3, 4 ]);
    is($sub->return_value, '1|2|3|4', '... got the right return value');
}
=cut

# named subs ...

{
    my $sub = mk_named_sub 'length' => params('@a'), body {
            my $sub = shift;
            my %param = $sub->bind_params( @_ );  
            my @a = @{$param{'@a'}};
            # my @a; bind_params;
            return 0 unless @a;
            shift @a;
            return 1 + call_named_sub('length', \@a);
    };

    is( $sub->name, 'length', '... $sub.name' );

    is(call_named_sub('length', [ 1 .. 20 ]), 20, '... called recursive named sub');
}

{
    my $sub1 = mksub(params('@a'), body {
            my $sub = shift;
            my %param = $sub->bind_params( @_ );  
            my @a = @{$param{'@a'}};
            # warn "a only, @a";
            return call_multi_sub('length', \@a, 0);
        });
    my $sub2 = mksub(params('@a', '$acc'), body {
            my $sub = shift;
            my %param = $sub->bind_params( @_ );  
            my @a = @{$param{'@a'}};
            my $acc= $param{'$acc'};
            # warn "a @a, acc $acc";
            return $acc unless @a;
            shift @a;
            return call_multi_sub('length', \@a, $acc + 1);
        });        
    mk_multi_sub 'length' => ( $sub1, $sub2 );    
    
    is(call_multi_sub('length', [ 1 .. 20 ]), 20, '... called recursive multi sub');    
}

{
    # un-named Sub with slurpy parameter 

    my $sub = mksub 
        [ 
            Perl6::Param->new( 'type' => undef, 'name' => '@names' ),
            Perl6::Param->new( 'type' => undef, 'name' => '*@numbers' ),
        ], 
        sub {
            my $sub = shift;
            my %param = $sub->bind_params( @_ );  
            my @names = @{$param{'@names'}}; 
            my @numbers = @{$param{'@numbers'}};
            return ( \@numbers, \@names );        
        };

    # TEST REMOVED - P6 subs can no longer return native ARRAY

    #my ( $x, $y ) = $sub->do( [ qw(a b c) ], 1 .. 5 );
    #is("@$x", '1 2 3 4 5', '... got the right return value');
    #is("@$y", 'a b c', '... got the right return value');
}

{
    # "primitive" example with slurpy @_

    my $output;
    my $sub = Sub->new( 
        '$.name' => 'say',
        '$.params' => 
            [ 
                Perl6::Param->new( 'name' => '*@_' ),
            ], 
        '$.body' => 
            sub {
                my $sub = shift;
                $output = "@_\n";
                # print $output;        
            } 
    );

    $sub->do('"say()" works', '!' );
    is( $output, "\"say()\" works !\n", '... .say() works' );
}

{
    # "primitive" example with hash of parameters

    my $output;
    my $sub = Sub->new( 
        '$.name' => 'say',
        '$.params' => 
            [ 
                Perl6::Param->new( 'name' => '*@param' ),
            ], 
        '$.body' => 
            sub {
                my $sub = shift;
                my %param = $sub->bind_params( @_ );  
                $output = "@{$param{'@param'}}\n";
            } 
    );

    $sub->do('"say()" works', '!' );
    is( $output, "\"say()\" works !\n", '... parameterized .say() works' );
}

