#!/usr/bin/perl -w
#
# Regina - A Normal Surface Theory Calculator
# Python Startup Script
#
# Copyright (c) 2002-2021, Ben Burton
# For further details contact Ben Burton (bab@debian.org).
#
# This is the main wrapper script to start the Python interpreter and import
# the Regina calculation engine, i.e., the module 'regina'.
#
# DO NOT EDIT THIS FILE DIRECTLY.  It has been automatically generated
# by the build system and any changes will be overwritten.  Try editing
# regina-python.in instead.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# As an exception, when this program is distributed through (i) the
# App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or
# (iii) Google Play by Google Inc., then that store may impose any
# digital rights management, device limits and/or redistribution
# restrictions that are required by its terms of service.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with this program; if not, write to the Free
# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
# MA 02110-1301, USA.

use strict;
use Cwd 'abs_path';
use File::Basename;

# --- Constants. ---

# The program name and directory.
my $prog_name = $0;
my $prog_dir = abs_path(dirname($prog_name));

# Default verbosity level.
my $default_verbosity = 1;

# Local configuration files.
my $home = $ENV{HOME};
if (! $home) {
    $home = `echo ~`;
}
my $python_conf = $home . "/.regina-python";

# --- Variables. ---

my $quiet = 0;
my $verbose = 0;
my $interactive = 0;
my $autoImport = 1;
my $regina_home = '';
my $python_lib_dir = '';
my $script = '';
my @script_args;

# --- Parse the command-line options. ---

my $readingScriptArgs = 0;
foreach my $arg (@ARGV) {
    if ($readingScriptArgs) {
        push @script_args, $arg;
        next;
    }

    if ($arg eq '-q' or $arg eq '--quiet') {
        $quiet = 1;
        $verbose = 0;
        next;
    }

    if ($arg eq '-v' or $arg eq '--verbose') {
        $quiet = 0;
        $verbose = 1;
        next;
    }

    if ($arg eq '-i' or $arg eq '--interactive') {
        $interactive = 1;
        next;
    }

    if ($arg eq '-n' or $arg eq '--nolibs') {
        print STDERR "I: The --nolibs option is no longer used, since Regina has dropped support\n";
        print STDERR "   for its ancient \"python libraries\" facility.  The Python language itself\n";
        print STDERR "   offers better and more standardised ways of importing your own code.\n\n";
        next;
    }

    if ($arg eq '-a' or $arg eq '--noautoimport') {
        $autoImport = 0;
        next;
    }

    if ($arg =~ /^-/) {
        &usage("Unrecognised option: $arg");
    }

    # We must be onto the script.
    $script = $arg;
    $readingScriptArgs = 1;
}

if ($interactive and not $script) {
    &usage("The interactive option (-i, --interactive) can only be used with a script.");
}

# --- Determine the python executable.

# In the absence of any user-specified options, use the python
# command that corresponds to the original build environment.

my $python_cmd = "@Python_EXECUTABLE@";
my $sandboxed = @PYTHON_SANDBOXED@;
if (not -e $python_cmd) {
    &warn("W: The python command used during the build was: $python_cmd.\n");
    &warn("W: This command does not appear to be available now in the runtime environment.\n");
    $python_cmd = '';
}

# --- Determine the installation type.

my $install_type = "@REGINA_INSTALL_TYPE@";
if ( -f "$prog_dir/cmake_install.cmake" and -f "$prog_dir/../CMakeCache.txt") {
    $install_type = 'Source';
}

&info("I: Running from installation type: $install_type\n");

# --- Determine the underlying operating system.
my $os = "@CMAKE_SYSTEM_NAME@";
&info("I: Running on operating system: $os\n");

# --- Set up the environment.

# On Windows, we need to make sure all our DLLs are on the path.
if ($install_type eq 'Windows') {
    &extendEnvVar('PATH', $prog_dir);
}

# --- Extract variables from the configuration file.

if (open(CONF, $python_conf)) {
    my @lines = <CONF>;
    chomp @lines;
    close(CONF);

    foreach my $line (@lines) {
        if ($line =~ /^\s*$/ or $line =~ /^\s*#/) {
            next;
        }
        if ($line =~ /^\s*([A-Za-z_]+)\s*=\s*$/) {
            &warn("W: Variable $1 is not assigned a value in $python_conf.\n");
            next;
        }
        if ($line =~ /^\s*([A-Za-z_]+)\s*=\s*(\S(.*\S)?)\s*$/) {
            # We have a configuration variable.
            if ($1 eq 'REGINA_VERBOSITY') {
                &setVerbosity($2);
            } elsif ($1 eq 'REGINA_PYTHON') {
                &setPythonCmd($2);
            } elsif ($1 eq 'REGINA_PYLIBDIR') {
                &setPythonLibDir($2);
            } elsif ($1 eq 'REGINA_HOME') {
                &setReginaHome($2);
            } else {
                &warn("W: Unknown variable $1 in $python_conf.\n");
            }
            next;
        }
        &warn("W: Bad line in $python_conf:\n$line\n");
    }
}

# --- Extract variables from the environment. ---

if ($ENV{REGINA_VERBOSITY}) {
    &setVerbosity($ENV{REGINA_VERBOSITY});
}
if ($ENV{REGINA_HOME}) {
    &setReginaHome($ENV{REGINA_HOME});
}
if ($ENV{REGINA_PYTHON}) {
    &setPythonCmd($ENV{REGINA_PYTHON});
}
if ($ENV{REGINA_PYLIBDIR}) {
    &setPythonLibDir($ENV{REGINA_PYLIBDIR});
}

# --- Check that the current options are reasonable. ---

my $prefix = "@CMAKE_INSTALL_PREFIX@";
my $exec_prefix = "@CMAKE_INSTALL_PREFIX@";
my $pkgdatadir = "@PKGDATADIR@";
my $libdir = "@LIBDIR@";
my $package = "@PACKAGE_NAME@";

if (! $regina_home) {
    if ($install_type eq 'Source') {
        my $srcdir = "@CMAKE_SOURCE_DIR@";
        $srcdir =~ /^\// or                  # Unix-style absolute path
            $srcdir =~ /^[A-Za-z]:[\\\/]/ or # Windows-style absolute path
            $srcdir = "$prog_dir/$srcdir";   # Relative path: make absolute

        &setReginaHome($srcdir);
    } elsif ($install_type eq 'Bundle') {
        &setReginaHome("$prog_dir/../Resources");
    } else {
        &setReginaHome($pkgdatadir);
    }
}

if (! $python_cmd) {
    my $which_python;
    chomp($which_python = `which python`);
    if (! $which_python) {
        &err("E: A Python interpreter could not be found.\n");
        &err("E: Set \$REGINA_PYTHON to a Python interpreter (such as /usr/bin/python)\n");
        &err("E: and try again.\n");
        exit 1;
    }
    &setPythonCmd($which_python);
}

if (! $python_lib_dir) {
    if ($install_type eq 'Bundle') {
        &setPythonLibDir("$prog_dir/python");
    } elsif ($install_type eq 'Source') {
        # Use the build directory.
        &setPythonLibDir("$prog_dir");
    } else {
        # We have installed the module in a standard Python location.
    }
}

# --- Go ahead and run the application. ---

if ($install_type eq 'Source') {
    # Since we're running from the source tree, we need to ensure that
    # the calculation engine library can be found.
    if ($os eq 'Darwin') {
        &extendEnvVar('DYLD_LIBRARY_PATH', "$regina_home/engine/");
    } elsif ($os eq 'Windows') {
        &extendEnvVar('PATH', "$regina_home/engine/");
    } else {
        &extendEnvVar('LD_LIBRARY_PATH', "$regina_home/engine/");
    }
} elsif ($install_type eq 'Bundle') {
    # All libraries shipped in the bundle should be found automatically
    # via @loader_path.  Nothing to do here.
} elsif ($install_type eq 'Windows') {
    # Make sure we can find the libraries shipped with the application.
    &extendEnvVar('PATH', $prog_dir);
}

# Do not use PYTHONPATH, since we might be running python in isolation mode.
# Instead edit the sys.path in code.
$python_lib_dir =~ s/\\/\\\\/g;
$python_lib_dir =~ s/'/\\'/g;
my $cmd = ($python_lib_dir ? "import sys; sys.path = ['$python_lib_dir'] + sys.path; " : '');
$cmd .= 'import regina; ';
$autoImport and $cmd .= 'from regina import *; ';

if ($script) {
    if (! -e $script) {
        &usage("E: The script $script could not be found.");
    }

    $cmd .= 'regina.reginaSetup(banner=False';
    ($sandboxed or not $interactive) and $cmd .= ', readline=False';
    $quiet and $cmd .= ', quiet=True';
    $cmd .= ', namespace=globals()); regina.__execScript(globals())';

    my @fullCommandLine = ($python_cmd);
    $sandboxed and push @fullCommandLine, '-I';
    $interactive and push @fullCommandLine, '-i';
    push @fullCommandLine, ('-c', $cmd, $script);
    push @fullCommandLine, @script_args;
    exec @fullCommandLine or &execError();
} else {
    $cmd .= 'regina.reginaSetup(banner=True';
    $sandboxed and $cmd .= ', readline=False';
    $quiet and $cmd .= ', quiet=True';
    $cmd .= ', namespace=globals())';

    my @fullCommandLine = ($python_cmd);
    $sandboxed and push @fullCommandLine, '-I';
    push @fullCommandLine, ('-i', '-c', $cmd);
    exec @fullCommandLine or &execError();
}

# --- Helper routines. ---

# Set various configuration options.
#
sub setVerbosity() {
    &info("I: Setting verbosity to $_[0].\n");
    if ($_[0] eq '0') {
        $quiet = 1;
        $verbose = 0;
    } elsif ($_[0] eq '1') {
        $quiet = 0;
        $verbose = 0;
    } elsif ($_[0] eq '2') {
        $quiet = 0;
        $verbose = 1;
    } else {
        &err("E: The verbosity level $_[0] is not recognised.\n");
        &err("E: This should be 0 (errors), 1 (errors/warnings) or 2 (everything).\n");
    }
}
sub setPythonCmd() {
    &info("I: Setting python command to $_[0].\n");
    $python_cmd = $_[0];
    $sandboxed = 0;
}
sub setPythonLibDir() {
    &info("I: Setting python module directory to $_[0].\n");
    if ( ! -e "$_[0]/regina/__init__.py") {
        &err("E: Regina's python module could not be found in the\n");
        &err("   directory $_[0].\n");
        &err("E: Please set \$REGINA_PYLIBDIR to the directory containing\n");
        &err("   the python module 'regina', and try again.\n");
        &err("   When this is set correctly, you should see files such as\n");
        &err("   \$REGINA_PYLIBDIR/regina/__init__.py and so on.\n");
        exit 1;
    }
    $python_lib_dir = $_[0];
}
sub setReginaHome() {
    &info("I: Setting Regina home directory to $_[0].\n");
    $regina_home = $_[0];

    # Sanity test.
    if (! (-e "$regina_home/examples/sample.rga" or -e "$regina_home/examples/sample-py3.rga")) {
        &err("E: \$REGINA_HOME should be set to the Regina base directory.\n");
        &err("   This is the directory containing the 'icons' subdirectory,\n");
        &err("   the 'examples' subdirectory, and so on.\n");
        &err("E: It is currently set to $regina_home .\n");
        exit 1;
    }
}
sub extendEnvVar() {
    &info("I: Appending $_[1] to \$$_[0].\n");
    if ($ENV{$_[0]}) {
        $ENV{$_[0]} = "$_[1]:$ENV{$_[0]}";
    } else {
        $ENV{$_[0]} = $_[1];
    }
}

# Write the given message to stderr if verbose mode is on.
#
sub info {
    if ($verbose) {
        print STDERR $_[0];
    }
}
# Write the given message to stderr if quiet mode is off.
#
sub warn {
    if (! $quiet) {
        print STDERR $_[0];
    }
}

# Write the given message to stderr.
#
sub err {
    print STDERR $_[0];
}

# Display usage information and exit.
#
sub usage {
    my $msg = shift;
    defined($msg) and print STDERR "$msg\n\n";
    print STDERR "Usage: 1. $prog_name\n";
    print STDERR "               [ -q, --quiet | -v, --verbose ] [ -a, --noautoimport ]\n";
    print STDERR "       2. $prog_name\n";
    print STDERR "               [ -q, --quiet | -v, --verbose ] [ -a, --noautoimport ]\n";
    print STDERR "               [ -i, --interactive ] script [ script_args ... ]\n";
    exit 1;
}

# The python interpreter could not be started.
#
sub execError() {
    &err("E: An error occurred whilst trying to start the Python interpreter.\n");
    &err("E: Run '$prog_name --verbose' to see more detailed diagnostic output,\n");
    &err("   including the runtime options being used.\n");
    &err("E: If you still cannot resolve the problem, please mail the authors\n");
    &err("   for assistance.\n");
}

