#!/usr/bin/perl

use v5.14;
use warnings;

use Test::More;

BEGIN {
   plan skip_all => "Future is not available"
      unless eval { require Future };
   plan skip_all => "Future::AsyncAwait >= 0.31_002 is not available"
      unless eval { require Future::AsyncAwait;
                    Future::AsyncAwait->VERSION( '0.31_002' ) };
   plan skip_all => "Syntax::Keyword::Dynamically >= 0.02 is not available"
      unless eval { require Syntax::Keyword::Dynamically;
                    Syntax::Keyword::Dynamically->VERSION( '0.02' ) };

   Future::AsyncAwait->import;
   Syntax::Keyword::Dynamically->import(qw( -async ));

   diag( "Future::AsyncAwait $Future::AsyncAwait::VERSION, " .
         "Syntax::Keyword::Dynamically $Syntax::Keyword::Dynamically::VERSION" );
}

{
   my $var = 1;
   async sub with_dynamically
   {
      my $f = shift;

      dynamically $var = 2;

      is( $var, 2, '$var is 2 before await' );
      await $f;
      is( $var, 2, '$var is 2 after await' );

      return "result";
   }

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

   is( $var, 1, '$var is 1 while suspended' );

   $f1->done;
   is( scalar $fret->get, "result", '$fret for dynamically in async sub' );
   is( $var, 1, '$var is 1 after finish' );
}

# multiple nested scopes
{
   my $var = 1;

   my @f;
   sub tick { push @f, my $f = Future->new; return $f }

   async sub with_dynamically_nested
   {
      dynamically $var = 2;

      {
         dynamically $var = 3;

         await tick();

         is( $var, 3, '$var is 3 in inner scope' );
      }

      is( $var, 2, '$var is 2 in outer scope' );

      await tick();

      is( $var, 2, '$var is still 2 in outer scope' );
   }

   my $fret = with_dynamically_nested();

   is( $var, 1, '$var is 1 while suspended' );

   while( @f ) {
      ( shift @f )->done;
      is( $var, 1, '$var is still 1' );
   }

   $fret->get;
   is( $var, 1, '$var is 1 after finish' );
}

# OP_HELEM_DYN is totally different in async mode
{
   my %hash = my %orig = (
      key    => "old",
      delkey => "gone",
   );
   async sub with_dynamically_helem
   {
      my $f = shift;

      dynamically $hash{key} = "new";
      dynamically $hash{newkey} = "added";

      dynamically $hash{delkey} = "begone!";
      delete $hash{delkey};

      await $f;

      is_deeply( \%hash, { key => "new", newkey => "added" },
         '%hash after await' );

      return "result";
   }

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

   is_deeply( \%hash, \%orig, '%hash while suspended ');

   $f1->done;
   is( scalar $fret->get, "result", '$fret for dynamically helem in async sub' );
   is_deeply( \%hash, \%orig, '%hash after finish' );
}

# dynamically set variables in outer scopes during await
{
   my $var = 1;

   async sub outer
   {
      dynamically $var = 2;
      await inner();

      is( $var, 2, '$var is 2 after await in outer()' );
   }

   my $f1 = Future->new;

   async sub inner
   {
      is( $var, 2, '$var is 2 before await in inner()' );

      await $f1;

      is( $var, 2, '$var is 2 after await in inner()' );
   }

   my $fret = outer();

   is( $var, 1, '$var is 1 while suspended' );

   $f1->done;
   $fret->get;

   is( $var, 1, '$var is 1 after finish' );
}

# captured outer dynamic can be re-captured by later async sub
{
   my $var = 1;
   my %hash = ( key => 3 );

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

   my $fret = do {
      dynamically $var = 2;
      dynamically $hash{key} = 4;

      (async sub {
         await $f1;
         is( $var, 2, '$var is 2 before later await' );
         is( $hash{key}, 4, '$var is 4 before later await' );

         await +(async sub {
            await $f2;
            is( $var, 2, '$var is 2 inside inner async sub' );
            is( $hash{key}, 4, '$var is 4 inside inner async sub' );
         })->();
      })->();
   };

   is( $var, 1, '$var is 1 before $f1->done' );
   is( $hash{key}, 3, '$hash{key} is 3 before $f1->done' );

   $f1->done;
   is( $var, 1, '$var is 1 before $f2->done' );
   is( $hash{key}, 3, '$hash{key} is 3 before $f2->done' );

   $f2->done;
   is( $var, 1, '$var is 1 after $f2->done' );
   is( $hash{key}, 3, '$hash{key} is 3 after $f2->done' );

   $fret->get;
}

# destroy test
{
   my %state;

   package DestroyChecker {
      sub new {
         my $class = shift;
         my $self = bless [ @_ ], $class;
         $state{$self->[0]} = "LIVE";
         return $self;
      }

      sub DESTROY {
         my $self = shift;
         $state{$self->[0]} = "DEAD";
      }
   }

   dynamically my $var = DestroyChecker->new( "orig" );

   my $f1 = Future->new;
   my $fret = (async sub {
      dynamically $var = DestroyChecker->new( "new" );
      await $f1;
   })->();

   is_deeply( \%state, { orig => "LIVE", new => "LIVE" }, '%state initially' );

   $f1->done;
   $fret->get;

   is_deeply( \%state, { orig => "LIVE", new => "DEAD" }, '%state after done' );

   undef $var;

   is_deeply( \%state, { orig => "DEAD", new => "DEAD" }, '%state finally' );
}

done_testing;