package Module::Recursive::Require;

use strict;
use warnings;
use Carp;
use File::Spec;
use File::Find;
use File::Basename;
use UNIVERSAL::require;
use vars qw/$VERSION/;

$VERSION = '0.04';

sub new {
    my $proto      = shift;
    my $class      = ref $proto || $proto;
    my $args_ref   = shift;
    
    # * Default path by $INC[0]
    my $path       
        = $args_ref->{path} || $INC[0];

    # * Default extentions by .pm and .pl
    my $extensions
        = $args_ref->{extensions} || [qw/pm pl/];

    my $self
        = {
            _path        => File::Spec->catfile( $path ),
            _filters     => [],
            _extensions  => $extensions,
            _packages    => undef,
            _first_loads => [],
          };

    return bless( $self, $class );
}

sub first_loads {
    my $self    = shift;
    my @modules = @_;

    $self->{_first_loads} = \@modules;

    return 1;
}

sub add_filter {
    my $self   = shift;
    my $filter = shift;

    push @{ $self->{_filters} }, $filter;

    return 1;
}

# * deprecated!!
sub require_by {
    my $self         = shift;
    my $package_name = shift;

    return $self->require_of( $package_name );
}

sub require_of {
    my $self         = shift;
    my $package_name = shift || croak "require package name!";

    my $modules
         = $self->_get_modules( $package_name ) or return 0;

    unshift( @$modules, @{ $self->{_first_loads} })
        if scalar @{ $self->{_first_loads} };

    my $_required        = {};
    my @required_modules = ();
    REQUIRED:
    for my $module ( @$modules ) {
        next REQUIRED if exists $_required->{$module};
        
        $module->require() or croak $@;
        $_required->{$module} = 1;
        push @required_modules, $module;
    }

    return ( wantarray ) ? @required_modules : \@required_modules;
}

sub _get_modules {
    my $self         = shift;
    my $package_name = shift;

    my $path
         = File::Spec->catfile(
            $self->{_path},
            split( '::', $package_name ),
           );

    find( $self->_make_filter_sub_ref(), $path);

    return $self->{_packages};
}

sub _make_filter_sub_ref {
    my $self   = shift;
    
    my $filters = $self->{_filters};
    my $extensions
         = $self->_scalar2array_ref( $self->{_extensions} );

    return sub {
        my $fullname = $File::Find::name;
        my $filename = $_;

        return 0
            unless ( $self->_has_exts_by($fullname, $extensions) );

        for my $filter ( @$filters ) {
            return 0 if $filename =~ /$filter/;
        }

        # * path to package name
        # ** UNIX OS only.. orz
        my $package_name
            = $self->_get_package_name(
                {
                    fullname => $fullname,
                    libpath  => $self->{_path}
                }
              );

        push @{ $self->{_packages} }, $package_name;

        return 1;
    }
}

sub _get_package_name {
    my $self    = shift;
    my $arg_ref = shift;

    my $fullname = $arg_ref->{fullname};
    my $libpath  = $arg_ref->{libpath};

    my $package_name = undef;
    if ( $fullname =~ m|^$libpath/(.+)\..+$| ) {
        $package_name = $1;
        $package_name =~ s/\//::/g;
    }

    return $package_name;
}

sub _has_exts_by {
    my $self       = shift;
    my $fullpath   = shift;
    my $extensions = shift;
    
    my ($name, $path, $ext)
          = fileparse( $fullpath, @$extensions );

    return ( $ext ) ? 1 : 0;
}

sub _scalar2array_ref {
    my $self = shift;
    my $val  = shift;

    return ( ref $val eq 'ARRAY' ) ? $val : [($val)];
}


1;

=head1 NAME

Module::Recursive::Require - This class require module recursively.

=head1 DESCRIPTION

 # ************************************** before
 use MyApp::Foo;
 use MyApp::Foo::CGI;
 use MyApp::Foo::Mail;
 use MyApp::Foo::Mail::Send;
 
 # use use use use use !!
 
 use MyApp::Foo::Hoge::Orz;

 # ************************************** after
 use Module::Recursive::Require;
 use MyApp::Foo;

 my @required_packages
    = Module::Recursive::Require->new()->require_by('MyApp::Foo'); 

=head1 SYNOPSIS

 use Module::Recursive::Require;
 
 my $r = Module::Recursive::Require->new();
 $r->first_loads(
                    qw/
                          MyApp::Foo::Boo
                      /
                );                          # * It loads first.
 $r->add_filter(qr/^Hoge/);                 # * Don't loaded  qr/^Hoge/
 $r->add_filter(qr/Base.pm$/);              # * Don't loaded  qr/Base.pm$/
 
 my @packages = $r->require_of('MyApp::Foo');

 # * or

 my $packages_array_ref
     = $r->require_of('MyApp::Foo');

=head1 METHOD

=head2 new( \%args )

 %args = (
    path       => '/var/www/my/lib', # * default $INC[0]
    extensions => 'pm'             , # * default "pm" and "pl"
 );

=head2 first_loads( @package_names );

=head2 add_filter(qr/regexp/)

=head2 require_of( 'MyApp::Foo' );

=head2 require_by( 'MyApp::Foo' );

Deprecated. For backwards compatibility only.

=head1 SEE ALSO

L<UNIVERSAL::require>

=head1 AUTHOR

Masahiro Funakoshi <[email protected]>

=cut