use strict;

use File::Spec;

use Test::More tests => 52;

use_ok('Exception::Class');

# There's actually a few tests here of the import routine.  I don't
# really know how to quantify them though.  If we fail to compile and
# there's an error from the Exception::Class::Base class then
# something here failed.
BEGIN
{
    package FooException;

    use vars qw[$VERSION];

    use Exception::Class;
    use base qw(Exception::Class::Base);

    $VERSION = 0.01;

    1;
}

use Exception::Class
  (  'YAE' => { isa => 'SubTestException', alias => 'yae' },

    'SubTestException' => { isa => 'TestException',
                            description => q|blah'\\blah| },

    'TestException',

    'FooBarException' => { isa => 'FooException' },

    'FieldsException' => { isa => 'YAE', fields => [ qw( foo bar ) ] },
    'MoreFieldsException' => { isa => 'FieldsException', fields => [ 'yip' ] },

    'Exc::AsString',

    'Bool' => { fields => [ 'something' ] },

    'ObjectRefs',
    'ObjectRefs2',

    'SubAndFields' => { fields => 'thing',
                        alias  => 'throw_saf',
                      },
  );


$Exception::Class::BASE_EXC_CLASS = 'FooException';
Exception::Class->import( 'BlahBlah' );

use strict;

$^W = 1;

# 2-14: Accessors
{
    eval { Exception::Class::Base->throw( error => 'err' ); };

    isa_ok( $@, 'Exception::Class::Base', '$@' );

    is( $@->error, 'err',
        "Exception's error message should be 'err'" );

    is( $@->message, 'err',
        "Exception's message should be 'err'" );

    is( $@->description, 'Generic exception',
        "Description should be 'Generic exception'" );

    is( $@->package, 'main',
        "Package should be 'main'" );

    my $expect = File::Spec->catfile( 't', 'basic.t' );
    is( $@->file, $expect,
        "File should be '$expect'" );

    is( $@->line, 62,
        "Line should be 62" );

    is( $@->pid, $$,
        "PID should be $$" );

    is( $@->uid, $<,
        "UID should be $<" );

    is( $@->euid, $>,
        "EUID should be $>" );

    is( $@->gid, $(,
        "GID should be $(" );

    is( $@->egid, $),
        "EGID should be $)" );

    ok( defined $@->trace,
        "Exception object should have a stacktrace" );
}

# 15-23 : Test subclass creation
{
    eval { TestException->throw( error => 'err' ); };

    isa_ok( $@, 'TestException' );

    is( $@->description, 'Generic exception',
        "Description should be 'Generic exception'" );

    eval { SubTestException->throw( error => 'err' ); };

    isa_ok( $@, 'SubTestException' );

    isa_ok( $@, 'TestException' );

    isa_ok( $@, 'Exception::Class::Base' );

    is( $@->description, q|blah'\\blah|,
        q|Description should be "blah'\\blah"| );

    eval { YAE->throw( error => 'err' ); };

    isa_ok( $@, 'SubTestException' );

    eval { BlahBlah->throw( error => 'yadda yadda' ); };

    isa_ok( $@, 'FooException');

    isa_ok( $@, 'Exception::Class::Base');
}


# 24-29 : Trace related tests
{
    ok( ! Exception::Class::Base->Trace,
        "Exception::Class::Base class 'Trace' method should return false" );

    eval { Exception::Class::Base->throw( error => 'has stacktrace', show_trace => 1 ) };
    like( $@->as_string, qr/Trace begun/,
          "Setting show_trace to true should override value of Trace" );

    Exception::Class::Base->Trace(1);

    ok( Exception::Class::Base->Trace,
        "Exception::Class::Base class 'Trace' method should return true" );

    eval { argh(); };

    ok( $@->trace->as_string,
        "Exception should have a stack trace" );

    eval { Exception::Class::Base->throw( error => 'has stacktrace', show_trace => 0 ) };

    unlike( $@->as_string, qr/Trace begun/,
	    "Setting show_trace to false should override value of Trace" );

    my @f;
    while ( my $f = $@->trace->next_frame ) { push @f, $f; }

    ok( ( ! grep { $_->package eq 'Exception::Class::Base' } @f ),
        "Trace should contain frames from Exception::Class::Base package" );
}

# 29-30 : overloading
{
    Exception::Class::Base->Trace(0);
    eval { Exception::Class::Base->throw( error => 'overloaded' ); };

    is( "$@", 'overloaded',
        "Overloading in string context" );

    Exception::Class::Base->Trace(1);
    eval { Exception::Class::Base->throw( error => 'overloaded again' ); };

 SKIP:
    {
        skip( "Perl 5.6.0 is broken.  See README.", 1 ) if $] == 5.006;

        my $re = qr/overloaded again.+eval {...}\('Exception::Class::Base', 'error', 'overloaded again'\)/s;

        my $x = "$@";
        like( $x, $re,
              "Overloaded stringification should include a stack trace" );
    }
}

# 32-33 - Test using message as hash key to constructor
{
    eval { Exception::Class::Base->throw( message => 'err' ); };

    is( $@->error, 'err',
        "Exception's error message should be 'err'" );

    is( $@->message, 'err',
        "Exception's message should be 'err'" );
}

# 34
{
    {
	package X::Y;

	use Exception::Class ( __PACKAGE__ );

	sub xy_die () { __PACKAGE__->throw( error => 'dead' ); }

	eval { xy_die };
    }

    is( $@->error, 'dead',
        "Error message should be 'dead'" );
}

# 35 - subclass overriding as_string

sub Exc::AsString::as_string { return uc $_[0]->error }

{
    eval { Exc::AsString->throw( error => 'upper case' ) };

    is( "$@", 'UPPER CASE',
        "Overriding as_string in subclass" );
}

# 36-37 - fields

{
    eval { FieldsException->throw( error => 'error', foo => 5 ) };

    can_ok( $@, 'foo');

    is( $@->foo, 5,
        "Exception's foo method should return 5" );
}

# 38-41 - more fields.
{
    eval { MoreFieldsException->throw( error => 'error', yip => 10, foo => 15 ) };

    can_ok( $@, 'foo');

    is( $@->foo, 15,
        "Exception's foo method should return 15" );

    can_ok( $@, 'yip');

    is( $@->yip, 10,
        "Exception's foo method should return 10" );
}

sub FieldsException::full_message
{
    return join ' ', $_[0]->message, "foo = " . $_[0]->foo;
}

# 42 - fields + full_message

{
    eval { FieldsException->throw (error => 'error', foo => 5) };

    like( "$@", qr/error foo = 5/,
          "FieldsException should stringify to include the value of foo" );
}

# 43 - truth
{
    Bool->do_trace(0);
    eval { Bool->throw( something => [ 1, 2, 3 ] ) };

    ok( $@,
        "All exceptions should evaluate to true in a boolean context" );
}

# 44 - single arg constructor
{
    eval { YAE->throw( 'foo' ) };

    ok( $@,
        "Single arg constructor should work" );

    is( $@->error, 'foo',
        "Single arg constructor should just set error/message" );
}

# 45 - no refs
{
    ObjectRefs2->NoRefs(1);

    eval { Foo->new->bork2 };
    my $exc = $@;

    my @args = ($exc->trace->frames)[1]->args;

    ok( ! ref $args[0],
        "No references should be saved in the stack trace" );
}

# 46 - no object refs (deprecated)
{
    ObjectRefs->NoObjectRefs(1);

    eval { Foo->new->bork };
    my $exc = $@;

    my @args = ($exc->trace->frames)[1]->args;

    ok( ! ref $args[0],
        "No references should be saved in the stack trace" );
}

# 47-52 - aliases
{
    eval { throw_saf 'an error' };
    my $e = $@;

    ok( $e, "Throw exception via convenience sub (one param)" );
    is( $e->error, 'an error', 'check error message' );

    eval { throw_saf error => 'another error', thing => 10 };
    $e = $@;

    ok( $e, "Throw exception via convenience sub (named params)" );
    is( $e->error, 'another error', 'check error message' );
    is( $e->thing, 10, 'check "thing" field' );
}

sub argh
{
    Exception::Class::Base->throw( error => 'ARGH' );
}

package Foo;

sub new
{
    return bless {}, shift;
}

sub bork
{
    my $self = shift;

    ObjectRefs->throw( 'kaboom' );
}

sub bork2
{
    my $self = shift;

    ObjectRefs2->throw( 'kaboom' );
}
