#!/usr/bin/perl
use strict;
use Config;

# window to the stars (WTTS) installer for Linux/Mac

# currently text only : but I will work on that ;)

# requires: perl, cp, curl (or wget), unzip (standard stuff!) etc.

my $script_version = '0.11';

# V0.08 12/06/2014 (WTTS 0.39)  
# V0.09 14/09/2014 : workaround for crappy http server at Bonn
# V0.10 01/04/2015 : force curl to use -L (argh!)
# V0.11 21/04/2015 : move to Cambridge
# V0.12 13/09/2017 : use STARS-0.40 perl module
#
# Note: if you have cpanm installed, the installer will
#       try to use it to install required perl modules.
#       Otherwise, the installer will install local::lib
#       first and use your system perl.
#

# to install on linux with ubuntu/mint run:
#
# sudo apt-get install gnuplot gfortran perl cpanminus cmake wget
# wget http://www.ast.cam.ac.uk/~rgi/code/wtts/installer
# chmod +x installer
# ./installer local
# echo "export PATH=\$PATH:~/bin" >> ~/.bashrc

$|=1; # set autoflush of stdout
setup_colour(); # use colour if available

print "
####################################################################
# Window To The Stars installer script for Linux/Mac. Version $script_version #
####################################################################

";

# get the current working directory
my $pwd=local_getpwd();
my $ppwd=parentdir($pwd); # and its parents
if(!defined $pwd)
{
    print "Cannot proceed if I do not know the present working directory (pwd) : please email with a bug report\n"; 
    exit 1;
}

# current path
my $path=$ENV{'PATH'};

# root URL for WTTS files
my $rooturl='http://www.ast.cam.ac.uk/~rgi/code/wtts';

# home directory
my $home=$ENV{'HOME'};

# locations of standard commands
my %cmds=(
    downloader=>`/usr/bin/curl --version`=~/curl \d/ ? '/usr/bin/curl -L --progress-bar -O' : `/usr/bin/wget --version`=~/GNU Wget/ ? '/usr/bin/wget' : undef,
    unzip=>'/usr/bin/unzip',
    cp=>'/bin/cp',
    rm=>'/bin/rm',
    gmake=>'/usr/bin/make',
    cmake=>'/usr/bin/cmake',
    nohump=>'/usr/bin/nohup'
    );

# do we want to use cpanm?
my $usecpanm = `cpanm -V`=~/^cpanm \(App\:\:cpanminus\)/?1:0;
print $usecpanm ? "Using cpanm for perl module installations\n" : "Perl module installations will be done without cpanm\n";

# force local install?
my $forcelocal = "@ARGV"=~/local/?1:0;

# temporary directory, make it and go there
# NB this should be /tmp/...
my $tmp='/tmp';
my $tmp_installer=$tmp.'/wtts_installer';
makedir($tmp_installer);
openlog($tmp_installer);
changedir($tmp_installer);
welcome();

my $ncpus=ncpus();

# get perl version
my $perlv;
my $perl5lib;
{
    my $x=execcmd('perl -v');
    $perlv = ($x=~/This is perl, v(\S+) /)[0] // ($x=~/v(\d+\.\d+\.\d+)/)[0] // undef;
    if(!defined $perlv)
    {
	Dprint("Failed to determine perl version\n",1);
	exit(10);
    }
}

$perl5lib=make_perl5lib($perlv);

Dprint("Perl5lib $perl5lib\n",0);

# output directories
my $bindir=$ENV{'HOME'}.'/bin';
my $answer=$forcelocal ? $bindir : question("You need to choose a binary ('executable') directory - where should it be? (must be in your \$PATH) [$bindir]");
$bindir=$answer if(defined($answer));
Dprint("bindir=$bindir\n",0);

my $datadir=$ENV{'HOME'}.'/wtts';
$answer=$forcelocal ? $datadir : question("Where would you like to install WTTS and TWIN data files? [$datadir]");
$datadir=$answer if(defined($answer));
Dprint("datadir=$datadir\n",0);

# append this to $perl5lib
$perl5lib.=":$datadir";

# determine fortran compiler
my $fort=determine_default_fortran_compiler();
$answer=$forcelocal ? $fort : question("What is your fortran compiler? [$fort]");
$fort=$answer if(defined($answer));
Dprint("fort=$fort\n",0);

map
{
    $_=checkdir($_);
    makedir($_);
}($bindir,$datadir);

Dprint("WTTS bin directory : $bindir\n",1);
Dprint("WTTS data file directory : $datadir\n",1);
Dprint("Fortran compiler : $fort\n",1);

# Check for Perl modules
Dprint("Checking for required Perl modules from CPAN...\n",1);

# modules in order, format is: 
# '<n> perl::name','CPAN URL'
# where they are sorted by <n>, and "use perl::name" is the perl usage
my %modules=(

    '1 ExtUtils::PkgConfig','ftp://ftp.cpan.org/pub/CPAN/authors/id/X/XA/XAOC/ExtUtils-PkgConfig-1.15.tar.gz',
    '2 ExtUtils::Depends','ftp://ftp.cpan.org/pub/CPAN/authors/id/X/XA/XAOC/ExtUtils-Depends-0.308.tar.gz',
    '3 Glib','ftp://ftp.cpan.org/pub/CPAN/authors/id/X/XA/XAOC/Glib-1.305.tar.gz',
    '4 Gtk2','ftp://ftp.cpan.org/pub/CPAN/authors/id/X/XA/XAOC/Gtk2-1.2491.tar.gz',
    '5 Gtk2::Ex::NoShrink','http://www.cpan.org/authors/id/K/KR/KRYDE/Gtk2-Ex-NoShrink-4.tar.gz',
    
    '6.0 File::Tail','ftp://ftp.cpan.org/pub/CPAN/authors/id/M/MG/MGRABNAR/File-Tail-0.99.3.tar.gz',
    '6.1 File::Copy','',
    '6.2 File::Basename','',
    '6.3 File::ReadBackwards','ftp://ftp.cpan.org/pub/CPAN/authors/id/U/UR/URI/File-ReadBackwards-1.05.tar.gz',
    
    '7 Time::HiRes','ftp://ftp.cpan.org/pub/CPAN/authors/id/Z/ZE/ZEFRAM/Time-HiRes-1.9726.tar.gz',
    
    '8 Math::Trig','ftp://ftp.cpan.org/pub/CPAN/authors/id/Z/ZE/ZEFRAM/Math-Complex-1.59.tar.gz',

    '9 Compress::Zlib','ftp://ftp.cpan.org/pub/CPAN/authors/id/P/PM/PMQS/IO-Compress-2.064.tar.gz',
    '10 File::Path','ftp://ftp.cpan.org/pub/CPAN/authors/id/D/DL/DLAND/File-Path-2.09.tar.gz',
    '10.1 File::Which','ftp://ftp.cpan.org/pub/CPAN/authors/id/A/AD/ADAMK/File-Which-1.09.tar.gz',
    '11 Archive::Zip','ftp://ftp.cpan.org/pub/CPAN/authors/id/P/PH/PHRED/Archive-Zip-1.37.tar.gz',
    '12 Proc::ProcessTable','ftp://ftp.cpan.org/pub/CPAN/authors/id/J/JW/JWB/Proc-ProcessTable-0.50.tar.gz',
    '13 Unix::Processors','ftp://ftp.cpan.org/pub/CPAN/authors/id/W/WS/WSNYDER/Unix-Processors-2.042.tar.gz',
    '14 Config::General','ftp://ftp.cpan.org/pub/CPAN/authors/id/T/TL/TLINDEN/Config-General-2.56.tar.gz',
    
    '15 Sys::Info::Base','ftp://ftp.cpan.org/pub/CPAN/authors/id/B/BU/BURAK/Sys-Info-Base-0.7803.tar.gz',
    '15.1 Test::Sys::Info','ftp://ftp.cpan.org/pub/CPAN/authors/id/B/BU/BURAK/Test-Sys-Info-0.20.tar.gz',
    # 15.2 should be set below
    #'15.3 Convert::Binary::C','ftp://ftp.cpan.org/pub/CPAN/authors/id/M/MH/MHX/Convert-Binary-C-0.76.tar.gz',
    '15.4 Sys::Info','ftp://ftp.cpan.org/pub/CPAN/authors/id/B/BU/BURAK/Sys-Info-0.78.tar.gz',
    
    '16 Carp','ftp://ftp.cpan.org/pub/CPAN/authors/id/Z/ZE/ZEFRAM/Carp-1.3301.tar.gz',
    
    '19 rob_misc','http://www.ast.cam.ac.uk/~rgi/code/wtts/rob_misc-0.13.tar.gz',
    '20 STARS','http://www.ast.cam.ac.uk/~rgi/code/wtts/STARS-0.40.tar.gz',
	     );

# require a driver for sys::info
if($Config{osname}=~/linux/i || $Config{osname}=~/nix/i)
{
    # Linux/Unix (same?)
    $modules{'15.2 Sys::Info::Driver::Linux'}='ftp://ftp.cpan.org/pub/CPAN/authors/id/B/BU/BURAK/Sys-Info-Driver-Linux-0.7903.tar.gz';
}
elsif($Config{osname}=~/darwin/i || $Config{osname}=~/osx/i)
{
    # OSX
    $modules{'15.2 Sys::Info::Driver::OSX'}='ftp://ftp.cpan.org/pub/CPAN/authors/id/B/BU/BURAK/Sys-Info-Driver-OSX-0.7958.tar.gz';
}
else
{
    print "I don't know which Sys::Info::Driver:: module to install for operating system \"$Config{osname}\" : please contact Rob to fix this!\n";
    exit(12);
}

# force installation of local::lib if we're using a system perl, i.e. not perlbrew
my $uselocallib;
my $useperlbrew = `perlbrew version`=~/App\:\:perlbrew/ ?1:0;
if($usecpanm && !$useperlbrew)
{
    print "perlbrew is not being used : adding local::lib to the dependency list\n";
    $modules{'0 local::lib'}='local::lib'; # cpanm will find it
    $uselocallib=1;
}
else
{
    $uselocallib=0;
}

# make environment prefix : not required for perlbrew
my $env= !$useperlbrew ? "env PERL5LIB=$perl5lib " : '';

my $M = $uselocallib ? "-Mlocal::lib " : '';

# loop over required modules
for my $mod (sort {$a<=>$b} keys(%modules))
{
    # get url for download, just in case
    my $url=$modules{$mod};

    # remove version string to get pure module name
    $mod=~s/^\d+(?:\.\d+)?\s+//;
    Dprint("Check module $mod\n",1);

    # perl string to check for module
    my $Mlocal = $M." -M$mod ";
        
    # run perl to do the check
    my $x=execcmd("$env perl $Mlocal -e1 2>\&1");
    Dprint($x,1);

    # check result
    if($x=~/Can\'t locate (\S+) in/)
    {
	# save module name
	my $y=$1;
	
	print color('red'),"Cannot locate requried perl module $y\n",color('reset');
	$y=~s/\.pm$//; # strip .pm

	# warn user
	print "$y must be installed before I can continue (it is at CPAN http://search.cpan.org/search?query=$y&mode=all)\n";
	
	# if we have a url (hope so!) try an install
	if(defined($url))
	{
	    my $answer=$forcelocal ? 'Y' : question( "I could try to install for you from $url - would you like me to try? (y/N)\n");

	    if($answer=~/^[yY]$/)
	    {
		$answer=$forcelocal ? 'L' : question("Would you like to install globally (as root) or locally (as you)?\n (g/L)");

		if($answer=~/^[gG]$/)
		{
		    # install globally
		    my $w=execcmd('whoami');
		    chomp $w;
		    if($w ne 'root')
		    {
			print "To install perl modules globally you must be root - please log in as the superuser and then run this script again, or install the perl modules as a local user (see http://www.ast.cam.ac.uk/~rgi/code/wtts/INSTALL).\n";
			exit(13);
		    }
		    print "OK, trying ... \n";
		    # this *should* install the module, provided there are not other problems
		    # such as missing dependencies
		    $url=~m!.*/(\S+)\.tar\.gz$!;
		    my $modname=$1;
		    my $modgz="$1.tar.gz";
		    
		    if(download_targz($url,$modgz))
		    {
			execcmd("cd $tmp_installer ; tar -xzf $modgz; cd $tmp_installer/$modname; perl Makefile.PL; make; make install");
		    }
		  
		    # install will fail if the file is not gzipped : this is ok!
		}
		else
		{
		    # install locally
		    $url=~m!.*/(\S+)\.tar\.gz$!;
		    my $modname=$1;
		    my $modgz="$modname.tar.gz";
		    
		    if($usecpanm)
		    {
			# use cpanm to install, ignore test results, get from ast.cam.ac.uk if required
			if($url=~/astro\.uni-bonn/||
			   $url=~/ast\.cam/)
			{
			    print "Install from rob's tar gz at $modgz\n";
			    
			    if(download_targz($url,$modgz))
			    {
				execcmd("cd $tmp_installer; cpanm --notest $modgz");
			    }	
			}
			else
			{
			    # do not download manually : use cpanm to get the module
			    execcmd("cpanm --notest $mod");
			}
		    }
		    else
		    {
			my $prefix="$home/lib/perl";
			print "Installing locally from tar.gz (perl prefix $prefix)\n";
			
			if(download_targz($url,$modgz))
			{
			    execcmd("cd $tmp_installer; tar -xzf $modgz; cd $tmp_installer/$modname; $env PREFIX=$prefix perl Makefile.PL PREFIX=$prefix; $env PREFIX=$prefix make; $env PREFIX=$prefix make install");
			}
		    }	  
		}
		    

		# test if the installation failed
		my $x=execcmd("$env perl $Mlocal -e1 2>\&1");
		 
		if($x=~/Can\'t locate (\S+) in/)
		{
		    print "Installation of the module failed! Sorry :(\n";

		    print "Suggestions:\nOften, to install a module I will need to build some code with a C or C++ compiler. This requires the -devel or \"dev\" packages to be installed on your system. For example, if you're trying to install Glib(2) or Gtk(2), please install the appropriate glib-devel, glib2-devel, Gtk-devel, Gtk2-devel package as well.\n\nSometimes the module you are trying to install requires other modules to be installed first. While I make every effort to determine the correct dependencies, things do sometimes go wrong when code is updated or on different architectures. I suggest you email the file $tmp/wtts_installer.log to rgi\@ast.cam.ac.uk so that Rob can have a look and try to find a solution.\n\nThanks for helping!\n";
		    exit(14);
		}
	    }
	    else
	    {
		print("OK - but you must install the $y module manually for WTTS to work!\n",1);
		exit(15);
	    }
	}
    }
    else
    {
	Dprint("Module $mod ok\n",1);
    }
}

Dprint("You seem to have the required Perl modules\n",1);

# check for gnuplot
{
    my $r=`gnuplot --version`;
    my $v;
    if($r=~/gnuplot (\S+) patchlevel (\S+)/)
    {
	$v=$1.'.'.$2;
    }
    elsif($r=~/gnuplot (\S+)/)
    {
	$v=$1;
    }
    else
    {
	print "Could not deterine gnuplot version : do you have it installed in your \$PATH ?\n";
	exit(16);
    }
    print "Found gnuplot version $v\n";
    if($v<4.4)
    {
	print "Please install the latest gnuplot (at least V4.4!)\n";
	exit(17);
    }
}

# check for make and cmake
{
    if(`make --version`=~/GNU Make (\S+)/)
    {
	print "Found GNU Make version $1\n";
    }
    else
    {
	print "Could not determine GNU Make version : do you have it installed?\d\n";
	exit(18);
    }
    if(`cmake --version`=~/cmake version (\S+)/)
    {
	print "Found cmake version $1\n";
    }
    else
    {
	print "Could not determine cmake version : do you have it installed?\d\n";
	exit(19);
    }
}

# get stars.perlscript
Dprint("Getting stars perlscript...\n",1);
unlink 'stars.perlscript';
execcmd("cd $tmp_installer; $cmds{downloader} $rooturl/stars.perlscript");
checkfile("$tmp_installer/stars.perlscript");
execcmd("cd $tmp_installer; $cmds{cp} stars.perlscript $bindir/stars.pl");
chmod 0755, "$bindir/stars.pl";

# make wtts executor script
my $exec_script="$bindir/wtts";
if((-e $exec_script) && (-d $exec_script))
{
    # already exists (due to dodgy directory naming set by the user) try this
    $exec_script="$bindir/runwtts";
}

if(-e $exec_script)
{
    print "Warning : exec script already exists! I will try to overwrite...\n";
}

open(FP,'>',$exec_script)||die("cannot open $exec_script for writing");
print FP "\#!/bin/bash
$env $cmds{nohup} /usr/bin/env perl $M $bindir/stars.pl webserver=0 histograms=0 TWINroot=$datadir evcoderoot=$datadir vb=0 \"\$\@\" 
\#>/dev/null 2>/dev/null
";
close FP;
chmod 0755, $exec_script;

# get TWIN, build with $fort (do this in the data dir)
print "Getting TWIN\n";
unlink 'TWIN.zip';
execcmd("cd $tmp_installer; $cmds{downloader} $rooturl/TWIN.zip");
checkfile("$tmp_installer/TWIN.zip");
execcmd("cd $tmp_installer; $cmds{cp} TWIN.zip $datadir");
changedir( $datadir);
execcmd("cd $datadir; $cmds{unzip} -o TWIN.zip");

print "Making TWIN, please wait...\n";
execcmd("cd $datadir; rm -rf build; mkdir -p build; cd build; $cmds{cmake} ..; $cmds{gmake} clean; $cmds{gmake} FORT=$fort -j$ncpus");
print "Finished\n";

# done, clean up, go back to PWD
changedir($pwd);
execcmd("$cmds{rm} -rf $tmp_installer");

print "Window To The Stars should now be installed\nTry it with the command $exec_script\n";

exit(0);

sub changedir
{
    my $d=shift;
    chdir $pwd;
    chdir $d;
    `cd $pwd; cd $d`;
    my $p=`pwd`; chomp $p;
    Dprint("Change to dir $d (`pwd` =$p ; ENV PWD=".$ENV{'PWD'}.")\n",0);
}

sub makedir
{
    my $d=shift;
    chdir $pwd;
    mkdir $d;
}

sub question
{
    print color('cyan'),$_[0],color('reset'),"\n";
    Dprint("Asking question \"$_[0]\"\n",0);
    my $x=<STDIN>;
    $x=~s/^\s+//o; $x=~s/\s+$//o;
    Dprint("Answer is \"$x\"\n",0);
    return $x ne '' ? $x : undef;
}

sub checkdir
{
    my $d=shift;
    $d=~s!^./!$pwd/!;
    $d=~s!^../!$ppwd/!;
    $d=~s!//!/!go;
    return $d;
}

sub checkfile
{
    if(-s $_[0] < 100)
    {
	Dprint("Failed to get $_[0] from $rooturl/$_[0] - OOPS\n",1);
	exit(20);
    }
}

sub determine_default_fortran_compiler
{
    # somehow try to find a fortran compiler
    my $vb=0;
    
    my @fcompilers=('gfortran','ifort','g95','sunf95');

    # first, try to run the compiler to get version information
    foreach my $fc (@fcompilers)
    {
	my $x=execcmd("$fc -v 2>\&1");
	Dprint("$fc -v -> $x\n",1) if($vb);
	chomp $x;
	if((!($x =~/command not found/))&&(!($x=~/^\s*$/)))
	{
	    Dprint("Use $fc from version\n",1) if($vb);
	    return($fc);
	}
    }
    
    # try whereis
    foreach my $fc (@fcompilers)
    {
	my $x=execcmd("whereis -b $fc 2>/dev/null");
	$x=~s/$fc\://;
	chomp $x;
	Dprint("Whereis $fc -> $x\n",1) if($vb);

	if($x ne '')
	{
	    Dprint("Use $fc from whereis ($x)\n",1) if($vb);
	    return($fc);
	}
    }

    # try locate
    foreach my $fc (@fcompilers)
    {
	my $x=execcmd("locate $fc 2>/dev/null");
	chomp $x;
	if($x ne '')
	{
	    Dprint("Use $fc from locate\n",1) if($vb);
	    return($fc);
	}
    }

    Dprint("Failed to find any fortran compiler :( Please install one (e.g. @fcompilers ?)\n",1);

}

sub parentdir
{
    my $d=shift;
    $d=~s!(.*)/.*!$1!;
    return $d;
}

sub execcmd
{
    # use backticks to execute cmd
    my $cmd=shift;
    if($cmd=~/$cmds{downloader} (\S+);/)
    {
	print "Downloading ",color('green'),$1,color('reset'),"\n";
    }
    Dprint("Exec: ".color('yellow')."\"$cmd\" (`pwd` = ".`pwd`." ENV PWD=".$ENV{'PWD'}.")".color('reset')."\n",0);
    my $result=`$cmd`;
    Dprint("Command result \"".color('magenta').$result.color('reset')."\"\n",0);
    return $result;
}

sub Dprint
{
    # debug print string $s to logfile
    my $s=shift;
    my $screen=shift; # if true, output to the screen as well 
    print LOG $s;
    print STDOUT $s ;#if($screen);
}

sub openlog
{
    # open log file
    my $tmp_installer=shift;
    open(LOG,">$tmp/wtts_installer.log")||die("cannot open $tmp_installer/wtts_installer.log logfile for output : please check $tmp_installer exists\n");

    # output (perhaps) useful information
    print LOG
"
Start WTTS installer log at ".time()."
pwd=$pwd
ppwd=$ppwd
path=$path
rooturl=$rooturl
home=$home
wget=$cmds{downloader}
unzip=$cmds{unzip}
cp=$cmds{cp}
rm=$cmds{rm}
gmake=$cmds{gmake}
cmake=$cmds{cmake}
nohup=$cmds{nohup}
";

    # force flushing
    my $old_fh = select(LOG);
    $| = 1;
    select($old_fh);
}

sub make_perl5lib
{
    # make PERL5LIB string
    my $v=shift;
    my $h="$home/lib/perl";
    return "$ENV{PERL5LIB}:$h/lib/perl5/site_perl/$v:$h/lib/perl/$v:$h/share/perl/$v:$h/$v";
}

sub welcome
{
    Dprint("Welcome to the Window To The Stars (WTTS) installer
(for Linux and Mac)

This script tries to install WTTS for you. However, every system
is different so it may fail. In the event of failure, please

1) Check the WTTS website http://www.ast.cam.ac.uk/~rgi/window.html
2) Check the FAQ http://www.ast.cam.ac.uk/~rgi/window-FAQ.html
3) Email Rob Izzard (see the website for current email address) and attach the file $tmp/wtts_installer.log 

Note that if you use Micro\$oft Windows you should consult the instructions
at http://www.ast.cam.ac.uk/~rgi/window-WTTS32.html and NOT use this
installer.

At questions you are usually given options, e.g. y/N. The default (which
will be activated if you just press 'return') is in upper case or in square brackets [..].

",1);
}


sub ncpus
{
    # count number of cpus or return 1
    my $n=0;
    my $cpufile='/proc/cpuinfo';
    if(-f $cpufile)
    {
	open(CPU_COUNT,'<'.$cpufile);
	while(<CPU_COUNT>)
	{
	    $n++ if(/processor/o);
	}
	close CPU_COUNT;
    }
    
    Dprint("Found $n CPUs\n",0);
    
    return defined $n && $n>0 ? $n : 1;
}


sub local_getpwd
{
    # several ways to find the present working directory
    my $pwd = $ENV{'PWD'};

    if(!(defined $pwd))
    {
	$pwd = `pwd`;
	chomp $pwd;
    }

    return $pwd;
}

sub setup_colour
{
    # try to use Term::ANSIColor
    eval "use Term::ANSIColor;";
    # otherwise set up dummy subroutine
    if($@)
    { 
	eval "sub color{} ";
    }
}

sub file_is_gzipped
{
    # check if $filename is a gzip file : if so return 1, if not 0
    # return 0 also if it doesn't exist
    # 
    # code taken from the File::Type module
    my $filename = $_[0];
    my $fh = IO::File->new($filename) || return 0;
    my $data;
    $fh->read($data, 16*1024);
    $fh->close;
    my $ret = $data=~m[^\037\213] ? 1 : 0;
    
    return $ret;
}

sub download_targz
{
    # wrapper to download a tar gz file to $tmp_installer
    # ... will try 5 times 
    my $url = $_[0];
    my $modgz = $_[1];
    my $count = 0;
    my $file = $tmp_installer.'/'.$modgz;
    while($count<5)
    {
	print "Try to unlink $file\n";
	unlink $file;
	execcmd("cd $tmp_installer; $cmds{downloader} $url"); 
	return 1 if(file_is_gzipped($file)==1);
	$count++;
    }
    print "Failed to download tar.gz file from $url !!!\n";
    return 0;
}
