#  Copyright (c) 2018
#  Charles Jordan (Hokkaido University, Sapporo, Japan),
#  Michael Joswig, Lars Kastner (Technische Universitaet Berlin, Germany)
# 
#  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 3, 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.
#-------------------------------------------------------------------------------

BEGIN {
   if ($] < 5.016) {
      print STDERR <<".";
mptopcom requires perl version not lower than 5.16;
your perl interpreter says it is $].

Please upgrade your perl installation;
if you already have an up-to-date perl interpreter somewhere else,
you can specify its location on the command line:

./configure PERL=/path/to/my/new/perl [other options ...]
.
      exit(1);
   }
}

use Config;
use Cwd;
use File::Path;

use strict 'vars', 'subs';
use lib "perllib";

my $root=Cwd::getcwd;
my ($NeedsArchFlag);

my $Wiki="http://www.polymake.org/doku.php";

my $warning;

package FakeNoShell;
sub new { bless \(my $dummy) }
sub interactive { 0 }

# set autoflush
$| = 1;

# import some utilities and prepare the fake environment for them
package Polymake;
use vars qw( $DeveloperMode $Shell $filename_re $directory_of_cmd_re );

$DeveloperMode=-d "$root/testscenarios";

use ConfigureStandalone;

$Shell=new FakeNoShell;
# we can't load regex.pl without compiled support for namespaces
$filename_re=qr{ ([^/]+) $ }x;
$directory_of_cmd_re=qr{ ^(.*) / [^/]+ (?: $ | \s)}x;

package Polymake::Configure;

@ARGV=grep { !/^PERL=/ } @ARGV;

my (%options, %vars, %allowed_options, %allowed_with, %allowed_flags, %allowed_vars);

# expected options for the core script
@allowed_options{ qw( prefix bindir includedir libdir build build-modes ) }=();
@allowed_with{ qw( toolchain polymake mpi ) }=();
@allowed_flags{ qw( prereq native openmp libcxx ) }=();
@allowed_vars{ qw( PM_CXXFLAGS PM_LIBS PM_INCLUDES PM_LDFLAGS PM_CXX CC CFLAGS CXX CXXFLAGS CXXOPT CXXDEBUG LDFLAGS LIBS Arch DESTDIR ) }=();

if ($^O eq "darwin") {
  @allowed_with{ qw( fink ) }=();
}

my (@ext, @ext_disabled, %ext_with_config, %ext_requires, %ext_requires_opt, %ext_conflicts, %ext_failed, %ext_bad,
    @ext_ordered, %ext_survived);

my $repeating_config=early_parse_command_line();
my $BuildDir= $options{build} ? "build.$options{build}" : "build";


# Load cdd configure.pl manually. Do some error checking here.
my $err;
eval <<"---";
{ package Polymake::Bundled::cdd;
  do "support/configure_cdd.pl";
  unless (\$err=\$\@) {
    allowed_options(\\%allowed_options, \\%allowed_with);
  }
}
---
if($err){
   die "cdd configure script defect.\n";
}

parse_command_line(\@ARGV, $repeating_config);
construct_paths();


# Extract polymake info.
locate_polymake();

determine_cxx_compiler();
determine_cxx_library();

# Check for mpi
check_mpi();

determine_architecture();
collect_compiler_specific_options();
check_build_modes();
# locate_gmp_and_mpfr_libraries();

# Extract cdd info from system.
print "checking cdd installation ... ";
my $result=eval { no strict 'refs'; &{"Polymake::Bundled::cdd::proceed"}(\%options) };
if($result){
   print "ok ($result)\n";
} else {
   die "cdd installation not found or misconfigured. $@\n";
}
$ext_survived{"cdd"} = 1;



# create the main build directory
File::Path::make_path("$BuildDir");

my $BundledLOG;
my $BundledLOGfile="$BuildDir/bundled.log";

# configure_enabled_bundled_extensions();
finalize_compiler_flags();
find_ninja();

File::Path::make_path($BuildDir);
write_configuration_file("$BuildDir/config.ninja");
create_build_trees("$root/$BuildDir");

if ($Polymake::DeveloperMode) {
   print <<"---";

* Configuration successful.
* You can run  'ninja -C $BuildDir/Opt'  now to build mptopcom with full optimization
* or  'ninja -C $BuildDir/Debug'  to build mptopcom with debugging support.
* To install an immutable snapshot of mptopcom at $InstallTop,
* run  'ninja -C $BuildDir/Opt install'.
*
* On systems with moderate amount of memory like laptops it is advisable to
* restrict the number of parallel build processes below the default ninja limit;
*   'ninja -l <NUMBERofCPUS>'  is a good approximation to start with.
* If the system still feels overloaded, take a lower hard job limit
*   'ninja -j <N>'  instead.
---
} else {
   print <<"---";

* Configuration successful.
* You can run 'ninja -C $BuildDir/Opt install' now to build and install mptopcom.
---
}

print $warning if defined $warning;
exit(0);

### end of main body ###

sub usage {
   my $GMPdef= $^O eq "darwin" ? '${fink}' : '/usr';

   print STDERR <<'---';
This is a script preparing the build and installation of mptopcom.
It tries to mimic well-known GNU auto-configuration scripts:

./configure [options] [VARIABLE=value ...]

Without any options, or with a sole --build option, it reuses the options specified
in the most recent run, if any; this is mostly useful for developers after an update
of the codebase.

Allowed options (and their default values) are:

 Mulitple build trees:

  --build SUFFIX      (none)  create or update one of several build directories,
                              named "build.SUFFIX".  This is primarily for developers
                              who want to test various configurations from a single code base,
                              or for packagers.
                              By default, a single "build" directory is created.

 Installation directories:

  --prefix=PATH       ( /usr/local )                   root of the installation tree
  --bindir=PATH       ( ${prefix}/bin )                main mptopcom script
  --includedir=PATH   ( ${prefix}/include )            include files
  --libdir=PATH       ( ${prefix}/lib )                callable library

 Build dependences:

  --with-toolchain=PATH    path to a full GCC or LLVM (including clang and libc++) installation
                           (sets CC, CXX, CXXFLAGS, LDFLAGS, LIBS and --with-libcxx accordingly)
---
   $^O eq "darwin" and
   print STDERR <<'---';
.
---
   print STDERR <<"---";
  --with-polymake=PATH     path to polymake installation
  --with-mpi=PATH          path to mpi installation, e.g. Open MPI
---
   &{"Polymake::Bundled::cdd::usage"}();

   print STDERR <<'---';


 Other options:

  --with-libcxx       build against the libc++ library instead of the GNU libstdc++, useful when
                      building with LLVM/Clang. (sets -stdlib=libc++, -lc++ and -lc++abi)
                      see also --with-toolchain when using a custom LLVM installation



---
   $^O ne "darwin" and
   print STDERR <<'---'; 
  --without-native    build without "-march=native" flag to allow execution on different CPU types
                      (passing one of -m{cpu,arch,tune} via CFLAGS or CXXFLAGS also disables "-march=native")

---
   print STDERR <<'---';
  --build-modes=MODE,MODE...
                      prepare for builds in non-standard modes; currently supported modes are:
                        Cov  generated code gathers coverage statistics
                        San  sanitized build detecting address violations and undefined behavior
                      Standard modes Opt and Debug are always supported and must not be listed here.

---
   $Polymake::DeveloperMode and
   print STDERR <<"---";
  --clone=PATH/config.ninja
                      Import the options stored in the given configuration file.
                      More options may follow; simple options may override the imported ones,
                      but no provisions are made for detecting more subtle contradictions.
                      Use at own risk.

---
   print STDERR <<"---";
Allowed variables are:

   Arch=       An abbreviation for the system architecture.
---
   $^O eq "darwin" and
   print STDERR <<'---';
               Allowed values are "i386" for 32bit or "x86_64" for 64bit.
---
   print STDERR <<'---';
               Default value is derived from the current system setup.
               Users' custom variables may have distinct values for different architectures.

   CC=cc       C compiler executable
   CFLAGS=     C compiler options
   CXX=c++     C++ compiler executable
   CXXFLAGS=   C++ compiler options
   CXXOPT=     C/C++ compiler optimization level (defaults to highest possible)
   LDFLAGS=    linker options
   LIBS=       additional libraries to be linked with
   PERL=perl   perl executable

Options incompatible with any others:

   --defaults    perform a configuration assuming all default settings,
                 ignoring any existing configured builds

---
   $Polymake::DeveloperMode and
   print STDERR <<"---";
   --alt-perl=TAG    create a configuration for a non-standard perl interpreter;
                     the ninja files will be called build.TAG.ninja

---
   print STDERR <<"---";
For detailed installation instructions, please refer to the polymake Wiki site:
  $Wiki/howto/install

---
}

###################################################################################
# * sieve out disabled extensions so that their configuration scripts are not loaded
# * process --build, --build-modes, and --defaults
# * restore options from previous runs if desired
sub early_parse_command_line {
   my (@other_args, $enforce_defaults);
   while (defined (my $arg=shift @ARGV)) {
      if ($arg eq "--defaults") {
         $enforce_defaults=1;
      } elsif (my ($flag, $value)= $arg =~ /^--(build(?:-modes)?|alt-perl)(?:=(\S+))?$/) {
         $value //= @ARGV && $ARGV[0] =~ /^\w/ ? shift @ARGV : die "option --$flag requires a value\n";
         $options{$flag}=$value;
      } elsif ($arg =~ /^--without-(\w+)$/  &&  -d "bundled/$1") {
         push @ext_disabled, $1;
         $options{$1}=".none.";
      } else {
         push @other_args, $arg;
      }
   }

   if ($options{'alt-perl'}) {
      if ($enforce_defaults || @other_args || @ext_disabled) {
         die "option --alt-perl can't be combined with any other configuration option\n";
      }
   }

   if ($enforce_defaults) {
      if (@other_args || @ext_disabled) {
         die "option --defaults can't be combined with any other configuration option\n";
      }

   } elsif (@other_args) {
      @ARGV=splice @other_args;

   } elsif (!@ext_disabled) {
      # no configuration options means we should reuse the options from the previous run

      if ($options{build}) {
         # want to keep several architectures in parallel
         if ($^O eq "darwin") {
            $options{build} =~ s/^(?!darwin\.)/darwin./;
         }
         if (-f "build.$options{build}/config.ninja") {
            retrieve_config_command_line("build.$options{build}/config.ninja", \@ARGV);
            return 1;
         } elsif (-f "build.$options{build}/conf.make") {
            retrieve_config_command_line("build.$options{build}/conf.make", \@ARGV);
            return 1;
         }
      } elsif (-f "build/config.ninja") {
         retrieve_config_command_line("build/config.ninja", \@ARGV);
         return 1;
      } elsif (my @ninja_configs=glob("build.*/config.ninja")) {
         die "mptopcom has been configured for several build architectures: ", (map { m{/build\.([^/]+)/} } @ninja_configs),
              "\nPlease specify the desired one with --build option or enforce default configuration with --defaults option.\n";
      } elsif (my @old_configs=glob("build.*/conf.make")) {
         if (@old_configs==1) {
            my ($old_build)=$old_configs[0] =~ m{(build.[^/])};
            retrieve_config_command_line($old_configs[0], \@ARGV);
            rename $old_build, "build";
            return 1;
         } else {
            die "mptopcom has been configured for several build architectures: ", (map { m{/build\.([^/]+)/} } @old_configs),
                "\nPlease specify the one to be migrated to ninja build system with --build option.\n",
                "If you want to preserve just one of them, delete all other build.XXX directories and re-run the configure command without any options.\n";
         }
      }
   }
   0
}

###########################w######################################
sub parse_command_line {
   my ($arglist, $repeating_config, $ignore_unknown)=@_;
   while (defined (my $arg=shift @$arglist)) {
      # trim and allow empty arguments
      $arg =~ s/^\s+//;
      $arg =~ s/\s+$//;
      next if length($arg)==0;
      $arg =~ s/^--with-libc\K\+\+$/xx/;
      if ($arg eq "--help") {
         usage();
         exit(0);
      } elsif (my ($with, $out, $name, $value)= $arg =~ /^--(with(out)?-)?(\w[-\w]+)(?(2)|=(.*))?$/) {
         if ($with and exists $allowed_flags{$name} and !defined($value)) {
            if (defined($out)) {
               $options{$name}=".none.";
            } else {
               $options{$name}=".true.";
            }
            next;
         } elsif ($with ? exists $allowed_with{$name}
                        : exists $allowed_options{$name}
                    and
                  defined($out) || defined($value) || @$arglist) {
            if (defined($out)) {
               $value=".none.";
            } else {
               $value //= shift(@$arglist);
               $value =~ s{^~/}{$ENV{HOME}/};
               $value =~ s{/+$}{};
            }
            if ($repeating_config && $name =~ /^build/) {
               $options{$name} //= $value;
            } elsif ($value eq "default") {
               delete $options{$name};
            } else {
               $options{$name}=$value;
            }
            next;
         } elsif ($Polymake::DeveloperMode and
                  $arg eq "--clone" && @$arglist  ||
                  $arg =~ s/^--clone=(?=.)//) {
            # used in testscenarios
            my @other_command_line;
            retrieve_config_command_line($arg eq "--clone" ? shift(@$arglist) : $arg, \@other_command_line);
            parse_command_line(\@other_command_line, 0, 1);
            next;
         }
      } elsif ($arg =~ /^([A-Z]\w+)=(.*)/) {
         if (exists $allowed_vars{$1}) {
            $vars{$1}=$2;
            next;
         }
      }
      if (!$ignore_unknown) {
         die "Unrecognized option $arg\nTry ./configure --help for detailed information\n";
      }
   }

   if (!$repeating_config) {
      # inherit some variables which haven't appeared on the command line from the environment
      foreach my $varname (grep { !exists $vars{$_} } keys %allowed_vars) {
         if (defined (my $value=$ENV{$varname})) {
            $vars{$varname}=$value;
         }
      }
   }

   $CXXOPT   =$vars{CXXOPT}   // "-O3";
   $CXXDEBUG =$vars{CXXDEBUG} // "-g";
   $CFLAGS   =$vars{CFLAGS}   // "";
   $CXXFLAGS =$vars{CXXFLAGS} // $CFLAGS;
   $LDFLAGS  =$vars{LDFLAGS}  // "";
   $LIBS     =$vars{LIBS}     // "-lgmpxx";
   $DESTDIR  =$vars{DESTDIR};
   $PM_CXXFLAGS = $vars{PM_CXXFLAGS} // "";
   $PM_INCLUDES = $vars{PM_INCLUDES} // "";
   $PM_LDFLAGS = $vars{PM_LDFLAGS} // "";
   $PM_LIBS = $vars{PM_LIBS} // "";

   ($Arch=$vars{Arch}) =~ s/^darwin\.//;
}


#####################################################
sub construct_paths {
   my $prefix=$options{prefix} || "/usr/local";
   $InstallBin=$options{bindir} || "$prefix/bin";
   $InstallLib=$options{libdir} || "$prefix/lib";
   $InstallInc=$options{includedir} || "$prefix/include";
}

#####################################################
sub determine_cxx_compiler {
   my @comp = @_;
   print "checking C++ compiler ... ";

   $CXX = $vars{CXX} // $PM_CXX;
   $CC = $vars{CC};

   if ($options{toolchain}) {
      if (-x $options{toolchain}."/bin/clang++") {
         $CXX ||= $options{toolchain}."/bin/clang++";
         $CC  ||= $options{toolchain}."/bin/clang";
      } elsif (-x $options{toolchain}."/bin/g++") {
         $CXX ||= $options{toolchain}."/bin/g++";
         $CC  ||= $options{toolchain}."/bin/gcc";
      } else {
         die <<"---"
Unsupported toolchain given, only GCC or LLVM/Clang are supported.
Make sure $options{toolchain}/bin/clang++ or $options{toolchain}/bin/g++ exists and is executable.
---
      }
   }

   check_program($CXX, "g++", "c++", "icpc", "clang++")
     or die "no supported C++ compiler found; please reconfigure with CXX=name\n";

   my $build_error = build_test_program(<<"---");
#include <iostream>
int main() {
#if defined(__APPLE__)
   std::cout << "apple";
#endif
#if defined(__clang__)
   std::cout << "clang " << __clang_major__ << '.' << __clang_minor__ << '.' << __clang_patchlevel__ << std::endl;
   return 0;
#elif defined(__GNUC__)
   std::cout << "gcc " << __GNUC__ << '.' << __GNUC_MINOR__ << '.' << __GNUC_PATCHLEVEL__ << std::endl;
   return 0;
#elif defined(__INTEL_COMPILER)
   std::cout << "icc " << __INTEL_COMPILER << std::endl;
   return 0;
#else
   return 1;
#endif
}
---

   if ($?) {
      die "C++ compiler $CXX could not compile a test program for version recognition:\n",
          $build_error,
          "\nPlease investigate and reconfigure with CXX=<appropriate C++ compiler>\n";
   }

   local $_=run_test_program();  chomp;
   if (/^(apple)?clang /) {
      $CLANGversion=$';
      $AppleClang = "true" if $1 eq "apple";
      if (defined($AppleClang) and v_cmp($CLANGversion, "5.1") < 0) {
         die "C++ compiler $CXX is from Xcode $CLANGversion, while the minimal required Xcode version is 5.1\n";
      } elsif (!defined($AppleClang) and v_cmp($CLANGversion, "3.4") < 0) {
         die "C++ compiler $CXX says its version is $CLANGversion, while the minimal required version is 3.4\n";
      }

   } elsif (/icc (\d+)(\d\d)$/) {
      $ICCversion="$1.$2";
      if (v_cmp($ICCversion, "16.0") < 0) {
        die "C++ compiler $CXX says its version is $ICCversion, while the minimal required version is 16.0\n";
      }
      # untested but since version 16 most of C++14 should be supported
      print "warning: probably unsupported C++ compiler Intel $ICCversion.\n";
   } elsif (/(apple)?gcc /) {
      $GCCversion=$';
      # decide whether we use the gcc provided by apple, in that case we need the -arch flags, otherwise we probably don't
      $NeedsArchFlag=defined($1);
      if (v_cmp($GCCversion, "5.1") < 0) {
         die "C++ compiler $CXX says its version is $GCCversion, while the minimal required version is 5.1\n";
      }
   } else {
      die "$CXX is an unsupported C++ compiler.  Please choose one of the supported: Intel, clang, or GCC.\n";
   }

   if (defined $CC) {
      check_program($CC);
   } else {
      $CC=$CXX;
      unless (defined($CLANGversion)
              ? $CC =~ s{clang\+\+$}{clang} :
              defined($ICCversion)
              ? $CC =~ s{\b([ei])cp?c$}{${1}cc} :
              defined($GCCversion)
              ? $CC =~ s{g\+\+(?=[^/]*)$}{gcc}
                  ||
                $CC =~ s{(?:c\+\+|CC)(?=[^/]*)$}{cc}
              : 0
              and find_program($CC)) {
         $CC=$Config::Config{cc};
      }
   }
   if (defined($GCCversion) or defined($CLANGversion)) {
      print "ok ($CXX is ",
            defined($GCCversion) ?
               "GCC $GCCversion" :
               defined($CLANGversion) ?
                  (defined($AppleClang) ?
                     "Apple CLANG (roughly ".clang_ver($CLANGversion).") from Xcode $CLANGversion" :
                     "CLANG $CLANGversion")
                  : "unknown", ")\n";
   }
}
#####################################################
sub determine_cxx_library {
   print "checking C++ library ... ";

   if ($options{toolchain}) {
      my $libdir;
      # CC/CXX was set earlier, the else case already died at that point
      if (-x $options{toolchain}."/bin/clang++") {
         $libdir = get_libdir($options{toolchain}, "clang");
         $options{libcxx} = ".true.";
      } elsif (-x $options{toolchain}."/bin/g++") {
         $libdir = get_libdir($options{toolchain}, "gcc_s");
      }
      $CFLAGS   .= " -I$options{toolchain}/include";
      $CXXFLAGS .= " -I$options{toolchain}/include";
      $LDFLAGS  .= " -L$libdir -Wl,-rpath,$libdir";
   }

   if ($options{libcxx} eq ".true.") {
      $CXXFLAGS .= " -stdlib=libc++";
      $LDFLAGS  .= " -stdlib=libc++";
      $LIBS     .= " -lc++";
      $LIBS     .= " -lc++abi" if ($^O eq "linux");
   }

   # All polymake releases after 3.0 require C++14,
   # but if someone explicitly requests another standard version go along with it,
   # if it is too old we will generate a warning / an error later on.
   if ($CXXFLAGS !~ /(?:^|\s)-std=/) {
      if (defined($CLANGversion) and v_cmp(clang_ver($CLANGversion), "3.5") < 0) {
         $CXXFLAGS .= ' -std=c++1y';
      } else {
         $CXXFLAGS .= ' -std=c++14';
      }
   }

   my $build_error = build_test_program(<<"---");
#include <cstddef>
#include <cstdio>
#include <iostream>
int main() {
   std::cout << "cplusplus " << __cplusplus << std::endl;
#if defined(_LIBCPP_VERSION)
   std::cout << "libc++ " << _LIBCPP_VERSION << std::endl;
   return 0;
#elif defined(__GLIBCXX__)
   std::cout << "GNU libstdc++ " << __GLIBCXX__ << std::endl;
   return 0;
#elif defined(__INTEL_CXXLIB_ICC)
   std::cout << "Intel " << __INTEL_CXXLIB_ICC << std::endl;
   return 0;
#else
   return 1;
#endif
}
---
   if ($?) {
      die <<"---";
C++ compiler $CXX could not compile a test program for C++ library recognition:
$build_error
Please investigate and reconfigure.
---
   }
   local $_=run_test_program();  chomp;
   my ($cppver, $cpplib) = $_ =~ m/^cplusplus (\d+)\n(.+)$/;

   unless ($cppver >= 201402 or
           $cppver >= 201305 and
               defined($CLANGversion) and v_cmp(clang_ver($CLANGversion), "3.5") < 0
          ) {
      # C++ standard older than C++14 wont work (C++1y from clang 3.4 is also ok)
      die "C++ standard version ($cppver) too old, C++14 or later is required. Please omit any '-std=' options.\n";
   }

   # see http://sourceforge.net/p/predef/wiki/Libraries/
   if ($cpplib =~ /^GNU libstdc\+\+ /) {
      # this is some more or less useful date
      my $stdlibversion=$';
      $CPPStd = $cppver;
      print "ok ($cpplib, C++ $CPPStd)\n";
   } elsif ($cpplib =~ /^libc\+\+ /) {
      $CPPStd = $cppver;
      if ($LIBS !~ /-lc\+\+/) {
         $LIBS .= " -lc++";
         $LIBS .= " -lc++abi" if ($^O eq "linux");
      }
      print "ok ($cpplib, C++ $CPPStd)\n";
   } elsif ($cpplib =~ /^Intel /) {
      my $intelcxxversion=$';
      $CPPStd = $cppver;
      print "warning: probably unsupported C++ library Intel $intelcxxversion.\n";
      # not tested in a long time
   } else {
      die "Unsupported C++ library, use -stdlib to specify libstdc++ or libc++.\n";
   }

   if ($LDFLAGS !~ /-stdlib=/ and $CXXFLAGS =~ /(-stdlib=\S+)/) {
      $LDFLAGS .= " $1";
   }
}

#####################################################
sub check_fink {
   print "checking fink installation ... ";
   if (defined( $FinkBase=$options{fink} )) {
      if (-d $FinkBase) {
         unless (-f "$FinkBase/etc/fink.conf") {
            die "option --with-fink does not point to the fink installation tree: $FinkBase/etc/fink.conf not found\n";
         }
      } elsif (-x $FinkBase && $FinkBase =~ s|/bin/fink$||) {
         unless (-f "$FinkBase/etc/fink.conf") {
            die "option --with-fink points to a broken fink installation: $FinkBase/etc/fink.conf not found\n";
         }
      } else {
         die <<'---';
option --with-fink points to a wrong program: something like /path/bin/fink expected.
If you have renamed the main fink program, please specify the top directory: --with-fink=/path
---
      }
   } else {
      # Fink location not specified, look for it at plausible places
      (undef, my ($fink, $error))=find_program_in_path("fink", sub {
         !(($FinkBase=$_[0]) =~ s|/bin/fink$|| && -f "$FinkBase/etc/fink.conf") && "!"
      });
      if ($fink) {
         if ($error) {
            die <<"---";
Found the fink program at $fink, but the corresponding configuration file is missing;
Please specify the top installation directory using option --with-fink
---
         }
      } elsif (-f "/sw/etc/fink.conf") {
         $FinkBase="/sw";
      } else {
         die <<"---";
The Fink package system is a mandatory prerequisite to build and use mptopcom under MacOS.
Please refer to $Wiki/mac for details and installation instructions.
If you already have Fink installed at a non-standard location, please specify it using option --with-fink
---
      }
   }

   if (defined (my $anylib=(glob("$FinkBase/lib/*.dylib"))[0])) {
      my ($fink_arch)= `lipo -info $anylib` =~ /architecture: (\S+)/;
      if (defined $Arch) {
         if ($Arch ne $fink_arch) {
            die "Required architecture $Arch does not match installed Fink architecture $fink_arch\n";
         }
      } else {
         $Arch=$fink_arch;
      }
   } else {
      die "Fink installation seems corrupt: no shared libraries found in $FinkBase/lib\n";
   }

   print "ok ($FinkBase)\n";
}
#####################################################
sub determine_architecture {
   print "determining architecture ... ";
   if ($^O eq "darwin") {
      if ($options{fink} ne ".none.") {
         check_fink();
      } elsif (defined $Arch) {
         if ($Arch ne "i386" && $Arch ne "x86_64") {
            die "Invalid architecture $Arch for Mac OS: allowed values are i386 and x86_64.\n";
         }
      } else {
         # choose i386 for 10.5 and x86_64 for all others
         `sw_vers | grep ProductVersion` =~ /(\d+(?:\.\d+)*)/;
         $Arch= v_cmp($1, "10.6")>=0 ? "x86_64" : "i386";
      }
      $ARCHFLAGS= $NeedsArchFlag ? "-arch $Arch" : "";
      $Arch="darwin.$Arch";
   } else {
      unless (defined $Arch) {
         if ($^O !~ /aix|rs6000|ibm/i) {
            ($Arch)= `uname -m` =~ /(\w+)/;
         }
         $Arch //= $Config::Config{archname};
      }

      # no arch flags set
      unless ($options{native} eq ".none." or "$CFLAGS $CXXFLAGS" =~ /(?:^|\s)-m(?:arch|tune|cpu)=/) {
         $CFLAGS .= " -march=native";
         $CXXFLAGS .= " -march=native";
      }
   }
   print "ok ($Arch)\n";
}

#####################################################
sub collect_compiler_specific_options {
   print "determining compiler flags ... ";
   if (defined($GCCversion)) {
      # avoid using temp files, they grow indeterminately during debug builds
      $CsharedFLAGS="-fPIC -pipe";
      # TODO: remove -fno-strict-aliasing when the core library is free from reintepret_casts
      $CXXFLAGS .= " -ftemplate-depth-200 -fno-strict-aliasing -Wno-parentheses -fwrapv";
      if ($options{openmp} ne ".none.") {
         $CXXFLAGS .= " -fopenmp";
         $LDFLAGS .= " -fopenmp";
      }
      $CXXCOV="--coverage -O1";
      $CXXSANITIZE="-fno-omit-frame-pointer -O1 " . ($CXXDEBUG =~ /(?:^|\s)-g0/ ? "-g1" : $CXXDEBUG);
      # external libraries might be somehow dirtier
      $CflagsSuppressWarnings="";
      # gcc-specific flags
      if (v_cmp($GCCversion, "6.3.0") >= 0 && v_cmp($GCCversion, "7.0.0") < 0) {
         $CXXFLAGS .= " -Wno-maybe-uninitialized";
      }

   } elsif (defined($ICCversion)) {
      $CsharedFLAGS="-fPIC";
      $CXXDEBUG !~ /(?:^|\s)-O/ and $CXXDEBUG =~ s/-g(?!0)\K/ -Ob0/;
      $CXXFLAGS .= " -wd193,383,304,981,1419,279,810,171,1418,488,1572,561";
      $CFLAGS .= " -wd193,1572,561";

   } elsif (defined($CLANGversion)) {
      # avoid using temp files, they grow indeterminately during debug builds
      $CsharedFLAGS="-fPIC -pipe";
      $CXXFLAGS .= " -Wno-logical-op-parentheses -Wno-shift-op-parentheses -Wno-mismatched-tags";
      $CflagsSuppressWarnings="-Wno-unused -Wno-empty-body -Wno-format-extra-args";
      if (v_cmp(clang_ver($CLANGversion), "3.6") >= 0) {
         $CXXFLAGS .= " -Wno-unused-local-typedef";
      }

      # verify openmp support which is available starting with 3.7 but depends on the installation,
      # but 3.7 seems to crash when compiling libnormaliz so we skip that version
      # version 3.8 is tested to work with openmp
      if (v_cmp(clang_ver($CLANGversion), "3.8") >= 0 && $options{openmp} ne ".none.") {
         my $ompflag = "-fopenmp";
         my $build_error=build_test_program(<<'---', CXXFLAGS => "$ompflag", LDFLAGS => "$ompflag");
#include <stdio.h>
#include <omp.h>

int main() {
#pragma omp parallel
    printf("Hello from thread %d, nthreads %d\n", omp_get_thread_num(), omp_get_num_threads());
    return 0;
}
---
         if ($? == 0) {
            my $openmpout = run_test_program();
            my ($nthr) = $openmpout =~ /Hello from thread \d+, nthreads (\d+)/;
            if ($? == 0) {
               $CXXFLAGS .= " $ompflag";
               $LDFLAGS .= " $ompflag";
            }
         }
      }

      if (v_cmp(clang_ver($CLANGversion), "3.8") >= 0) {
         # stick to the gcov-compatible coverage tool for now
         # let's experiment with -fcoverage-mapping -fprofile-instr-generate later
         $CXXCOV="--coverage -O1";
      }
      if (v_cmp(clang_ver($CLANGversion), "3.9") >= 0) {
         $CXXSANITIZE="-fno-omit-frame-pointer -O1 " . ($CXXDEBUG =~ /(?:^|\s)-g0/ ? "-g1" : $CXXDEBUG);
      }
   }

   $LDsharedFLAGS=$Config::Config{lddlflags};

   if ($^O eq "darwin") {
      # MacOS magic again: remove multi-architecture options for fat binaries
      my $allarch=qr/ -arch \s+ \S+ (?: \s+ -arch \s+ \S+)* /x;
      $CFLAGS =~ s/$allarch//o;
      $CXXFLAGS =~ s/$allarch//o;
      $LDsharedFLAGS =~ s/$allarch//o;
      if ($options{callable} ne ".none.") {
         ($LDcallableFLAGS = $Config::Config{ldflags}) =~ s/$allarch//o;
         $LDcallableFLAGS = "$LDsharedFLAGS $LDcallableFLAGS";
         $LDcallableFLAGS =~ s/-bundle/-dynamiclib/;
         $LDsonameFLAGS = "-install_name $InstallLib/";
      } else {
         $LDcallableFLAGS="none";
      }
   } else {
      # enforce lazy binding unless it's already explicitly enabled or disabled by the user
      # (there are few strange Linux distributions where it's not enabled by default contradicting the ld manual)
      if ("$LDFLAGS $LDsharedFLAGS" !~ /-z(?:,|\s+)(?:lazy|now)\b/) {
         $LDsharedFLAGS .= " -Wl,-z,lazy";
      }

      # prevent early loading of third-party shared modules which are used only in few applications
      if ("$LDFLAGS $LDsharedFLAGS" !~ /-as-needed/) {
         $LDsharedFLAGS .= " -Wl,--as-needed";
      }

      if ($options{callable} ne ".none.") {
         $LDcallableFLAGS="$LDsharedFLAGS $Config::Config{ldflags}";
         $LDsonameFLAGS = "-Wl,-soname,";
      } else {
         $LDcallableFLAGS="none";
      }
   }

   if (defined($CLANGversion) && v_cmp(clang_ver($CLANGversion), "3.5") < 0) {
      # old clangs do not have this option which is actively used in newer perls
      s/-fstack-protector\K-strong//g for $LDsharedFLAGS, $LDcallableFLAGS;
   }

   print <<"---";
ok
   CFLAGS=$CFLAGS
   CXXFLAGS=$CXXFLAGS
---
}

#####################################################
sub check_build_modes {
   $BuildModes="Opt Debug";
   if ($options{'build-modes'}) {
      # might want to use a non-standard perl just with one build mode
      $BuildModes="" if $options{'alt-perl'};
      add_build_modes($options{'build-modes'});
   }
}



#######################################################################
sub locate_polymake {
   print "checking polymake installation ... ";
   my $pmpath=$options{polymake};
   my $pmc="polymake-config";
   if (defined($pmpath)) {
      if (-x "$pmpath/bin/polymake-config") {
         $pmc="$pmpath/bin/polymake-config";
      } elsif ($pmc !~ /-config$/ || !-x $pmc) {
         die <<"---";

Could not derive the location of the configuration program polymake-config from
the option --with-polymake. Maybe your polymake installation does not come with
shared library support. You might need to rebuild and install polymake from the
source files.
---
      }
   }
   check_program($pmc);
   if($@){
      die <<"---";

The program polymake-config failed. Maybe your polymake installation does not
come with shared library support. You might need to rebuild and install
polymake from the source files.
---
   }
   my $version = `$pmc --version`;
   if ($?) {
      die <<"---";
Could not determine polymake version.
---
   }
   chomp $version;
   if(v_cmp($version, "3.1") < 0){
      die <<"---";
mptopcom needs polymake version >= 3.1, detected version $version.
---
   }

   $PM_CXX = `$pmc --cc`;
   $PM_CXXFLAGS = `$pmc --cflags`;
   $PM_INCLUDES = `$pmc --includes`;
   $PM_LDFLAGS = `$pmc --ldflags`;
   $PM_LIBS = `$pmc --libs`;
   chomp $PM_CXX;
   chomp $PM_CXXFLAGS;
   chomp $PM_INCLUDES;
   chomp $PM_LDFLAGS;
   chomp $PM_LIBS;
   print "ok ($version)\n";
}


#######################################################################
sub check_mpi{
   # Check mpi compiler with temporary switch
   print "checking mpi installation ... ";
   my $mpicxx = "mpicxx";
   my $mpicc = "mpicc";
   if($options{mpi} ne ""){
      $MPI_PATH = $options{mpi};
      $mpicxx = "$options{mpi}/bin/mpicxx";
      $mpicc = "$options{mpi}/bin/mpicc";
   }
   eval{
      check_program($mpicxx);
      check_program($mpicc);
   };
   if($@){
      die "mpi wrapper not found, please specify path with --with-mpi=PATH\n";
   }
   my $version = `$mpicxx -showme:version` or die "Could not determine MPI version.\n";
   my $isOMPI = $version =~ m/Open MPI/;
   chomp $version;
   $version =~ s/.*?:\s*//;
   print "ok ($version)\n";
   if($isOMPI){
      $MPICXX = "OMPI_CXX=$CXX $mpicxx";
      $MPICC = "OMPI_CC=$CC $mpicc";
   }
}


##############################################################################
sub finalize_compiler_flags {
   # add flags and libraries that should not appear in the test builds tried before

   if ($options{callable} ne ".none."
       and $LIBS !~ /-ldl/
       and $^O eq "linux") {
      $LIBS .= " -ldl";
   }

   $LIBS="-lmpfr -lgmp -lpthread $LIBS";

   # be rigorous about own code
   if ($Polymake::DeveloperMode) {
      $CXXFLAGS="-Wall -Werror $CXXFLAGS";
   }

   if (defined($options{ccache}) && $options{ccache} ne ".none.") {
      $CCACHE=$options{ccache};
      check_program($CCACHE);
   }
}

##############################################################################
sub find_ninja {
   find_program_in_path("ninja")
     or die <<'---';
ERROR: The ninja program is required for building mptopcom.

It can usually be installed as a ready-to-use package, e.g.
on Ubuntu:
   apt-get install ninja-build
on MacOS with Homebrew:
   brew install ninja-build

As a fallback, you can obtain its source code from GitHub, build it yourself,
and install at a location of your choice:

INSTALL_DIR=/usr/local/bin  # or anything else reachable through your PATH

git clone https://github.com/ninja-build/ninja.git
cd ninja
./configure.py --bootstrap
sudo install -s -m 555 ninja $INSTALL_DIR

---
}

##############################################################################
sub write_configuration_file {
   my ($filename)=@_;
   open my $conf, ">", $filename
     or die "can't create $filename: $!\n";

   # preserve a copy of the command line for later reference
   print $conf "# last configured with:\n", "configure.command=$root/configure";
   write_config_command_line($conf, \%options, \%allowed_with, \%vars, \%ext_failed);

   my @external_includes=map { "-I\${root}/include/external/$_" } $ExternalHeaders =~ /(\S+)/g;
   print $conf <<"---";

root=$root
core.includes=-I\${root}/include/core-wrappers -I\${root}/include/core
app.includes=-I\${root}/include/app-wrappers -I\${root}/include/apps @external_includes

---
   write_config_vars(__PACKAGE__, "", $conf);

   print $conf <<"---";
AR=$Config::Config{ar}
PERL=$Config::Config{perlpath}
---
   # write configuration variables for successfully configured bundled extensions
   foreach my $ext (keys %ext_survived) {
      write_config_vars("Polymake::Bundled::$ext", "bundled.$ext.", $conf);
      print $conf "bundled.$ext.RequireExtensions=@{$ext_requires{$ext}}\n";
   }


   close $conf;
}


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