#!/usr/bin/perl

use v5.14;
use warnings;

use Test::More;

use Future;

use Future::AsyncAwait;

my $orig_cxstack_ix = Future::AsyncAwait::__cxstack_ix;

my $before;
my $after;

async sub identity
{
   await $_[0];
}

# scalar
{
   my $f1 = Future->new;
   my $fret = identity( $f1 );

   isa_ok( $fret, "Future", 'identity() returns a Future' ) and do {
      ok( !$fret->is_ready, '$fret is not immediate for pending scalar' );
   };

   $f1->done( "result" );
   is( scalar $fret->get, "result", '$fret->get for scalar' );
}

# list
{
   my $f1 = Future->new;
   my $fret = identity( $f1 );

   isa_ok( $fret, "Future", 'identity() returns a Future' );

   $f1->done( list => "goes", "here" );
   is_deeply( [ $fret->get ], [qw( list goes here )], '$fret->get for list' );
}

async sub makelist
{
   1, 2, [ 3, await $_[0], 6 ], 7, 8
}

# stack discipline test
{
   my $f1 = Future->new;
   my $fret = makelist( $f1 );

   $f1->done( 4, 5 );

   is_deeply( [ $fret->get ],
              [ 1, 2, [ 3, 4, 5, 6 ], 7, 8 ],
              'async/await respects stack discipline' );
}

# await twice from function
{
   my @futures;
   sub another_f
   {
      push @futures, my $f = Future->new;
      return $f;
   }

   async sub wait_twice
   {
      await another_f();
      await another_f();
   }

   my $fret = wait_twice;
   ok( my $f1 = shift @futures, '$f1 created' );

   $f1->done;
   ok( my $f2 = shift @futures, '$f2 created' );

   $f2->done( "result" );

   is( scalar $fret->get, "result", '$fret->get from double await by func' );
}

# await twice from pad
{
   async sub wait_for_both
   {
      my ( $f1, $f2 ) = @_;
      return await( $f1 ) + await( $f2 );
   }

   my $f1 = Future->new;
   my $f2 = Future->new;

   my $fret = wait_for_both( $f1, $f2 );

   $f1->done( 12 );

   $f2->done( 34 );

   is( scalar $fret->get, 46, '$fret->get from double await by pad' );
}

# failure
{
   my $f1 = Future->new;
   my $fret = identity( $f1 );

   isa_ok( $fret, "Future", 'identity() returns a Future' );

   $f1->fail( "It failed\n" );

   is( $fret->failure, "It failed\n", '$fret->failure for fail' );
}

# die
{
   my $f1 = Future->new;
   async sub dies {
      await $f1;
      die "Oopsie\n";
   }

   my $fret = dies();
   $f1->done;

   is( $fret->failure, "Oopsie\n", '$fret->failure for dies' );
}

# ANON sub
{
   my $func = async sub {
      return await $_[0];
   };

   my $f1 = Future->new;
   my $fret = $func->( $f1 );

   ok( !$fret->is_ready, '$fret is not immediate for pending ANON' );

   $f1->done( "later" );
   is( scalar $fret->get, "later", '$fret->get for ANON' );
}

# ANON sub closure
{
   my $f1 = Future->new;

   my $func = async sub {
      return await $f1;
   };

   my $fret = $func->( $f1 );

   ok( !$fret->is_ready, '$fret is not immediate for pending ANON closure' );

   $f1->done( "later" );
   is( scalar $fret->get, "later", '$fret->get for ANON closure' );
}

# await EXPR puts EXPR in scalar context
{
   my $f1 = Future->new;

   sub yieldcontext { return Future->done( wantarray ); }

   my $func = async sub {
      return await yieldcontext();
   };
   my $fret = $func->();

   is( $fret->get, '', 'await EXPR provides scalar context' );
}

# await in non-async sub is forbidden
{
   my $ok = !eval 'sub { await $_[0] }';
   my $e = $@;

   ok( $ok, 'await in non-async sub fails to compile' );
   $ok and like( $e, qr/Cannot 'await' outside of an 'async sub' at /, '' );
}

{
   my $ok = !eval 'async sub { my $c = sub { await $_[0] } }';

   ok( $ok, 'await in non-async sub inside async sub fails to compile' );
}

is( Future::AsyncAwait::__cxstack_ix, $orig_cxstack_ix,
   'cxstack_ix did not grow during the test' );

done_testing;