die "'CIPRES_PERL_HOME' undefined" if not $ENV{'CIPRES_PERL_HOME'};
my @argv = @ARGV;
use ExtUtils::MakeMaker;
use Config;
use strict;
use warnings;
use Getopt::Long;

# CIPRES_PERL_HOME is the installation root for
# all cipres perl dependencies (and core libraries).
# Typically, this points to $CIPRES_ROOT/lib/perl
my $HOME = &_fix_slashes( $ENV{'CIPRES_PERL_HOME'} );

# build targets, default 'base' target installs prerequisites
# to run cipres services. These are invoked as --base --cpan etc.
my $TARGETS = { 
    'base'      => 1,
    'cpan'      => 0, # updates cpan, for newer installation options
    'bioperl'   => 0, # installs bioperl, big install
    'testsuite' => 0, # to be able to run build tests
    'wx'        => 0, # for gui stuff
    'env'       => 0, # just create script with env vars
};
my $justEnv = 0;
# parse command line arguments
GetOptions(
    'base'      => sub { $TARGETS->{'base'}++      },
    'cpan'      => sub { $TARGETS->{'cpan'}++      }, 
    'bioperl'   => sub { $TARGETS->{'bioperl'}++   },   
    'testsuite' => sub { $TARGETS->{'testsuite'}++ },
    'wx'        => sub { $TARGETS->{'wx'}++        },
    'env'       => sub { \$justEnv++              },
);

# 'Cipres/Config.pm' is a package that functions like 
# ExtUtils::FakeConfig in that it overrides %Config for
# custom installation paths. If it's not in %INC at this
# point, the assumption is that cipres hasn't been set up yet.
if ( not exists $INC{'Cipres/Config.pm'} ) {
    &writeCIPRESConfig( $HOME );
    &writeCIPRESbashrc( $HOME );
    &writeCPANConfig( 'MyConfig.pm' );
    if ( $justEnv ) {
        if ($^O eq 'MSWin32') { 
            open(ENVFILE, "<$HOME/etc/perlvars.bat");
            for (<ENVFILE>) {
                if (m/set\s*(.*)/) {
                    print $1."\n"
                }
            }
        }
        else {
            open(ENVFILE, "<$HOME/etc/perlvars.bashrc");
            for (<ENVFILE>) {
                if (m/\s*(.*);\s*export\s*.*/) {
                    my $envDef = $1;
                    $envDef =~ s/\\ / /g ;
                    print $envDef."\n"
                }
            }
        }
    }
    else {
        my $source_command = $^O eq 'MSWin32' 
                         ? "call $HOME/etc/perlvars.bat" 
                         : "source $HOME/etc/perlvars.bashrc";
        print 
"
        This script was invoked with the wrong environment. 
        I wrote a new configuration file with the right 
        settings. Import these with the following command:

        $source_command

        ...and rerun this script.

";
    }
    exit 0;
}


local $/ = undef;
my $__ENDDB__ = <DATA>; # this <DATA> handle refers to the __DATA__ section at the bottom of this script.
my $prereqs = eval $__ENDDB__;
die "Prerequisites data base corrupted: $@" if $@;

# this is the main loop that iterates over the entries in the __DATA__ section,
# tries to load them, if not present tries to install from ppd or cpan
for my $prereq ( @$prereqs ) {
    next unless $TARGETS->{ $prereq->{'target'} };
    my $mod = $prereq->{'mod'};
    my $version = $prereq->{'version'};
    my $PPDInstall = &makePPDInstallFunc();
    if ( $version ) {
        eval "use $mod $version";
    }
    else {
        eval "require $mod";    
    }
    if ( $@ ) {
        print "Going to install $mod...(because: $@)\n";
        undef($@);
        eval {
            if ( $prereq->{'ppd'} and $PPDInstall and $^O eq 'MSWin32' ) {        
                $PPDInstall->( $prereq->{'ppd'} );
            }
            else {
                &CPANInstall( $prereq );
            }
        };
        if ( $@ ) {
            print "$mod NOK: $@\n";
            undef($@);
        }
        else {
            print "$mod OK: installed succesfully\n";        
        }
    }
    else {
        print "$mod OK: already installed\n";
    }
}

# final report printout
my ( %success, %failure );
for my $prereq ( @$prereqs ) {
    next unless $TARGETS->{ $prereq->{'target'} };
    my $mod = $prereq->{'mod'};
    eval "require $mod";
    if ( $@ ) {
        $failure{$mod}++;
        undef($@);
    }
    else {
        $success{$mod}++;
    }
}
print "\n================================\n";
printf
    "SUCCESS (%s):\n\n%s\n\nFAILURE (%s):\n\n%s",
    scalar( keys( %success ) ),
    join( "\n", keys( %success ) ),
    scalar( keys( %failure ) ),
    join( "\n", keys( %failure ) ),
;
print "\n================================\n";

# attempt to locate binary package managers. PPM
# is for activestate perl, IPM for indigoperl
# (the two are binary compatible)
sub makePPDInstallFunc {
    my $InstallFunc;
    for ( qw(PPM IPM_InstallPkg) ) {
        my $class = "$_";
        eval "require $class";
        if ( not $@ ) {
            $InstallFunc = \&PPM::InstallPackage if $class =~ qr/PPM/;
            $InstallFunc = \&IPM_InstallPkg::InstallPkg if $class =~ qr/IPM/;
            last;
        }
        else {
            undef($@);
        }
    }
    return $InstallFunc ? $InstallFunc : undef;
}

sub CPANInstall {
    my $prereq = shift;
    require CPAN;
    # it looks like CPAN system wide config data is reloaded
    # under some conditions (overwriting MyConfig data in the
    # process). The hack below is to reload custom config.
    use lib "$ENV{'HOME'}/.cpan";
    delete $INC{'Cipres/Config.pm'};
    delete $INC{'CPAN/MyConfig.pm'};
    require 'Cipres/Config.pm';
    require 'CPAN/MyConfig.pm';

    my $mbuildpl_arg      = $CPAN::Config->{'mbuildpl_arg'};
    my $makepl_arg        = $CPAN::Config->{'makepl_arg'};
    my $prefer_installer  = $CPAN::Config->{'prefer_installer'};
    $CPAN::Config->{'prefer_installer'} = $prereq->{'builder'};
    if ( $CPAN::Config->{'prefer_installer'} eq 'MB' ) {
        $CPAN::Config->{'mbuildpl_arg'} .= $prereq->{'flags'} if $prereq->{'flags'};
    }
    else {
        $CPAN::Config->{'makepl_arg'} .= $prereq->{'flags'} if $prereq->{'flags'};            
    }    

    if ( $prereq->{'force'} ) {
        CPAN::Distribution::force( 'install', $prereq->{'cpan'} );
    }
    else {
        CPAN::Shell->install( $prereq->{'cpan'} );
    }
    $CPAN::Config->{'mbuildpl_arg'}     = $mbuildpl_arg;
    $CPAN::Config->{'makepl_arg'}       = $makepl_arg;
    $CPAN::Config->{'prefer_installer'} = $prefer_installer;    
}



sub writeCPANConfig {
    my $outfile = shift;
    mkdir "$ENV{'HOME'}/.cpan";
    mkdir "$ENV{'HOME'}/.cpan/CPAN";
    my $configfile = "$ENV{'HOME'}/.cpan/CPAN/$outfile";
    my $make = $^O eq 'MSWin32' ? 'nmake' : 'make';
    my $bin = {
        'gzip'  => '',
        'tar'   => '',
        'unzip' => '',
        $make   => '',
        'more'  => '',
        'ftp'   => '',
        'ncftp' => '',
    };
    EXE: for my $exe ( keys %$bin ) {
        for my $path ( split $Config::Config{path_sep}, $ENV{'PATH'} ) {
            if ( -X "$path/$exe$Config::Config{exe_ext}" ) {
                $bin->{$exe} = "$path/$exe$Config::Config{exe_ext}";
                next EXE;
            }
        }
    }
    if ( -e $configfile ) {
        rename $configfile, "$configfile.bak";
    }
    my $time = localtime;
    open my $fh, '>', $configfile or die $!;
    print $fh 
"
        # This file was automatically generated by $0 with args '@argv'
        # on $time. Do not edit by hand! All changes will 
        # be lost next time $0 is run.
        # The purpose of this file is to provide CPAN.pm with a 
        # configuration that:
        #	* is not interactive (no scary choices)
        #	* uses a local (tiny) CPAN mirror at:
        #	  file://$HOME/src/CPAN/MIRROR
        #	* sets up prefixes for non-root installs

        \$CPAN::Config = {
        'gzip'                         => '$bin->{'gzip'}',
        'tar'                          => '$bin->{'tar'}',
        'unzip'                        => '$bin->{'unzip'}',
        'make'                         => '$bin->{$make}',
        'pager'                        => '$bin->{'more'}',
        'ftp'                          => '$bin->{'ftp'}',
        'ncftp'                        => '$bin->{'ncftp'}',
        'build_cache'                  => 0,
        'build_dir'                    => '$HOME/src/CPAN/LOCAL/build',
        'cache_metadata'               => 0,
        'cpan_home'                    => '$HOME/src/CPAN/LOCAL/',
        'dontload_hash'                => {  },
        'ftp_proxy'                    => '',
        'http_proxy'                   => '',
        'inactivity_timeout'           => 30,
        'index_expire'                 => 1,
        'inhibit_startup_message'      => 1,
        'keep_source_where'            => '$HOME/src/CPAN/LOCAL',
        'make_arg'                     => '',
        'make_install_arg'             => '',
        'makepl_arg'                   => ' PREFIX=$HOME SITEPREFIX=$HOME VENDORPREFIX=$HOME ',
        'mbuildpl_arg'                 => ' --prefix $HOME ',        
        'mbuild_arg'                   => '',
        'mbuild_install_arg'           => '',
        'mbuild_install_build_command' => '',        
        'no_proxy'                     => '',
        'prerequisites_policy'         => 'follow',
        'scan_cache'                   => 'never',
        'urllist'                      => [ 'file://$HOME/src/CPAN/MIRROR' ],
        };
1;
";
    close $fh;
}

sub _fix_slashes {
    my $path = shift;
    $path =~ tr/\\/\//;
    $path =~ s/\/+/\//g;
    return $path;
}

sub makePERL5LIB {
    my $home = shift;
    my $ciprefix  = &_fix_slashes( $home );
    my $sysprefix = &_fix_slashes( $Config::Config{'prefix'} ); # e.g. '/usr'

    # base
    my $PERL5LIB = "$home/lib";

    # replicate system subtrees, change prefix
    for my $subtree ( qw(sitelibexp archlibexp vendorlibexp privlibexp) ) {
        my $fixed_subtree = &_fix_slashes( $Config::Config{$subtree} );
        $fixed_subtree =~ s/^\Q$sysprefix\E//;
        $PERL5LIB .= $Config{'path_sep'} . $ciprefix . $fixed_subtree;
    }

    return $PERL5LIB;
}

sub writeCIPRESbashrc {
    my $home = shift;
    &writeCIPRESdirs( $home );    
    my $INCLUDES = "$home/etc";  
    my $PERL5LIB = &makePERL5LIB( $home );  
    if ( $^O eq 'MSWin32' ) {
        open my $fh, '>', "$home/etc/perlvars.bat" or die $!;
        print $fh 
"
        :: to import these variables, execute
        :: call $home/etc/perlvars.bat
        set CIPRES_PERL_HOME=$home
        set PERL5LIB=$PERL5LIB;
        set PERL5OPT=-I$INCLUDES -MCipres::Config \%PERL5OPT\%
";
        close $fh; 
    }
    else {
        open my $fh, '>', "$home/etc/perlvars.bashrc" or die $!;
        print $fh 
"
        # to import these variables, execute
        # source $home/etc/perlvars.bashrc
        CIPRES_PERL_HOME=$home; export CIPRES_PERL_HOME
        PERL5LIB=$PERL5LIB; export PERL5LIB
        PERL5OPT=-I$INCLUDES\\ -MCipres::Config\\ \$PERL5OPT; export PERL5OPT
";
        close $fh;
    }
}

sub writeCIPRESConfig {
    my $home = shift;
    &writeCIPRESdirs( $home ); 
    open my $fh, '>', "$home/etc/Cipres/Config.pm" or die $!;
    my $time = localtime;
    print $fh 
"
        package Cipres::Config;
        # do not edit this file, it was autogenerated by
        # $0 on $time with commands '@argv'.
        # use this file to configure perl installs as non-root,
        # with the PERL5OPT=-MCipres::Config environment variable
        # and this file in \@INC.
        require Config;
        my \$MyConfig = tied \%Config::Config;
        \$MyConfig->{'Author'}    = 'Rutger Vos';
        \$MyConfig->{'cf_by'}     = 'CIPRES';
        \$MyConfig->{'cf_email'}  = 'rvosa\@sfu.ca';
        \$MyConfig->{'cf_time'}   = '$time';
        \$MyConfig->{'startperl'} = '$Config::Config{startperl}';
        \$MyConfig->{'perlpath'}  = '$Config::Config{perlpath}';
1;
";
    close $fh;
}

sub writeCIPRESdirs {
    my $home = shift;
    if ( not -d $home ) {
        mkdir $home or die "Can't make cipres home dir '$home': $!";
    }
    for ( qw(etc etc/Cipres bin doc lib src src/CPAN src/CPAN/MIRROR src/CPAN/LOCAL) ) {
        if ( not -d "$home/$_" ) {
            mkdir "$home/$_" or die "Can't make cipres dir '$home/$_': $!";
        }
    }
}

__DATA__

[
#    {
#        'mod'     => 'ExtUtils::MakeMaker',
#        'cpan'    => 'MSCHWERN/ExtUtils-MakeMaker-6.30.tar.gz',
#        'target'  => 'base',
#        'builder' => 'EUMM',
#        'ppd'     => 'http://ppm.activestate.com/PPMPackages/5.8-windows/ExtUtils-MakeMaker.ppd',
#    },
#    {
#        'mod'     => 'ExtUtils::FakeConfig',
#        'cpan'    => 'MBARBON/ExtUtils-FakeConfig-0.07.zip',
#        'target'  => 'base',
#        'builder' => 'EUMM',
#    },
    {
        'mod'     => 'Bundle::CPAN',
        'cpan'    => 'ANDK/Bundle-CPAN-1.852.tar.gz',
        'target'  => 'cpan',
        'builder' => 'EUMM',        
    },
    {
        'mod'     => 'CPAN',
        'cpan'    => 'ANDK/CPAN-1.87.tar.gz',
        'target'  => 'cpan',
        'builder' => 'EUMM',        
    },
    { 
        'mod'     => 'Bio::Tree::TreeI',
        'cpan'    => 'BIRNEY/bioperl-1.4.tar.gz',
        'ppd'     => 'http://bioperl.org/DIST/Bioperl-1.4.ppd',
        'force'   => 1,  
        'target'  => 'bioperl', 
        'builder' => 'EUMM',         
    },
    { 
        'mod'     => 'Exception::Class',
        'cpan'    => 'DROLSKY/Exception-Class-1.23.tar.gz',
        'ppd'     => 'http://ppm.activestate.com/PPMPackages/5.8-windows/Exception-Class.ppd',
        'target'  => 'base',
        'builder' => 'EUMM',
    },
    { 
        'mod'     => 'Exception::Class::TCF',
        'cpan'    => 'RVOSA/Exception-Class-TCF-0.03.tar.gz',
        'target'  => 'base',
        'builder' => 'EUMM',        
    },
    { 
        'mod'     => 'Math::Random',
        'cpan'    => 'GROMMEL/Math-Random-0.68.tar.gz',
        'ppd'     => 'http://ppm.activestate.com/PPMPackages/5.8-windows/Math-Random.ppd',
        'target'  => 'base',
        'builder' => 'EUMM',
    },
    { 
        'mod'     => 'Math::BigInt',
        'cpan'    => 'TELS/math/Math-BigInt-1.77.tar.gz',
        'ppd'     => 'http://ppm.activestate.com/PPMPackages/5.8-windows/Math-BigInt.ppd', 
        'target'  => 'base',
        'builder' => 'EUMM',           
    },
    { 
        'mod'     => 'Scalar::Util',
        'cpan'    => 'GBARR/Scalar-List-Utils-1.14.tar.gz',
        'ppd'     => 'http://ppm.activestate.com/PPMPackages/5.8-windows/List-Utils.ppd',  
        'target'  => 'base', 
        'builder' => 'EUMM',
        'version' => '1.14',         
    },
    { 
        'mod'     => 'Module::Build',
        'cpan'    => 'KWILLIAMS/Module-Build-0.2805.tar.gz',
        'ppd'     => 'http://ppm.activestate.com/PPMPackages/5.8-windows/Module-Build.ppd',  
        'target'  => 'base', 
        'builder' => 'EUMM',                 
    },
#    {
#        'mod'     => 'Module::Install',
#        'cpan'    => 'AUDREYT/Module-Install-0.64.tar.gz',
#        'ppd'     => 'http://ppm.activestate.com/PPMPackages/5.8-windows/Module-Install.ppd',
#        'target'  => 'base',
#        'builder' => 'EUMM',
#    },
    { 
        'mod'     => 'SVG',
        'cpan'    => 'RONAN/SVG-2.33.tar.gz',
        'target'  => 'base',
        'builder' => 'EUMM',                
    },
    { 
        'mod'     => 'Class::Inspector',
        'cpan'    => 'ADAMK/Class-Inspector-1.14.tar.gz',
        'ppd'     => 'http://ppm.activestate.com/PPMPackages/5.8-windows/Class-Inspector.ppd',
        'target'  => 'base',
        'builder' => 'EUMM',         
    },
#    { 
#        'mod'     => 'XML::Simple',
#        'cpan'    => 'GRANTM/XML-Simple-2.14.tar.gz',
#        'ppd'     => 'http://theoryx5.uwinnipeg.ca/ppms/XML-Simple.ppd',  
#        'target'  => 'xml',
#        'builder' => 'EUMM',
#    },
    { 
        'mod'     => 'Log::Dispatch',
        'cpan'    => 'DROLSKY/Log-Dispatch-2.12.tar.gz',
        'ppd'     => 'http://ppm.activestate.com/PPMPackages/5.8-windows/Log-Dispatch.ppd',  
        'target'  => 'base',  
        'builder' => 'EUMM',         
    },        
    { 
        'mod'     => 'Log::Log4perl',
        'cpan'    => 'MSCHILLI/Log-Log4perl-1.06.tar.gz',
        'ppd'     => 'http://theoryx5.uwinnipeg.ca/ppms/Log-Log4perl.ppd',  
        'target'  => 'base',  
        'builder' => 'EUMM',         
    },
    { 
        'mod'     => 'Test::Pod',
        'cpan'    => 'PETDANCE/Test-Pod-1.26.tar.gz',
        'ppd'     => 'http://www.bribes.org/perl/ppm/Test-Pod.ppd',  
        'target'  => 'testsuite',
        'builder' => 'EUMM',          
    },
    { 
        'mod'     => 'Test::Pod::Coverage',
        'cpan'    => 'PETDANCE/Test-Pod-Coverage-1.08.tar.gz',
        'ppd'     => 'http://www.bribes.org/perl/ppm/Test-Pod-Coverage.ppd',
        'target'  => 'testsuite', 
        'builder' => 'EUMM',                
    },
    { 
        'mod'     => 'Test::Distribution',
        'cpan'    => 'SRSHAH/Test-Distribution-1.26.tar.gz', 
        'target'  => 'testsuite',  
        'builder' => 'MB',                          
    },
    {
        'mod'     => 'Alien::wxWidgets',
        'cpan'    => 'MBARBON/Alien-wxWidgets-0.21.tar.gz',
        'builder' => 'MB',
        'flags'   => '--build_wx --source=tar.gz',  
        'ppd'     => 'http://prdownloads.sourceforge.net/wxperl/Wx-0.26-wxmsw2.6.2-win32-u-5.8.6.zip',  
        'target'  => 'wx',        
    },    
    { 
        'mod'     => 'Wx',
        'cpan'    => 'MBARBON/Wx-0.57.tar.gz', 
        'target'  => 'wx',
        'builder' => 'EUMM',            
    },
    { 
        'mod'     => 'Wx::Perl::VirtualTreeCtrl',
        'cpan'    => 'BBC/Wx-Perl-VirtualTreeCtrl-1.015.tar.gz',    
        'target'  => 'wx',
        'builder' => 'EUMM',        
    },    
]
