use strict;
use warnings;

use Test::More 'no_plan';
use Test::Exception;
use Test::Differences;

use_ok("Parse::Method::Signatures") or BAIL_OUT("$@");

is( Parse::Method::Signatures->new("ArrayRef")->_ident(), "ArrayRef");
is( Parse::Method::Signatures->new("where::Foo")->_ident(), "where::Foo");

is( Parse::Method::Signatures->new("where Foo")->_ident(), undef);

throws_ok {
  Parse::Method::Signatures->new("Foo[Bar")->tc()
} qr/^\QRunaway '[]' in type constraint near '[Bar' at\E/,
  q/Runaway '[]' in type constraint near '[Bar' at/;

throws_ok {
  Parse::Method::Signatures->new("Foo[Bar:]")->tc()
} qr/^\QError parsing type constraint near ':' in 'Bar:' at\E/,
  q/Error parsing type constraint near ':' in 'Bar:' at/;

is( Parse::Method::Signatures->new("ArrayRef")->tc(), "ArrayRef");
is( Parse::Method::Signatures->new("ArrayRef[Str => Str]")->tc(), 'ArrayRef["Str",Str]');
is( Parse::Method::Signatures->new("ArrayRef[Str]")->tc(), "ArrayRef[Str]");
is( Parse::Method::Signatures->new("ArrayRef[0 => Foo]")->tc(), "ArrayRef[0,Foo]");
is( Parse::Method::Signatures->new("ArrayRef[qq/0/]")->tc(), "ArrayRef[qq/0/]");
is( Parse::Method::Signatures->new("Foo|Bar")->tc(), "Foo|Bar");

lives_ok { Parse::Method::Signatures->new('$x')->param() };

throws_ok {
  Parse::Method::Signatures->new('$x[0]')->param()
  } qr/Error parsing parameter near '\$x' in '\$x\[0\]' at /,
  q{Error parsing parameter near '\$x' in '\$x\[0\]' at };


test_param(
  Parse::Method::Signatures->new('$x')->param(),
  { required => 1,
    sigil => '$',
    variable_name => '$x',
    __does => ["Parse::Method::Signatures::Param::Positional"],
  }
);

test_param(
  Parse::Method::Signatures->new('$x!')->param(),
  { required => 1,
    sigil => '$',
    variable_name => '$x',
    __does => ["Parse::Method::Signatures::Param::Positional"],
  }
);

test_param(
  Parse::Method::Signatures->new('$x?')->param(),
  { required => 0,
    sigil => '$',
    variable_name => '$x',
    __does => ["Parse::Method::Signatures::Param::Positional"],
  }
);

test_param(
  Parse::Method::Signatures->new('@x')->param(),
  { required => 1,
    sigil => '@',
    variable_name => '@x',
    __does => ["Parse::Method::Signatures::Param::Positional"],
  }
);

test_param(
  Parse::Method::Signatures->new(':$x')->param(),
  { required => 0,
    sigil => '$',
    variable_name => '$x',
    __does => ["Parse::Method::Signatures::Param::Named"],
  }
);

# ":y($x)" is an important test, as this tests the replacment of PPI's regexp operators
test_param(
  Parse::Method::Signatures->new(':y($x)')->param(),
  { required => 0,
    sigil => '$',
    variable_name => '$x',
    label => 'y',
    __does => ["Parse::Method::Signatures::Param::Named"],
  }
);

test_param(
  Parse::Method::Signatures->new('$x?')->param(),
  { required => 0,
    sigil => '$',
    variable_name => '$x',
    __does => ["Parse::Method::Signatures::Param::Positional"],
  }
);


throws_ok {
  Parse::Method::Signatures->new(':foo( [$x, @y?])')->param(),
} qr/^Cannot have optional parameters in an unpacked-array near '\@y' in '\$x, \@y\?' at /,
  q/Cannot have optional parameters in an unpacked-array near '@y' in '$x, @y?' at /;

throws_ok {
  Parse::Method::Signatures->new(':foo( [$x, :$y])')->param(),
} qr/^Cannot have named parameters in an unpacked-array near ':' in '\$x, :\$y' at /,
  q/Cannot have named parameters in an unpacked-array near ':' in '$x, :$y' at /;

throws_ok {
  Parse::Method::Signatures->new(':foo( [$x, :@y])')->param(),
} qr/^Arrays or hashes cannot be named near '\@y' in '\$x, :\@y' at /,
  q/Arrays or hashes cannot be named near '@y' in '$x, :@y' at /;

throws_ok {
  Parse::Method::Signatures->new(':foo( {$x, :@y])')->param(),
} qr/^Runaway '{}' in unpacked parameter near '{\$x, :\@y' at /,
  q/Runaway '{}' in unpacked parameter near '{$x, :@y' at /;

test_param(
  my $param = Parse::Method::Signatures->new(':foo( {:$x, @y})')->param(),
  { label => 'foo',
    sigil => '$',
    required => 0,
    __does => ['Parse::Method::Signatures::Param::Unpacked::Hash'],
  }
);

throws_ok {
  Parse::Method::Signatures->new('($x = 0xfG)')->signature(),
} qr/^'\)' expected whilst parsing signature near 'G' in '\$x = 0xfG' at/,
  q/')' expected whilst paring signautre near 'G' in '$x = 0xfG' at/;

test_param(
  $param->params->[0],
  { required => 0,
    sigil => '$',
    variable_name => '$x'
  }
);
test_param(
  $param->params->[1],
  { required => 1,
    sigil => '@',
    variable_name => '@y'
  }
);
#test_param(
#  Parse::Method::Signatures->new(':foo( [$x, @y?])')->param(),
#  { required => 1,
#    sigil => '$',
#    variable_name => '$x',
#    label => 'y',
#    __does => ["Parse::Method::Signatures::Param::Named"],
#  }
#);

sub test_param {
  my ($param, $wanted, $msg) = @_;
  local $Test::Builder::Level = 2;

  if (my $isa = delete $wanted->{__isa}) {
    isa_ok($param, $isa, $msg)
      or diag("@{[$param->meta->linearized_isa]}");
  }

  for ( @{ delete $wanted->{__does} || [] }) {
    ok(0 , "Param doesn't do $_" ) && last
      unless $param->does($_)
  }

  my $p = { %$param };
  delete $p->{_trait_namespace};
  delete $p->{_params};
  delete $p->{__MOP__};
  eq_or_diff($p, $wanted, $msg);
}
