#!/usr/bin/perl
=head MP3 Deduplicator Directory Class

=cut #-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
#===============================================================================
#                               MODULES AND PRAGMAS
#===============================================================================
package MP3_Dedupe_Dir; 
use strict; use warnings;
use File::Spec; # Provides rel2abs sub for getting fully-qualified dir names.
use Cwd;
use MP3_Dedupe_Fileset;
#===============================================================================
#                                  VARIABLES
#===============================================================================
my $TRUE  = 1;
my $FALSE = 0;
#===============================================================================
#                               PRIVATE SUBROUTINES
#===============================================================================
my $divide_files_and_dirs = sub { #---------------------------------------------
=head NOTES
 note1: Don't expect we'll see any situations where a file is !-f and !-d
        but we will check for that just in case. Such a file might be
        some weird special file we'd want to handle by hand.
=cut #--------------------------------------------------------------------------
    my $self  = shift;
    my @list  = @{ $$self{"list"} };
    my $OLD_DIR = getcwd;
    my $dir = $$self{"name"};
    chdir $dir;
    my @files = grep {   -f $_ && ! -d $_ } @list;
    my @dirs  = grep {              -d $_ } @list;
    my @weird = grep { ! -f $_ && ! -d $_ } @list; #note1
    chdir $OLD_DIR;
    $$self{"files"} = \@files;
    $$self{"dirs" } = \@dirs;
    if( @weird ){
        warn "The following files in \"$dir\" do not pass a -d or -f test: " .
            join( ', ', map { "\"" . $_ . "\"" } @weird )                    . 
            " PWD is: " . getcwd() . "\n"                                    ;
    }
    return $TRUE;
};

my $read = sub { #--------------------------------------------------------------
    my $self = shift;
    my $name = $$self{"name"};
    opendir( my $DH, $name );
    $$self{"list"} = [ grep { $_ ne '.' && $_ ne '..' } readdir $DH ];
    closedir( $DH );
    $self->$divide_files_and_dirs();
    return $TRUE;
};

my $branch = sub { #------------------------------------------------------------
    my ( $nf, $nd ) = @_; # number of files, number of directories
    if( $nf == 0 && $nd  > 0 ){ 
        return $TRUE; }
    else{ 
        return $FALSE; }
};

my $empty = sub { #-------------------------------------------------------------
    my ( $nf, $nd ) = @_; # number of files, number of directories
    if( $nf == 0 && $nd == 0 ){ 
        return $TRUE; }
    else{ 
        return $FALSE; }
};

my $general_is = sub { #--------------------------------------------------------
    my $self   = shift;
    my $if_sub = shift;
    my $num_list  = @{ $$self{"list"}  };
    my $num_files = @{ $$self{"files"} };
    my $num_dirs  = @{ $$self{"dirs"}  };
    my $sum_list  = $num_files + $num_dirs;
    die "Special files detected in dir \"". $$self{"name"} ."\" " .
        "list " . $num_list . ", files " . $num_files . ", dirs ". $num_dirs .
        "\n" 
        if $sum_list != $num_list;
    if( $if_sub->( $num_files, $num_dirs ) ){
        return $TRUE;
    }
    else{ return $FALSE; }
};

my $general_get_array = sub { #-------------------------------------------------
    my ( $self, $member ) = @_;
    return @{ $$self{ $member } };
};

my $general_get_num = sub { #---------------------------------------------------
    my ( $self, $member ) = @_;
    return scalar ( @{ $$self{$member} } );
};

#===============================================================================
#                               PUBLIC SUBROUTINES
#===============================================================================
sub give_name { #---------------------------------------------------------------
    my $self         = shift;
    my ( $dir_name ) = @_;
    $dir_name = File::Spec->rel2abs( $dir_name );
    if( ! -d $dir_name ){ return $FALSE; }
    $$self{"name"}   = $dir_name || return $FALSE;
    return $TRUE;
}

sub get_name { #----------------------------------------------------------------
    my $self = shift;
    return $$self{"name"};
}

sub get_list { #----------------------------------------------------------------
    my $self = shift;
    $self->$general_get_array( "list" );
}

sub get_num_list { #------------------------------------------------------------
    my $self = shift;
    $self->$general_get_num( "list" );
}

sub get_files { #---------------------------------------------------------------
    my $self = shift;
    $self->$general_get_array( "files" );
}

sub get_num_files { #-----------------------------------------------------------
    my $self = shift;
    $self->$general_get_num( "files" );
}

sub get_dirs { #---------------------------------------------------------------
    my $self = shift;
    $self->$general_get_array( "dirs" );
}

sub get_num_dirs { #------------------------------------------------------------
    my $self = shift;
    $self->$general_get_num( "dirs" );
}

sub is_branch { #---------------------------------------------------------------
=head NOTES
 note1: Why does the $general_is call here not require a dereferencing -> ?
=cut
    my $self = shift;
    return $self->$general_is( $branch ); #note1
}

sub is_empty { #----------------------------------------------------------------
=head NOTES
 note1: Why does the $general_is call here not require a dereferencing -> ?
=cut
    my $self = shift;
    return $self->$general_is( $empty ); #note1
}

sub new { #---------------------------------------------------------------------
=head NOTES
 note1: Best practice for constructors from the Camel book. Helps inheritance.
        That said, we're unlikely to be inheriting anything here.
=cut #--------------------------------------------------------------------------
    my $class = shift; #note1
    my ( $dir_name ) = @_;
    my $obj = { };
    bless $obj, $class;
    my $is_dir = defined $dir_name ? $obj->give_name( $dir_name ) : $FALSE;
    $obj->$read( $dir_name ) if $is_dir;
    return $obj;
}

sub analyze { #-----------------------------------------------------------------
=head NOTES
 note1: The fileset and files lists are somewhat redundant, but having fileset 
        replace files would require retooling the MP3_Dedupe_Dir class to create 
        and use its MP3_Dedupe_Fileset automatically. Not worth it to refactor.
 note2: We're only giving the fileset object files. It can do nothing with
        directories so there is little point in giving it those.
=cut 
    my $self = shift;
    my $files = MP3_Dedupe_Fileset->new( $self->get_name, $self->get_files );
    $$self{"fileset"} = $files; #note2
    #note1
    return $TRUE;
}

sub get_fileset { #-------------------------------------------------------------
    my $self = shift;
    return $$self{"fileset"};
}
#-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
#                                 END OF CLASS
#-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
1;
