#!/usr/bin/env perl
#
#  Copyright (c) 1997-2021
#  Ewgenij Gawrilow, Michael Joswig, and the polymake team
#  Technische Universität Berlin, Germany
#  https://polymake.org
#
#  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, or (at your option) any
#  later version: http://www.gnu.org/licenses/gpl.txt.
#
#  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.
#-------------------------------------------------------------------------------

use v5.16;

package Polymake;

# global variables needed for the bootstrap
use vars qw($InstallTop $InstallArch $Arch $DeveloperMode @BundledExts $ConfigTime $DebugLevel);
my @addlibs;
BEGIN {
   require Cwd;
   $InstallTop=Cwd::abs_path( $0 =~ m{(?:^|/+)[^/]+/+[^/]+$} ? ($` || ".") : ".." );
   my $buildroot= $ENV{POLYMAKE_BUILD_ROOT} ? "$InstallTop/build.$ENV{POLYMAKE_BUILD_ROOT}" : "$InstallTop/build";
   $InstallArch="$buildroot/".($ENV{POLYMAKE_BUILD_MODE} || "Opt");
   do "$InstallTop/support/install_utils.pl";
   my %ConfigFlags=load_config_file("$buildroot/config.ninja", $InstallTop);
   $ConfigTime=(stat "$buildroot/config.ninja")[9];
   $Arch=$ConfigFlags{Arch};
   @BundledExts=$ConfigFlags{BundledExts} =~ /(\S+)/g;
   if ($^O eq "darwin" && $ConfigFlags{FinkBase}) {
      push @addlibs, "$ConfigFlags{FinkBase}/lib/perl5";
   }
   $DeveloperMode=-d "$InstallTop/testscenarios";
}

use lib "$InstallTop/perllib", "$InstallArch/perlx", @addlibs;

#########################################################################################
#
#  Parsing the command line
#
use Getopt::Long qw( GetOptions :config require_order bundling no_ignore_case );

my ($verbose, $script, $iscript, $connect, $touch, $help, $tell_version, $start_application, $ignore_start_applications, $reconfigure);
my @config_path;

if ( ! GetOptions( 'v+' => \$verbose, 'd+' => \$DebugLevel,
                   'A=s' => sub { $start_application = $_[1]; $ignore_start_applications=1; },
                   'a=s' => \$start_application,
                   ### 'T=f' => \$Polymake::Core::Rule::timeout,
                   'script=s' => sub { $script=$_[1]; die "!FINISH\n" },
                   'iscript=s' => sub { $iscript=$_[1]; die "!FINISH\n" },
                   'connect=s' => \$connect,
                   'touch' => \$touch, 'help' => \$help, 'version' => \$tell_version,
                   'config-path=s' => \@config_path,
                   'no-config' => sub { @config_path=("none") },
                   'ignore-config' => sub { @config_path=("ignore") },
                   'reconfigure' => \$reconfigure,
                 )
     #  --*script --connect --touch --help --version are mutually exclusive
     or defined($script)+defined($iscript)+defined($connect)+(@ARGV==1 && $ARGV[0] eq "-")+$touch+$help+$tell_version > 1
     #  --help --version --connect do not consume any additional args
     or $help+$tell_version+defined($connect) && @ARGV
     # there can be maximal one free argument
     or defined($script)+defined($iscript)+$touch == 0 && @ARGV > 1) {
   $!=1;
   die <<'.';
usage: polymake [-dv] [-A|-a <application>]
                [--reconfigure] [--config-path PATH ... | --no-config | --ignore-config]
                [ --script | --iscript <script_file> arg ... ] | script_file | 'one-liner' |
                - | [ --connect SOCKETFILE | HOST:PORT ] |
                --touch <file> ... | --help | --version
.
}

if ($help) {
   print STDERR <<'.';
usage: polymake [options] [arguments]
   Called without arguments:
      start an interactive shell.

   Arguments may be one of the following:
      --help
         Print this text and exit.
      --version
         Print the version number, copyright notice, and exit.
      [application::]script_file
         Execute the script stored in a file.
         If application prefix is specified, this application is loaded
         and the script file is looked up in its script directory.
      --script [application::]script_file arguments ...
         Execute the script, passing the arguments in @ARGV .
      --iscript [application::]script_file arguments ...
         Execute the script which may contain interactive commands.
      'one-liner'
         Interpret the string as an inline polymake/perl script.
      -
         Read and execute commands from the standard input.
         Standard output and error streams are not redirected.
      --connect SOCKETFILE
         Read and execute commands from a named socket.
         Print commands send the data back into the socket.
      --connect HOST:PORT
         Connect to the remote host, read and execute commands.
         Standard output and error streams are sent back to the host.
      --touch file [ file ... ]
         Read data files and write them out; useful for converting from
         older polymake versions.

   Options are:
      -A application_name
          Start with this application, ignoring the $default_application and
          @start_applications settings.
      -a application_name
          Start with this application, ignoring the $default_application but,
          in contrast to -A, load everything in @start_applications.
      -d  Produce some debug output; can be repeated to increase the debug level.
      -v  Tell what's going on; can be repeated to increase the verbosity level.
          This is an obsolete option, please use custom variables $Verbose::*
          to gain more detailed control.

      --reconfigure
          Re-run the autoconfiguration sections in all rule files.
      --config-path SETTINGS_FILE
          Import settings from a global configuration file.
          Several --config-path options are allowed, later instances have higher priority.
      --config-path user[=DIR]
          Specify the location of user's private settings, by default it's ~/.polymake
          which may be overridden via POLYMAKE_USER_DIR environment variable.
          All global --config-path imports must precede this option.
      --no-config
          Don't read any configuration files,
          don't try to configure rules automatically,
          don't load rule files requiring auto-configuration.
          Equivalent to --config-path=none .
      --ignore-config
          Don't read any configuration files,
          skip auto-configuration routines in the rule files.
          Equivalent to --config-path=ignore .
.
   exit;
}

# Getopt::Long does not allow to combine short option bundling with multi-value options
# Here is a work-around.  It is not used in this main program but in some scripts.
sub collect_arglist {
   my $list=shift;
   @$list=@_;
   while (@ARGV && $ARGV[0] !~ /^-/) {
      push @$list, shift @ARGV;
   }
}

#########################################################################################
#
#  Start of the real work
#
use strict;
use Polymake;
use namespaces;
use warnings qw(FATAL void syntax misc);

if ($tell_version) {
   print STDERR Main::greeting();
   exit;
}

# prepare for graceful termination from outside, e.g. in a docker container
$SIG{TERM}=sub { print STDERR "Terminating...\n"; exit; };

if ($verbose || $DebugLevel) {
   print STDERR "polymake version $Version\n";
}
if ($reconfigure) {
   require Polymake::Configure;
   Configure::prepare_reconfigure();
}

Main::init(@config_path ? join(";", @config_path) : $ENV{POLYMAKE_CONFIG_PATH} || "user");
Main::execute_command_line(@ARGV);
if ($@) {
   err_print($@);
   exit 1;
}

package Polymake::Main;

sub execute_command_line {
   local if ($DebugLevel || $verbose) {
      my $verbose_rules = $DebugLevel ? 3 : $verbose;
      local $Verbose::rules = $verbose_rules if $Verbose::rules < $verbose_rules;
      my $verbose_scheduler = max($DebugLevel, $verbose);
      local $Verbose::scheduler = $verbose_scheduler if $Verbose::scheduler < $verbose_scheduler;
      local $Verbose::external = 1 if $DebugLevel > 1;
   }

   if ($touch) {
      Main::touch_files();

   } elsif (defined($script)) {
      Main::run_script($script);

   } elsif (defined($iscript)) {
      require Polymake::Core::Shell;
      if (-t STDIN) {
         Core::Shell::start();
      }
      Main::run_script($iscript);

   } elsif (defined($connect)) {
      ### named socket or host connection
      require Polymake::Sockets;
      my ($socket, $redirects);
      if (-S $connect) {
         $socket=new ClientSocket($connect);
         $redirects=1;
      } elsif (!-e $connect && $connect =~ /^(.*):(\d+)$/) {
         $socket=new ClientSocket($1, $2);
         $redirects=3;
      } else {
         die "option --connect requires a named socket file or a remote host:port\n";
      }
      require Polymake::Core::Shell;
      if (Main::load_apps()) {
         Core::Shell::run_pipe($socket, $redirects);
      }

   } elsif (@_ == 0) {
      if (-t STDIN) {
         ### interactive shell
         print "Welcome to ", Main::greeting(), "\nLoading applications now...";
         STDOUT->flush();
         require Polymake::Core::Shell;
         if (Main::load_apps()) {
            Core::Shell::start();
            Core::Shell::run();
         }
      } else {
         die "can't start the interactive shell without terminal input\n";
      }
   } else {
      my $arg = shift;
      if ($arg eq "-") {
         ### anonymous pipe
         require Polymake::Core::Shell;
         if (Main::load_apps()) {
            Core::Shell::run_pipe(\*STDIN, 0);
         }
      } elsif ($arg !~ /[\s'"(){}\[\]\$]/) {
         ### script file
         Main::run_script($arg);
      } elsif (Main::load_apps()) {
         ### one-liner
         local unshift @INC, $User::application;
         local $Scope = new Scope();
         $User::application->eval_expr->("package Polymake::User; $arg");
         beautify_error() if $@;
      }
   }
}

sub load_apps {
   if ($start_application // $User::default_application) {
      eval {
         unless ($ignore_start_applications) {
            foreach my $app_name (@User::start_applications) {
               add Core::Application($app_name);
            }
         }
         run_init_script();
         User::application($start_application // $User::default_application);
         1
      }
   } else {
      $@="Start application not specified\n";
      0
   }
}

sub run_script {
   local $Scope = new Scope();
   local $standalone_script = true;
   load_dummy Core::Application;
   run_init_script();
   eval { User::script(@_, @ARGV) };
   beautify_error() if $@;
}

sub run_init_script {
   if (length(my $init_script = $User::init_script) != 0) {
      replace_special_paths($init_script);
      eval {
         if ($init_script =~ m{^/}) {
            -f $init_script
              or die "init_script file $init_script does not exist\n";
         } else {
            $init_script = find_file_in_path($init_script, $PrivateDir ? [ $PrivateDir, @User::lookup_scripts ]
                                                                       : \@User::lookup_scripts)
              or die "can't find init_script $User::init_script\n";
         }
         if (!defined($User::application)) {
            load_dummy Core::Application;
         }
         do "script=$init_script";
         die $@ if $@;
      };
      if ($@) {
         print STDERR "\n\nWARNING:\ninit_script disabled because of a following error:\n$@\n";
         undef $User::init_script;
      }
   }
}

sub touch_files {
   local $Scope = new Scope();
   load_dummy Core::Application;
   foreach (@ARGV) {
      eval { User::load_data($_) };
      err_print($@) if $@;
   }
}

# to serve as a breakpoint in the perl debugger
sub Polymake::stop_here { print STDERR "@_\n" if @_ }

# Local Variables:
# mode: perl
# cperl-indent-level:3
# indent-tabs-mode:nil
# End:
