package Test::Mock::One;
use warnings;
use strict;

# ABSTRACT: Mock the world with one object

our $VERSION = '0.004';

our $AUTOLOAD;

use overload '""' => '_stringify';

use List::Util 1.33 qw(any);
use Scalar::Util qw(blessed);

sub new {
    my $class = shift;
    return bless({@_}, ref($class) || $class);
}

sub AUTOLOAD {
    my $self = shift;

    my ($call) = $AUTOLOAD =~ /([^:]+)$/;

    if (exists $self->{$call}) {
        my $ref = ref $self->{$call};
        if ($ref eq 'HASH') {
            return $self->new(%{ $self->{$call} });
        }
        elsif ($ref eq 'ARRAY') {
            return $self->new(map { $_ => $self->new() } @{ $self->{$call} });
        }
        elsif ($ref eq 'CODE') {
            return $self->{$call}->(@_);
        }
        elsif ($ref eq 'REF') {
            return ${ $self->{$call} };
        }
        return $self->{$call};
    }
    elsif ($self->{"X-Mock-Strict"}) {
        die sprintf("Using %s in strict mode, called undefined function '%s'",
          __PACKAGE__, $call);
    }
    return $self->new();
}

sub isa {
    my ($self, $class) = @_;

    if (my $isas = $self->{"X-Mock-ISA"}) {
        my $ref = ref $isas;
        if (!$ref && $isas eq $class) {
            return 1;
        }
        elsif ($ref eq 'ARRAY') {
            return 1 if any { $_ eq $class } @$isas;
        }
        elsif ($ref eq 'CODE') {
            return $isas->($class);
        }
        elsif ($ref eq "Regexp") {
            return $class =~ /$isas/;
        }
        return 0;
    }
    elsif (exists $self->{"X-Mock-ISA"} && !defined $self->{"X-Mock-ISA"}) {
        return 0;
    }
    return 1;
}



sub _stringify {
    my ($self) = @_;
    if (my $stringify = $self->{'X-Mock-Stringify'}) {
        return ref $stringify eq 'CODE' ? $stringify->() : $stringify;
    }
    return __PACKAGE__ . " stringified";
}

# Just an empty method to prevent weird AUTOLOAD loops
sub DESTROY { }

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

Test::Mock::One - Mock the world with one object

=head1 VERSION

version 0.004

=head1 SYNOPSIS

    use Test::Mock::One;

    my $mock = Test::Mock::One->new(
        foo      => 'return value',
        bar      => 1,

        hashref  => \{ foo => bar },
        arrayref => \[ foo => bar ],
        code     => sub    { return your_special_function },

    );

    $mock->foo;         # 'return value'
    $mock->bar;         # 1
    $mock->hashref;     # { foo => bar}
    $mock->arrayref;    # [ foo, bar ]
    $mock->code;        # executes your_special_function

=head1 DESCRIPTION

Be able to mock many things with little code by using AUTOLOAD.

=head1 METHODS

=head2 new

Ways to override specific behaviours

=over

=item X-Mock-Strict

Boolean value. Undefined attributes will not be mocked and calling them makes us die.

=item X-Mock-ISA

Mock the ISA into the given class. Supported ways to mock the ISA:

    'X-Mock-ISA' => 'Some::Pkg',
    'X-Mock-ISA' => qr/Some::Pkg/,
    'X-Mock-ISA' => [qw(Some::Pkg Other::Pkg)],
    'X-Mock-ISA' => sub { return 0 },
    'X-Mock-ISA' => undef,

=item X-Mock-Stringify

Tell us how to stringify the object

    'X-Mock-Stringify' => 'My custom string',
    'X-Mock-Stringify' => sub { return "foo" },

=back

=head2 isa

Override the ISA from the object

=head1 AUTHOR

Wesley Schwengle <waterkip@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is Copyright (c) 2017 by Wesley Schwengle.

This is free software, licensed under:

  The (three-clause) BSD License

=cut
