#!/bin/bash
#
# build
#
# -- This script downloads and installs the OpenCoarrays prerequisites
#
# OpenCoarrays is distributed under the OSI-approved BSD 3-clause License:
# Copyright (c) 2015-2016, Sourcery, Inc.
# Copyright (c) 2015-2016, Sourcery Institute
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
#    list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice, this
#    list of conditions and the following disclaimer in the documentation and/or
#    other materials provided with the distribution.
# 3. Neither the names of the copyright holders nor the names of their contributors
#    may be used to endorse or promote products derived from this software without
#    specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

this_script=`basename $0`

usage()
{
    echo ""
    echo " $this_script - Bash script for building OpenCoarrays prerequisites from source"
    echo ""
    echo " Usage (optional arguments in square brackets): "
    echo "      $this_script [<options>] or [<package-name> <version-number> <installation-path> <number-of-threads>]"
    echo ""
    echo " Options:"
    echo "   --help, -h           Show this help message"
    echo "   --list , -l          List the packages this script can build"
    echo ""
    echo " Examples:"
    echo ""
    echo "   $this_script gcc                              Build the default GCC release"
    echo "   $this_script gcc trunk                        Build the GCC development trunk"
    echo "   $this_script gcc --default /opt/gnu/5.3.0 4   Build the default release in the specified path using 4 threads"  
    echo "   $this_script gcc 5.2.0 /opt/gnu/5.2.0         Build the GCC 5.2.0 release"  
    echo "   $this_script wget                             Build the wget package"
    echo "   $this_script mpich --default --query-version  Return the default mpich version built by $this_script"
    echo "   $this_script flex  --default --query-path     Return the default flex installation path"
    echo "   $this_script mpich --default --query-url      Return the default mpich version built by $this_script"
    echo "   $this_script --help"
    echo "   $this_script --list"
    echo ""
    echo "[exit 10]"
    exit 10
 
# The following argument invocation works only with gcc but could be extended to work with any svn download:
# 
#    ./build gcc --avail  list the development branches available for checkout
#    ./build gcc --a      same as --avail
#
#  The --avail and -a options are not listed in the printed usage information because building 
#  non-trunk development branches (e.g., gcc-5-branch) is currently broken.  A fix could be 
#  accomplished most elegantly by first implementing POSIX getopts argument parsing.
}

# If the package name is recognized, then set the default version.
# Otherwise, list the allowable package names and default versions and then exit.
set_default_version()
{
  if [[ $package_to_build == "--list" || $package_to_build == "-l" ]]; then
       printf "\n"
       printf "The '$this_script' script can build the following packages:\n"
  fi
  # This is a bash 3 hack standing in for a bash 4 hash (bash 3 is the lowest common
  # denominator because, for licensing reasons, OS X only has bash 3 by default.)
  # See http://stackoverflow.com/questions/1494178/how-to-define-hash-tables-in-bash
  package_version=(
    "cmake:3.4.0"
    "gcc:5.3.0"
    "mpich:3.1.4"
    "wget:1.16.3"
    "flex:2.6.0"
    "bison:3.0.4"
    "pkg-config:0.28"
    "make:4.1"
    "m4:1.4.17"
    "subversion:1.9.2"
    "_unknown:0"
  )
  for package in "${package_version[@]}" ; do
     KEY="${package%%:*}"
     VALUE="${package##*:}"
     if [[ "$package_to_build" == "--list" || $package_to_build == "-l" ]]; then
       # If we are not at the end of the list, print the current element of the name:version list.
       if [[ !("$KEY" ==  "_unknown") ]]; then
         printf "%s (default version %s)\n" "$KEY" "$VALUE"
       fi
     elif [[ "$KEY" == "_unknown" ]]; then
       # No recognizeable argument passed so exit:
       printf "$this_script: Package name not recognized.  Execute '$this_script --list' to list the allowable package names.\n"
       echo ""
       echo "Aborting. [exit 20]"
       exit 20
     elif [[ $package_to_build == "$KEY" ]]; then
       # We recognize the package name so we set the default version:
       if [[ $2 != "--default" ]]; then
         printf "Using default version $VALUE\n"
       fi
       default_version=$VALUE
       break
     fi
  done
  if [[ $package_to_build == "--list" || $package_to_build == "-l" ]]; then
    echo ""
    echo "Aborting. [exit 30]"
    exit 30
  fi
}

check_prerequisites()
{
  if [[ "$package_to_build" == "gcc" && "$version_to_build" != "trunk" ]]; then
    gcc_fetch="ftp-url"
  else
    gcc_fetch="svn"
  fi
  # This is a bash 3 hack standing in for a bash 4 hash (bash 3 is the lowest common
  # denominator because, for licensing reasons, OS X only has bash 3 by default.)
  # See http://stackoverflow.com/questions/1494178/how-to-define-hash-tables-in-bash
  package_download_mechanism=(
    "gcc:$gcc_fetch"
    "wget:ftp-url"
    "cmake:wget"
    "mpich:wget"
    "flex:wget"
    "bison:ftp-url"
    "pkg-config:wget"
    "make:ftp-url"
    "m4:ftp-url"
    "subversion:wget"
    "_unknown:0"
  )
  for package in "${package_download_mechanism[@]}" ; do
     KEY="${package%%:*}"
     VALUE="${package##*:}"
     if [[ "$KEY" == "_unknown" ]]; then
       printf "$this_script: No specified dowload mechanism.\n"
       printf "Please add a 'KEY:VALUE' pair to the 'package_download_mechanism' list in the '$this_script' script.\n"
       echo "Aborting. [exit 40]"
       exit 40
     elif [[ "$package_to_build" == "$KEY" ]]; then
       # Set the download mechanism corresponding to the recognized package:
       fetch=$VALUE
       break
     fi
  done
  # Default to gcc/g++ if there are no user-specified compilers in the path
  if [ -z $CC ]; then
    CC=gcc
  fi
  if [ -z $CXX ]; then
    CXX=g++
  fi
  if ! type make > /dev/null; then
    printf "$this_script: 'make' is required for compiling packages from source. \n"
    printf " Please ensure that 'make' is installed and in your path.  Aborting.\n"
    echo "Aborting. [exit 50]"
    exit 50
  fi
}

# Define the package location 
set_url()
{
  if [[ $package_to_build == 'cmake' ]]; then
    major_minor="${version_to_build%.*}"
  elif [[ "$package_to_build" == "gcc" && "$version_to_build" != "trunk" ]]; then
    gcc_url_head="ftp.gnu.org:/gnu/gcc/gcc-$version_to_build/"
  else
    gcc_url_head="svn://gcc.gnu.org/svn/gcc/"
  fi
  package_url_head=(
    "gcc;$gcc_url_head"
    "wget;ftp.gnu.org:/gnu/wget/"
    "m4;ftp.gnu.org:/gnu/m4/"
    "pkg-config;http://pkgconfig.freedesktop.org/releases/"
    "mpich;http://www.mpich.org/static/downloads/$version_to_build/"
    "flex;http://sourceforge.net/projects/flex/files/"
    "make;ftp://ftp.gnu.org/gnu/make/"
    "bison;ftp.gnu.org:/gnu/bison/"
    "cmake;http://www.cmake.org/files/v$major_minor/"
    "subversion;http://www.eu.apache.org/dist/subversion/"
    "_unknown;0"
  )
  for package in "${package_url_head[@]}" ; do
     KEY="${package%%;*}"
     VALUE="${package##*;}"
     if [[ "$KEY" == "_unknown" ]]; then
       # No recognizeable argument passed so exit:
       printf "$this_script: Package name not recognized.  Execute '$this_script --list' to list the allowable package names.\n"
       echo "Aborting. [exit 60]"
       exit 60
     elif [[ $package_to_build == "$KEY" ]]; then
       # We recognize the package name so we set the URL head:
       url_head=$VALUE
       break
     fi
  done

  # Set differing tails for GCC trunk versus branches
  if [[ $package_to_build == 'gcc' ]]; then
    if [[ $fetch == 'svn' ]]; then
      gcc_tail=$version_to_build
    elif [[ $version_to_build == '--avail' || $version_to_build == '-a' ]]; then
      gcc_tail='branches'
    else
      gcc_tail="gcc-$version_to_build.tar.bz2"
    fi
  fi
  package_url_tail=(
    "gcc;$gcc_tail"
    "wget;wget-$version_to_build.tar.gz"
    "m4;m4-$version_to_build.tar.bz2"
    "pkg-config;pkg-config-$version_to_build.tar.gz"
    "mpich;mpich-$version_to_build.tar.gz"
    "flex;flex-$version_to_build.tar.bz2"
    "bison;bison-$version_to_build.tar.gz"
    "make;make-$version_to_build.tar.bz2"
    "cmake;cmake-$version_to_build.tar.gz "
    "subversion;subversion-$version_to_build.tar.gz"
    "_unknown;0"
  )
  for package in "${package_url_tail[@]}" ; do
     KEY="${package%%;*}"
     VALUE="${package##*;}"
     if [[ "$KEY" == "_unknown" ]]; then
       # No recognizeable argument passed so exit:
       printf "$this_script: Package name not recognized.  Execute '$this_script --list' to list the allowable package names.\n"
       echo "Aborting. [exit 70]"
       exit 70
     elif [[ $package_to_build == "$KEY" ]]; then
       # We recognize the package name so we set the URL tail:
       url_tail=$VALUE
       break
     fi
  done
  url="$url_head""$url_tail"

}

# Download a file from an anonymous ftp site 
#
# Usage:
#    ftp-url  <ftp-mode>  <ftp-site-address>:/<path-to-file>/<file-name>
#
# Example:  
#    ftp-url -n ftp.gnu.org:/gnu/m4/m4-1.4.17.tar.bz2

ftp-url()
{
  ftp_mode=$1
  url=$2

  text_before_colon="${url%%:*}"
  FTP_SERVER=$text_before_colon

  text_after_colon="${url##*:}"
  text_after_final_slash="${text_after_colon##*/}"
  FILE_NAME=$text_after_final_slash

  text_before_final_slash="${text_after_colon%/*}"
  FILE_PATH="$text_before_final_slash"

  USERNAME=anonymous
  PASSWORD=""
  echo "$this_script: starting anonymous download: ftp $ftp_mode $FTP_SERVER... cd $FILE_PATH... get $FILE_NAME"

ftp $ftp_mode $FTP_SERVER <<Done-ftp
user $USERNAME $PASSWORD
cd $FILE_PATH
passive
binary
get "$FILE_NAME"
bye
Done-ftp

echo "$this_script: finished anonymous ftp"
}

# Download pkg-config if the tar ball is not already in the present working directory
download_if_necessary()
{
  set_url
   
  if [ -f $url_tail ] || [ -d $url_tail ]; then
    echo "Found '$url_tail' in ${PWD}."
    echo "If it resulted from an incomplete download, building $package_to_build could fail."
    printf "Would you like to proceed anyway? (y/n)"
    read proceed
    if [[ $proceed == "y"  ]]; then
      printf "y\n"
    else
      printf "n\n"
      printf "Please remove $url_tail and restart the installation to to ensure a fresh download."
      echo "Aborting. [exit 80]"
      exit 80
    fi
  elif ! type $fetch &> /dev/null; then
    # The download mechanism is missing
    echo ""
    echo ""
    echo "*****************"
    printf "$this_script: The default download mechanism for $KEY is $fetch.\n"
    printf "$this_script: Please either ensure that $fetch is installed and in your PATH \n"
    printf "$this_script: or download the $KEY source from "
    set_url
    printf "$url\n" 
    called_by_install_sh=`echo "$(ps -p $PPID -o args=)" | grep install.sh`
    printf "$this_script: Place the downloaded file in ${PWD}\n"
    if [[ ! -z $called_by_install_sh ]]; then
      caller="install.sh"
    else
      caller="build"
    fi
    printf "$this_script: Then restart $caller. Aborting [exit 90]\n"
    echo "*****************"
    echo ""
    echo ""
    exit 90
  else
    # The download mechanism is in the path.
    if [[ "$fetch" == "svn" ]]; then
      if [[ $version_to_build == '--avail' || $version_to_build == '-a' ]]; then
        args=ls
      else
        args=checkout
      fi
    elif [[ "$fetch" == "wget" ]]; then
      args=--no-check-certificate
    elif [[ "$fetch" == "ftp-url" ]]; then
      args=-n
    elif [[ "$fetch" == "git" ]]; then
      args=clone
    fi
    if [[ $fetch == "svn" || $fetch == "git" ]]; then
      package_source_directory=$url_tail
    else
      package_source_directory=$package_to_build-$version_to_build
    fi
    printf "Downloading $package_to_build $version_to_build to the following location:\n"
    printf "$download_path/$package_source_directory \n"
    printf "Download command: $fetch $args $url\n"
    printf "Depending on the file size and network bandwidth, this could take several minutes or longer."
    $fetch $args $url
    if [[ $version_to_build == '--avail' || $version_to_build == '-a' ]]; then
      # In this case, args="ls" and the list of available versions has been printed so we can move on.
      exit 1
    fi
    if [[ "$fetch" == "svn" ]]; then
      search_path=$download_path/$version_to_build
    else
      search_path=$download_path/$url_tail
    fi
    if [ -f $search_path ] || [ -d $search_path ]; then
      echo "Download succeeded. The $package_to_build source is in the following location:"
      echo "$search_path"
    else
      echo "Download failed. The $package_to_build source is not in the following, expected location:"
      echo "$search_path"
      echo "Aborting. [exit 110]"
      exit 110
    fi
  fi
}

# Unpack if the unpacked tar ball is not in the present working directory
unpack_if_necessary()
{
  if [[ $fetch == "svn" || $fetch == "git" ]]; then
    package_source_directory=$version_to_build
  else
    printf "Unpacking $url_tail. \n"
    printf "Unpack command: tar xf $url_tail \n"
    tar xf $url_tail
    package_source_directory=$package_to_build-$version_to_build
  fi
}

. ./set_SUDO.sh

# Make the build directory, configure, and build
build_and_install()
{

  build_path=${PWD}/$package_to_build-$version_to_build-build &&
  printf "Building $package_to_build $version_to_build.\n" &&
  mkdir -p $build_path &&
  pushd $build_path &&
  set_SUDO_if_necessary &&
  if [[ $package_to_build == "gcc" ]]; then
    pushd $download_path/$package_source_directory &&
    ${PWD}/contrib/download_prerequisites &&
    popd &&
    echo "Configuring with the following command: " &&
    echo "$download_path/$package_source_directory/configure  --prefix=$install_path --enable-languages=c,c++,fortran,lto --disable-multilib --disable-werror " &&
    $download_path/$package_source_directory/configure --prefix=$install_path --enable-languages=c,c++,fortran,lto --disable-multilib --disable-werror &&
    echo "Building with the commmand 'make -j $num_threads bootstrap'" &&
    make -j $num_threads bootstrap &&
    if [[ ! -z $SUDO ]]; then
      echo "You do not have write permissions to the installation path $install_path"
      echo "If you have administrative privileges, enter your password to install $package_to_build."
    fi &&
    echo "Installing with the command '$SUDO make install'" &&
    $SUDO make install
  else
    $download_path/$package_source_directory/configure --prefix=$install_path &&
    echo "Building with the following command:" &&
    echo "CC=$CC CXX=$CXX make -j $num_threads" &&
    CC=$CC CXX=$CXX make -j $num_threads &&
    printf "Installing $package_to_build in $install_path.\n" &&
    if [[ ! -z $SUDO ]]; then
      echo "You do not have write permissions to the installation path $install_path"
      echo "If you have administrative privileges, enter your password to install $package_to_build."
    fi &&
    echo "Installing with the command '$SUDO make install'" &&
    $SUDO make install
  fi &&
  popd
}

# Print usage information and exit if script is invoked without arguments or with --help or -h as the first argument
if [ $# == 0 ]; then
  usage | less
  exit 120
elif [[ $1 == '--help' || $1 == '-h' ]]; then
  usage | less
  exit 130
fi

# Interpret the first argument as the name of the package to build
package_to_build=$1

# Interpret the second command-line argument, if present, as the package version.
# Otherwise, set the default package version.
if [[ -z $2 || $2 == "--default"  ]]; then
  set_default_version $*
  version_to_build=$default_version
else
  version_to_build=$2
fi

# Interpret the third command-line argument, if present, as the installation path.
# Otherwise, install in a subdirectory of the present working directory.
default_install_path=${PWD}/$package_to_build-$version_to_build-installation
if [[ -z $3 || $3 == "--query-path" ]]; then
  install_path=$default_install_path
else
  install_path=$3
fi

# Interpret the fourth command-line argument, if present, as the number of threads for 'make'.
# Otherwise, default to single-threaded 'make'.
if [ -z $4 ]; then
  num_threads=1
else
  num_threads=$4
fi

if [[ $1 == '--help' || $1 == '-h' ]]; then
  # Print usage information if script is invoked with --help or -h argument
  usage | less
elif [[ $2 == '--default' && $3 == '--query-path' ]]; then
  # Print the installation path and exit
  printf "$install_path\n"
  exit 0
elif [[ $2 == '--default' && $3 == '--query-version' ]]; then
  printf "$default_version\n"
  exit 0
elif [[ $2 == '--default' && $3 == '--query-url' ]]; then
  set_url
  printf "$url\n"
  exit 0
elif [[ $1 == '-v' || $1 == '-V' || $1 == '--version' ]]; then
  # Print script copyright if invoked with -v, -V, or --version argument
  echo ""
  echo "OpenCoarrays prerequisites Build Script"
  echo "Copyright (C) 2015-2016 Sourcery, Inc."
  echo "Copyright (C) 2015-2016 Sourcery Institute"
  echo ""
  echo "$this_script comes with NO WARRANTY, to the extent permitted by law."
  echo "You may redistribute copies of $this_script under the terms of the"
  echo "BSD 3-Clause License.  For more information about these matters, see"
  echo "http://www.sourceryinstitute.org/license.html"
  echo ""
else
  # Download, unpack, and build CMake
  download_path=${PWD}
  check_prerequisites $* &&
  download_if_necessary &&
  unpack_if_necessary &&
  CC=$CC CXX=$CXX build_and_install $package_to_build $version_to_build $install_path $num_threads $download_path \
  >&1 | tee build-$package_to_build.log &&
  printf "\n" &&
  printf "$this_script: Done. Check for $package_to_build in the installation path $install_path\n" &&
  printf "\n"
fi
