#! /bin/sh
exec perl -x $0 ${1+"$@"};
#!perl

###############################################################################
#
# Purpose:
#   This is the primary front end launcher for programs in the VisIt toolchain.
#   It is separate from the pieces than change on a per-version basis.
#
#   More specifically:
#     Parse out version arguments and determine the version to run.
#     Fall back to legacy launcher if needed.
#     Use argv[0] (i.e. $0) to determine if which program is being run.
#     Determine if someone is trying to use the old "visit -prog" method
#         of launching tools, and if so both warn them and fix things.
#     Keep track of mixing of public and private versions of VisIt.
#     Finally, if all goes well, launch the "internallauncher" which is
#         allowed to contain version-specific pieces.
#
# Note: Place NO version specific code here!
#       Place NOTHING that requires backwards compatibility code here.
#       ...
#       The only exception in this file is that which is just enough to
#       fall back to the pre-version-specific visit launcher script, and
#       by the time you read this note, that should not require changes.
#
# Programmer:  Jeremy Meredith
# Date      :  December  8, 2004
#
# Modifications:
#    Jeremy Meredith, Mon Dec 13 13:38:05 PST 2004
#    It was erroneously grepping for partial version name matches.
#    E.g. it would think 1.4 had an internallauncher if it found
#    one for version 1.4.1.  This caused old versions to fail to run.
#
#    Jeremy Meredith, Fri Jan  7 13:48:45 PST 2005
#    My last fix wasn't completely portable across perl versions.  I fixed it.
#
#    Jeremy Meredith, Tue Apr 19 08:36:36 PDT 2005
#    Made sure both "-convert" and "-visitconvert" work.
#
#    Brad Whitlock, Tue Sep 19 17:48:12 PST 2006
#    Added -mpeg2encode, -composite, -transition.
#
#    Brad Whitlock, Mon Dec 10 14:01:54 PST 2007
#    Added -protocol.
#
#    Jeremy Meredith, Mon Dec 10 16:49:26 EST 2007
#    For versions 1.7 and later, requests for "-v X.Y" will pick the latest
#    patchlevel in the X.Y.z series.  Note that requests for a specific patch
#    level (-v X.Y.Z) or beta versions (e.g. -v X.Yb1 or -v X.Y.Zb) will
#    still be honored here.  (Note that the code makes its own determination
#    about which version request it passes along, so this is no guarantee
#    that a user request for X.Y.Z at the GUI will result in X.Y.Z down the
#    whole chain; at the moment, beta versions propagate properly but public
#    ones always get converted to "-v X.Y".)
#    NOTE: When 1.7 is the latest likely version on any platform, the check
#    for >=1.7 in that test may safely be removed.
#
#    Jeremy Meredith, Fri Dec 14 15:31:48 EST 2007
#    Added a -forceversion command line option which can be used to
#    intelligent override all version selection logic.
#
#    Sean Ahern, Mon Aug  4 10:55:39 EDT 2008
#    Changed how we run perl.
#
#    Jeremy Meredith, Thu Dec 18 15:18:10 EST 2008
#    Quote the $visitdir string by adding backslashes to special
#    characters before passing it into a regular expression.
#
#    Dave Pugmire, Thu May  7 13:46:51 EDT 2009
#    Add support for running visitconvert in parallel.
#
#    Mark C. Miller, Tue Jul  7 18:00:34 PDT 2009
#    Added logic to avoid backtick pwd if possible. That is causing problems
#    on LLNL systems when ANY lscratch filesystem happens to be down and
#    the user may be starting VisIt from ANOTHER lscratch filesystem.
#
#    Mark C. Miller, Wed Jul  8 16:52:17 PDT 2009
#    Alter fix applied above for pwd to use getcwd() instead of $ENV{PWD}.
#
#    Jeremy Meredith, Wed Jan  6 11:30:14 EST 2010
#    If user specified all three components of a non-beta version with -v,
#    (e.g. -v 1.12.0), then we assume they meant to use -forceversion and
#    add that value for them.
#
#    Tom Fogal, Fri Jan  8 12:33:01 MST 2010
#    Fixed launching convert_par.
#
#    Hank Childs, Wed Jan 13 11:19:00 CST 2010
#    Renamed convert_par to visitconvert_par.  (The name "convert" collides
#    with other tools.)
#
#    Eric Brugger, Fri Jan 22 15:08:37 PST 2010
#    I modified the script to only pass -forceversion if the program it
#    was launching was visit.  This is the only program that needs it and
#    other programs do not accept it, such as mpeg2encode.
#
#    Jeremy Meredith, Wed Feb  3 16:00:24 EST 2010
#    Stop supporting legacy versions (pre-1.4.1).
#
###############################################################################


# -----------------------------------------------------------------------------
#                                 Setup
# -----------------------------------------------------------------------------

# Set a secure and reasonable path

use Cwd;

$ENV{PATH} = join ':' , ("$ENV{PATH}","/bin","/usr/bin","/usr/sbin",
                         "/usr/local/bin", "/usr/bsd","/usr/ucb" );

# set base directory
chomp( $progname= `basename $0`);
chomp( $progdir = `dirname $0` );
chomp( $cwd     = getcwd()    );
if (!$cwd) {
    chomp( $cwd     = `pwd`        );
}

# check if we are called via a symbolic link to visit
$link = readlink($0);
if ($link) {
    chomp( $linkname= `basename $link`);
    if ($linkname eq "visit") {
        # Get directory to which link points
        chomp( $linkdir = `dirname $link`);

        for ($linkdir) {
            /^\//  && do { $progdir = $linkdir; last; }; # starts with `/'
            $progdir="$progdir/$linkdir";
        }
    }
}

# Add the program directory to the path!
$ENV{PATH} = join ':' , ("$progdir", "$ENV{PATH}");

for ($progdir) {
    /^\//  && do { $tmpdir = $progdir; last; }; # starts with `/'
    /^\.$/ && do { $tmpdir = $cwd; last; };     # is exactly  `.'
    $tmpdir = "$cwd/$progdir";
}

# strip single . paths
while ($tmpdir =~ s|(/\./)|/|) {}

# compress out remaining /dir/.. forms
while ($tmpdir =~ s|(/[^/]+/\.\.)||) {}

# Note: the above ".."-compression is safe because we already determined we
# had an absolute path, we know the substitution goes left-to-right, and
# we cannot legally .. above the root of the directory tree.  If at some
# point these assumptions change, we can use the following line instead,
# which is more complex but makes sure not to compress out /../.. forms.
#while ($tmpdir =~ s@(/([^/.][^/]*|[^/]*[^/.]|[^/]{3,})/\.\.)@@) {}

chomp( $visitdir = `dirname $tmpdir` );

# -----------------------------------------------------------------------------
#                            Parse the arguments
# -----------------------------------------------------------------------------

# Set some defaults.
$want_version = 0;
$ver          = "";
$beta         = 0;
$using_dev    = 0;

# Parse the arguments 
@visitargs = ();
while (scalar(@ARGV) > 0) {
    $arg = shift @ARGV;
    if ($arg eq "-v")
    {
        $ver  = shift; $ver_set  = 1;
        die "The '-v' version argument requires a value.\n" if (!defined $ver);
    }
    elsif ($arg eq "-forceversion")
    {
        $forceversion  = shift; $forceversion_set  = 1;
        die "The '-forceversion' argument requires a value.\n"
          if (!defined $forceversion);
    }
    elsif ($arg eq "-beta")
    {
        $beta = 1;
    }
    elsif ($arg eq "-dv")
    {
        $using_dev = 1;
    }
    elsif ($arg eq "-version")
    {
        $want_version = 1;
    }
    elsif ($arg eq "-xml2atts"        or
           $arg eq "-xml2avt"         or
           $arg eq "-xml2info"        or
           $arg eq "-xml2makefile"    or
           $arg eq "-xml2projectfile" or
           $arg eq "-xml2plugin"      or
           $arg eq "-xml2python"      or
           $arg eq "-xml2window"      or
           $arg eq "-xml2java"        or
           $arg eq "-xmltest"         or
           $arg eq "-xmledit"         or
           $arg eq "-makemili"        or
           $arg eq "-convert"         or
           $arg eq "-visitconvert"    or
           $arg eq "-silex"           or
           $arg eq "-surfcomp"        or
           $arg eq "-text2polys"      or
           $arg eq "-time_annotation")
    {
        $progname = substr($arg, 1);
        print STDERR "NOTE:  Specifying tools as an argument to VisIt is ";
        print STDERR "no longer necessary.\nIn the future, you should ";
        print STDERR "just run '$progname' instead.\n\n";
    }
    elsif ($arg eq "-visitconvert_par" or
           $arg eq "-convert_par")
    {
        $progname = "visitconvert_par";
    }
    elsif ($arg eq "-mpeg2encode"     or
           $arg eq "-mpeg_encode")
    {
        $progname = substr($arg, 1);
    }
    elsif ($arg eq "-composite")
    {
        $progname = "visit_composite";
    }
    elsif ($arg eq "-transition")
    {
        $progname = "visit_transition";
    }
    elsif ($arg eq "-protocol")
    {
        $progname = "visitprotocol";
    }
    else
    {
        push @visitargs, $arg;
    }
}

# -----------------------------------------------------------------------------
#                        Parse the requested version
# -----------------------------------------------------------------------------
# A forced version: (a) overrides any other -v argument, and (b) makes
# no attempt to parse the version string, which bypasses the rest of
# the intelligent version selection logic in this launcher script.
if ($forceversion_set)
{
    $ver_set = 1;
    $ver = $forceversion;
    if ($progname eq "visit")
    {
        push @visitargs, "-forceversion", $forceversion;
    }
}
elsif ($ver_set)
{
    if (grep /^\d+\.\d+$/, $ver) {
        # e.g. -v 1.7
        ($ver_major = $ver) =~ s/^(\d+)\.\d+$/$1/;
        ($ver_minor = $ver) =~ s/^\d+\.(\d+)$/$1/;
        ($ver_patch = -1);
        ($ver_beta  = -1);
    } elsif (grep /^\d+\.\d+\.\d+$/, $ver) {
        # e.g. -v 1.7.11
        ($ver_major = $ver) =~ s/^(\d+)\.\d+\.\d+$/$1/;
        ($ver_minor = $ver) =~ s/^\d+\.(\d+)\.\d+$/$1/;
        ($ver_patch = $ver) =~ s/^\d+\.\d+\.(\d+)$/$1/;
        ($ver_beta  = -1);
        print STDERR "WARNING: full patchlevel was specified with -v.\n";
        print STDERR "Assuming '-forceversion $ver' was intended.\n";
        if ($progname eq "visit")
        {
            push @visitargs, "-forceversion", $ver;
        }
    } elsif (grep /^\d+\.\d+b$/, $ver) {
        # e.g. -v 1.7b
        ($ver_major = $ver) =~ s/^(\d+)\.\d+b$/$1/;
        ($ver_minor = $ver) =~ s/^\d+\.(\d+)b$/$1/;
        ($ver_patch = -1);
        ($ver_beta  = 0);
    } elsif (grep /^\d+\.\d+b\d+$/, $ver) {
        # e.g. -v 1.7b3
        ($ver_major = $ver) =~ s/^(\d+)\.\d+b\d+$/$1/;
        ($ver_minor = $ver) =~ s/^\d+\.(\d+)b\d+$/$1/;
        ($ver_patch = -1);
        ($ver_beta  = $ver) =~ s/^\d+\.\d+b(\d+)$/$1/;
    } elsif (grep /^\d+\.\d+\.\d+b$/, $ver) {
        # e.g. -v 1.7.11b
        ($ver_major = $ver) =~ s/^(\d+)\.\d+\.\d+b$/$1/;
        ($ver_minor = $ver) =~ s/^\d+\.(\d+)\.\d+b$/$1/;
        ($ver_patch = $ver) =~ s/^\d+\.\d+\.(\d+)b$/$1/;
        ($ver_beta = 0);
    } elsif (grep /^\d+\.\d+\.\d+b\d+$/, $ver) {
        # e.g. -v 1.7.11b3
        ($ver_major = $ver) =~ s/^(\d+)\.\d+\.\d+b\d+$/$1/;
        ($ver_minor = $ver) =~ s/^\d+\.(\d+)\.\d+b\d+$/$1/;
        ($ver_patch = $ver) =~ s/^\d+\.\d+\.(\d+)b\d+$/$1/;
        ($ver_beta  = $ver) =~ s/^\d+\.\d+\.\d+b(\d+)$/$1/;
    }
}


# -----------------------------------------------------------------------------
#                          Find the right version
# -----------------------------------------------------------------------------
# If we have a top-level "exe" directory, then don't bother looking
# for versions to use; this is a development executable.
if (-d "$visitdir/exe")
{
    if ($want_version)
    {
        print STDERR "The version of VisIt in the directory $visitdir/exe/ ".
                     "will be launched.\n";
        exit 0;
    }

    # They may have specified the version, but we need to ignore it
    # because development trees don't have version directories.
    $ver = "";

    # We want to make sure we know if we are trying to launch a public
    # version from under a development version.  Keep track of this.
    # (Note -- don't add it on our own if we're launching a tool.)
    if ($using_dev or $progname eq "visit" or $progname eq "xml2cmake")
    {
        # stick the -dv at beginning of args to ensure it comes
        # before any '-s' option.
        @visitargs_tmp = ();
        push @visitargs_tmp, "-dv", @visitargs;
        @visitargs = @visitargs_tmp;
    }
}
else
{
    # look for the version-specific visit script to determine viable versions
    $visitdirquoted = quote_string($visitdir);
    @exe = <$visitdirquoted/*/bin/internallauncher>;
    @exeversions = map {m|^$visitdirquoted/(.*?)/|; $_ = $1;} @exe;
    $current_version = readlink("$visitdir/current");
    $beta_version    = readlink("$visitdir/beta");

    if ($want_version)
    {
        if (! defined $current_version)
        {
            print STDERR "There is no current version of VisIt.\n";
            exit 1;
        }

        print STDERR "The current version of VisIt is $current_version.\n";
        exit 0;
    }

    if (! $ver_set)
    {
        if ($beta)
        {
            if (! defined $beta_version)
            {
                print STDERR "There is no beta version of VisIt.\n";
                exit 1;
            }
            $ver = $beta_version;
        }
        else
        {
            if (! defined $current_version)
            {
                print STDERR "There is no current version of VisIt.\n";
                exit 1;
            }

            $ver = $current_version;
        }
    }

    # If they requested no minor version (i.e. $ver_patch is -1)
    # then fill it in with the most recent bugfix release.
    # We don't want to attempt this trick with beta versions.
    # This behavior is new for 1.7.
    if ((not ($ver_major == 1 and $ver_minor < 7)) and
        $ver_patch == -1 and $ver_beta == -1)
    {
        @unsorted_matches = grep /^${ver}(\.\d+)?$/, @exeversions;
        sub by_patch_version
        {
            my ($maj1,$min1,$pat1) = split /\./, $a;
            my ($maj2,$min2,$pat2) = split /\./, $b;
            return -1 if (not defined($pat1));
            return +1 if (not defined($pat2));
            return $pat1 <=> $pat2;
        }
        @sorted_matches = sort by_patch_version @unsorted_matches;
        $ver = @sorted_matches[-1];
    }

    # If there was no internal laucher for that version, then either
    # that version wasn't installed, or it is an old version that
    # didn't have a version-specific launcher script.
    if (! grep /^${ver}$/, @exeversions)
    {
        print STDERR "Version $ver of does not exist.\n";
        exit 1;
    }

    # Warn if we mixed public and private development versions.
    if ($using_dev)
    {
        print STDERR "\n";
        print STDERR "WARNING: You are launching a public version of VisIt\n";
        print STDERR "         from within a development version!\n";
        print STDERR "\n";
        push @visitargs, "-dv";
    }

    # The actual visit directory is now version-specific
    $visitdir = "$visitdir/$ver";
}

# -----------------------------------------------------------------------------
#     Set the environment variables needed for the internal visit launcher
# -----------------------------------------------------------------------------
$ENV{VISITVERSION} = $ver;
$ENV{VISITPROGRAM} = $progname;
$ENV{VISITDIR}     = $visitdir;

# -----------------------------------------------------------------------------
#                       Run the internal launcher!
# -----------------------------------------------------------------------------
@visitcmd = ("${visitdir}/bin/internallauncher", @visitargs);
exec @visitcmd or die "Can't execute visit launcher script: $!\n";


# -----------------------------------------------------------------------------
#                          Helper functions
# -----------------------------------------------------------------------------
sub quote_string
{
    my ($str) = @_;
    $str =~ s/([\+\{\}\(\)])/\\$1/g;
    return $str;
}
