#!/bin/bash
#
# This is the spinalcord toolbox (SCT) installer
# It downloads the Conda (http://conda.pydata.org/) version
# of python and installs the SCT requirements over it
#
# The SCT can be installed in the location where you download it. If you choose to do so,
# do not delete the source code or you will delete the installation too!
#
# If you run the installer as super user, the default install is /opt,
# if you choose this option or any other directory other than the
# source location, you can get rid of the source code after the
# installation is successful.
#
# USAGE
#   ./install_sct
#   yes | ./install_sct   # will install without interruption with 'yes' as default answer
#
# Copyright (c) 2019 Polytechnique Montreal <www.neuro.polymtl.ca>
# Authors: PO Quirion, J Cohen-Adad, J Carretero
# License: see the file LICENSE.TXT

set -v  # v: verbose, e: exit if non-zero output is encountered. Using set -e will exit even when trying to remove
# a folder that already exists, therefore, it should only be used for debugging mode.

# Where tmp file are stored
TMP_DIR=$(mktemp -d 2>/dev/null || mktemp -d -t 'TMP_DIR')
# Start Directory So we go back there at the end of the Script
SCT_SOURCE=$PWD
SCRIPT_DIR="scripts"
DATA_DIR="data"
PYTHON_DIR="python"
BIN_DIR="bin"
# Misc
OSXVERSUPPORTED=12  # minimum version of OSX supported


# ======================================================================================================================
# FUNCTIONS
# ======================================================================================================================

# Print with color
# @input1: {info, code, error}: type of text
# @input2: text to print
function print() {
  type=$1
  txt=$2
  case $type in
  # Display useful info (green)
  info)
    echo -e "\n\033[0;32m${txt}\033[0m\n"
    ;;
  # To interact with user (no carriage return) (light green)
  question)
    echo -e -n "\n\033[0;92m${txt}\033[0m"
    ;;
  # To display code that is being run in the Terminal (blue)
  code)
    echo -e "\n\033[0;34m${txt}\033[0m\n"
    ;;
  # Warning message (yellow)
  warning)
    echo -e "\n\033[0;93m${txt}\033[0m\n"
    ;;
  # Error message (red)
  error)
    echo -e "\n\033[0;31m${txt}\033[0m\n"
    ;;
  esac
}

# Elegant exit with colored message
function die() {
  print error "$1"
  exit 1
}

# Run a command and display it in color. Exit if error.
# @input: string: command to run
function run() {
  cmd=$1
  print code "$cmd"
  $cmd
  if [[ $? != 0 ]]; then
    die "ERROR: Command failed."
  fi
}

# Force a clean exit
function finish() {
  # Catch the last return code
  value=$?
  # Get back to starting point
  cd $SCT_SOURCE
  if [[ $value -eq 0 ]]; then
    print info "Installation finished successfully!"
  elif [[ $value -eq 99 ]]; then
    # Showing usage with -h
    echo ""
  else
    print error "Installation failed"
  fi
  # clean tmp_dir
  rm -r $TMP_DIR
  exit $value
}

# reenable tty echo when user presses keyboard interrupt and output non-zero status for finish() function
detectKeyboardInterrupt() {
      # reenable tty echo
      print error "Installation aborted by the user."
      stty icanon echo echok
      exit 1
}

# Fetches the OS type
# @output: OS var is modified with the appropriate OS
function fetch_os_type() {
  print info "Checking OS type and version..."
  OSver="unknown"  # default value
  uname_output=`uname -a`
  echo $uname_output
  # OSX
  if echo $uname_output | grep -i darwin >/dev/null 2>&1; then
    # Fetch OSX version
    sw_vers_output=`sw_vers | grep -e ProductVersion`
    echo "$sw_vers_output"
    OSver=`echo $sw_vers_output | cut -c 20- | cut -c -2`
    # Make sure OSver us supported
    if (("$OSver" < "$OSXVERSUPPORTED")); then
      die "Sorry, this version of OSX (10.$OSver) is no more supported. The minimum version is 10.$OSXVERSUPPORTED".
    fi
    # Fix for non-English Unicode systems on MAC
    if [[ -z $LC_ALL ]]; then
      export LC_ALL=en_US.UTF-8
    fi

    if [[ -z $LANG ]]; then
      export LANG=en_US.UTF-8
    fi
    OS=osx
    # make sure bashrc is loaded when starting a new Terminal
    force_bashrc_loading
  # Linux
  elif echo $uname_output | grep -i linux >/dev/null 2>&1; then
    # CentOS 6.X
    if cat /etc/issue | grep -i centos | grep 6. 2>&1; then
      OS=linux_centos6
    # RedHat 6.X
    elif cat /etc/issue | grep -i Red | grep 6. 2>&1; then
      OS=linux_centos6
    # Other Linux
    else
      OS=linux
    fi
  else
    die "Sorry, the installer only supports Linux and OSX, quitting installer"
  fi
}

# Checks if the necessary tools for SCT are installed on the machine
function check_requirements() {
  print info "Checking requirements..."
  # check curl
  if [[ ! $(which curl) && ! $(which wget) ]]; then
    die "ERROR: neither \"curl\" nor \"wget\" is installed. Please install either of them and restart SCT installation."
  fi
  # check gcc
  gcc --version > /dev/null 2>&1  # run silently, then check output status
  if [[ $? -ne 0 ]]; then
    print warning "WARNING: \"gcc\" is not installed."
    if [[ $OS == "osx" ]]; then
      while [[ ! $GCC_INSTALL =~ ^([Yy](es)?|[Nn]o?)$ ]]; do
        print question "Do you want to install it now? (accepting to install \"gcc\" will also install \"brew\" in case it is not installed already)? [y]es/[n]o: "
        read GCC_INSTALL
      done
      if [[ $GCC_INSTALL =~ [Yy](es)? ]]; then
        if [[ ! $(which brew) ]]; then
          yes | /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
        fi
        yes | brew install gcc
        # check if gcc install ran properly
        gcc --version > /dev/null 2>&1  # run silently, then check output status
        if [[ $? -ne 0 ]]; then
          die "ERROR: Installation of \"gcc\" failed. Please contact SCT team for assistance."
        fi
      else
        die "Please install \"gcc\" and restart SCT installation."
      fi
    else
      die "Please install \"gcc\" and restart SCT installation. On Debian/Ubuntu, run: \"apt install gcc\". On CentOS/RedHat, run: \"yum -y install gcc\"."
    fi
  fi
  print info "OK!"
}

# Gets the shell rc file path based on the default shell.
# @output: THE_RC and RC_FILE_PATH vars are modified
function get_shell_rc_path() {
  if [[ $SHELL == *"bash"* ]]; then
    THE_RC="bash"
    RC_FILE_PATH="$HOME/.bashrc"
  elif [[ $SHELL == *"zsh"* ]]; then
    THE_RC="bash"
    RC_FILE_PATH="$HOME/.zshrc"
  elif [[ $SHELL == *"csh"* ]]; then
    THE_RC="csh"
    RC_FILE_PATH="$HOME/.cshrc"
  else
    die "ERROR: Shell was not recognized: $SHELL"
  fi
}

# Force bashrc loading
function force_bashrc_loading() {
  sourceblock="
if [[ -n \"\$BASH_VERSION\" ]]; then
    # include .bashrc if it exists
    if [[ -f \"\$HOME/.bashrc\" ]]; then
    . \"\$HOME/.bashrc\"
    fi
fi"
  for profiles in ~/.bash_profile ~/.bash_login ~/.profile; do
    if [[ -a $profiles ]]; then
      if ! grep -E "(\.|source) .*bashrc" $profiles >/dev/null 2>&1; then
        echo "$sourceblock" >>$profiles
      fi
      bidon=0
      break
    fi
  done

  if [[ -z $bidon ]]; then
    echo "$sourceblock" >>~/.bash_profile
  fi
}

# Installation text to insert in shell config file
function edit_shellrc() {
  # Write text common to all shells
  echo
  echo "" >>$RC_FILE_PATH
  echo "# SPINALCORDTOOLBOX (installed on $(date +%Y-%m-%d\ %H:%M:%S))" >>$RC_FILE_PATH
  echo $DISPLAY_UPDATE_PATH >>$RC_FILE_PATH
  # Switch between shell
  if [[ $THE_RC == "bash" ]]; then
    echo "export SCT_DIR=$SCT_DIR" >>$RC_FILE_PATH
    echo "export MPLBACKEND=Agg" >>$RC_FILE_PATH
  elif [[ $THE_RC == "csh" ]]; then
    echo "setenv SCT_DIR $SCT_DIR" >>$RC_FILE_PATH
    echo "setenv MPLBACKEND Agg" >>$RC_FILE_PATH
  fi
  # add line
  echo "" >>$RC_FILE_PATH
}

# Download from URL using curl/wget
function download() {
  # Use curl or wget to download goodies
  e_status=0
  # Try with wget
  if [[ $(which wget) ]]; then
    cmd="wget -O $1 $2"
    print code "$cmd"
    $cmd
    e_status=$?
    echo exit status is $e_status
  fi
  # Try with curl
  if [[ $(which curl) && ! -e $1 ]]; then
    cmd="curl -o $1 -L $2"
    print code "$cmd"
    $cmd
    e_status=$?
    echo exit status is $e_status
  fi
  # check success
  if [[ $e_status -ne 0 || ! -e $1 ]]; then
    die "The download of $2 failed\n
Please check your internet connection before relaunching the installer\n"
  fi
}

# Usage of this script
function usage() {
  echo -e "\nUsage: $0 [-d] [-b] [-v]" 1>&2
  echo -e "\nOPTION"
  echo -e "\t-d \v Prevent the (re)-installation of the \"data/\" directory "
  echo -e "\n\t-b \v Prevent the (re)-installation of the SCT binaries files "
  echo -e "\n\t-v \v Full verbose"
}


# ======================================================================================================================
# SCRIPT STARTS HERE
# ======================================================================================================================

# This trap specifically catches keyboardInterrupt and output a non-zero status before running finish()
trap detectKeyboardInterrupt INT
# Set a trap which, on shell error or shell exit, runs finish()
trap finish EXIT

print info "
*******************************
* Welcome to SCT installation *
*******************************
"

# ----------------------------------------------------------------------------------------------------------------------
# CLI parser
# ----------------------------------------------------------------------------------------------------------------------

fetch_os_type
check_requirements

# Transform  long option "--long" into short option  "-l"
for arg in "$@"; do
  shift
  case "$arg" in
    *)       set -- "$@" "$arg"
  esac
done

while getopts ":dhbpv" opt; do
  case $opt in
  d)
    echo " data directory will not be (re)-installed"
    NO_DATA_INSTALL=yes
    ;;
  b)
    echo " SCT binaries will not be (re)-installed "
    NO_SCT_BIN_INSTALL=yes
    ;;
  v)
    echo " Full verbose!"
    set -x
    ;;
  h)
    usage
    exit 99
    ;;
  \?)
    usage
    exit 99
    ;;
  esac
done


# ----------------------------------------------------------------------------------------------------------------------
# Prepare installation
# ----------------------------------------------------------------------------------------------------------------------

# Catch SCT version
if [[ -e "version.txt" ]]; then
  SCT_VERSION=$(cat version.txt)
else
  die "ERROR: version.txt not found. \n
The install_sct script must be executed from the source directory"
fi

# Get installation type (from git or from package)
if [[ "x$SCT_INSTALL_TYPE" == "x" ]]; then
  if [[ -d ".git" ]]; then
    # folder .git exist, therefore it is a git installation
    SCT_INSTALL_TYPE="in-place"
  else
    SCT_INSTALL_TYPE="package"
  fi
fi

# Define sh files
get_shell_rc_path

# Display install info
echo -e "\nSCT version ......... "$SCT_VERSION
echo -e "Installation type ... "$SCT_INSTALL_TYPE
echo -e "Operating system .... $OS ($OSver)"
echo -e "Shell config ........ "$RC_FILE_PATH

# If you do not want the crash reports question to be ask,
# set ASK_REPORT_QUESTION at installation time like this:
# >>> ASK_REPORT_QUESTION=false ./install_sct
REPORT_STATS=no
if [[ ! $ASK_REPORT_QUESTION =~ ^([[Ff]alse?|[Nn]o?)$ ]]; then
# Send crash statistic and error logs to developers, that is the question:
  print question "To improve user experience and fix bugs, the SCT development team is using a
report system to automatically receive crash reports and errors from users.
These reports are anonymous.

Do you agree to help us improve SCT? [y]es/[n]o: "
  read REPORT_STATS
fi

if [[ $REPORT_STATS =~ [Yy](es)? ]]; then
  echo -ne '# Auto-generated by install_sct\nimport os\nSENTRY_DSN=os.environ.get("SCT_SENTRY_DSN", "https://5202d7c96ad84f17a24bd2653f1c4f9e:c1394bb176cc426caf0ff6a9095fb955@sentry.io/415369")\n' >spinalcordtoolbox/sentry_dsn.py
  print info "--> Crash reports will be sent to the SCT development team. Thank you!"
else
  print info "--> Crash reports will not be sent."
fi

# if installing from git folder, then becomes default installation folder
if [[ "$SCT_INSTALL_TYPE" == "in-place" ]]; then
  SCT_DIR=$SCT_SOURCE
else
  SCT_DIR="$HOME/sct_$SCT_VERSION"
fi

# Set install dir
while true; do
#  print info "SCT will be installed here: [$SCT_DIR]"
  while [[ ! $change_default_path =~ ^([Yy](es)?|[Nn]o?)$ ]]; do
    print question "SCT will be installed here: [$SCT_DIR]

Do you agree? [y]es/[n]o: "
    read change_default_path
  done
  if [[ $change_default_path =~ ^[Yy] ]]; then
    # user accepts default path --> exit loop
    break
  fi
  print question "Choose install directory. Warning! Give full path (e.g. /usr/django/sct_v3.0): \n"
  # user enters new path
  read new_install

  # Expand ~/
  new_install=${new_install/#\~\//$HOME\/}
  # Remove trailing /
  new_install=${new_install%/}

  # Avoid horrible bug, like removing /bin if SCT_DIR "/" or $HOME/bin
  if [[ "$new_install" == "/" ]] || [[ "$HOME" == "${new_install%/}" ]]; then
    print info "Cannot be installed directly in $new_install"
    print info "Please pick a full path"
    continue
  elif [[ -d "$new_install" ]]; then
    # directory exists --> update SCT_DIR and exit loop
    print warning "WARNING: Directory already exists. Files will be overwritten."
    SCT_DIR=$new_install
    break
  elif [[ ! "$new_install" ]]; then
    # If no input, asking again, and again, and again
    continue
  else
    SCT_DIR=$new_install
    break
  fi
done

# Create directory
mkdir -p $SCT_DIR
# check if directory was created
if [[ -d "$SCT_DIR" ]]; then
  # check write permission
  if [[ ! -w "$SCT_DIR" ]]; then
    die "ERROR: $SCT_DIR exists but does not have write permission."
  fi
else
  die "ERROR: $SCT_DIR cannot be created. Make sure you have write permission."
fi

# Update PATH variables based on Shell type
UPDATE_PATH="export PATH=$SCT_DIR/$BIN_DIR:$PATH"
if [[ $THE_RC == "bash" ]]; then
  DISPLAY_UPDATE_PATH="export PATH=\"$SCT_DIR/$BIN_DIR:\$PATH\""
elif [[ $THE_RC == "csh" ]]; then
  DISPLAY_UPDATE_PATH="setenv PATH \"$SCT_DIR/$BIN_DIR:\$PATH\""
else
  die "This variable is not recognized: THE_RC=$THE_RC"
fi

# Update MPLBACKEND on headless system. See: https://github.com/neuropoly/spinalcordtoolbox/issues/2137
if [[ -z $MPLBACKEND ]]; then
  export MPLBACKEND=Agg
fi

# Copy files to destination directory
if [[ "$SCT_DIR" != "$SCT_SOURCE" ]]; then
  print info "Copying source files from $SCT_SOURCE to $SCT_DIR"
  cp -vR $SCT_INSTALL_CP_OPTIONS "$SCT_SOURCE/"* "$SCT_DIR/" | while read line; do echo -n "."; done
else
  print info "Skipping copy of source files (source and destination folders are the same)"
fi

# Clean old install setup in bin/ if existing
if [[ -x $SCT_DIR/$BIN_DIR ]]; then
  print info "Removing sct and isct softlink from $SCT_DIR/$BIN_DIR"
  find $SCT_DIR/$BIN_DIR -type l -name \"sct_*\" -exec rm {} \;
  find $SCT_DIR/$BIN_DIR -type l -name \"isct_*\" -exec rm {} \;
fi

# Go to installation folder
cd $SCT_DIR

# Make sure we are in SCT folder (to avoid deleting folder from user)
if [[ ! -f "version.txt" ]]; then
  die "ERROR: Cannot cd into SCT folder. SCT_DIR="$SCT_DIR
fi


# ----------------------------------------------------------------------------------------------------------------------
# Install Python
# ----------------------------------------------------------------------------------------------------------------------

# We make sure that there is no conflict with local python install by unsetting PYTHONPATH and forcing PYTHONNOUSERSITE
unset PYTHONPATH
export PYTHONNOUSERSITE=1

# Remove old python folder
print info "Installing conda..."
run "rm -rf $SCT_DIR/$PYTHON_DIR"
run "mkdir -p $SCT_DIR/$PYTHON_DIR"

# Download miniconda
case $OS in
linux*)
  download $TMP_DIR/miniconda.sh https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh
  ;;
osx)
  download $TMP_DIR/miniconda.sh https://repo.continuum.io/miniconda/Miniconda3-latest-MacOSX-x86_64.sh
  ;;
esac

# Run conda installer
run "bash $TMP_DIR/miniconda.sh -p $SCT_DIR/$PYTHON_DIR -b -f"

# create py3.6 venv (for Keras/TF compatibility with Centos7, see issue #2270)
yes | python/bin/conda create -n venv_sct python=3.6

# activate miniconda
source python/etc/profile.d/conda.sh
conda activate venv_sct

# Install Python dependencies
print info "Installing Python dependencies..."
pip install numpy
# Check if a frozen version of the requirements exist (for release only)
if [[ -f "requirements-freeze.txt" ]]; then
  print info "Using requirements-freeze.txt (release installation)"
  pip install -r requirements-freeze.txt
else
  # Not a package
  print info "Using requirements.txt (git installation)"
  pip install -r requirements.txt
fi
if [[ $? != 0 ]]; then
  die "Failed running pip install: $?"
fi

## Install the spinalcordtoolbox into the Conda venv
pip install -e $SCT_DIR
e_status=$?
if [[ $e_status != 0 ]]; then
  die "Failed to pip install sct."
fi

## Create launchers for Python scripts
print info "Creating launchers for Python scripts..."
mkdir -p $SCT_DIR/$BIN_DIR
for file in $SCT_DIR/python/envs/venv_sct/bin/*sct*; do
  cp "$file" "$SCT_DIR/$BIN_DIR/"
  res=$?
  if [[ $res != 0 ]]; then
    die "Problem creating launchers!"
  fi
done


# ----------------------------------------------------------------------------------------------------------------------
# Install shell scripts
# ----------------------------------------------------------------------------------------------------------------------

## Make shell scripts executable
print info "Make shell scripts executable..."
for file in $SCT_DIR/shell/*; do
  chmod 775 "$file"
done

## Create symbolic links for Shell scripts (-f: overwrite if exists)
print info "Creating symbolic links for Shell scripts..."
ln -s -f $SCT_DIR/shell/sct_run_batch.sh $SCT_DIR/$BIN_DIR/sct_run_batch
ln -s -f $SCT_DIR/shell/_run_with_log.sh $SCT_DIR/$BIN_DIR/_run_with_log.sh
res=$?
if [[ $res != 0 ]]; then
  die "Problem creating symlinks!"
fi

## Update PATH within this script (to launch sct_check_dependencies)
$UPDATE_PATH


# ----------------------------------------------------------------------------------------------------------------------
# Download binaries and data
# ----------------------------------------------------------------------------------------------------------------------

# Install binaries
if [[ $NO_SCT_BIN_INSTALL ]]; then
  print warning "WARNING: SCT binaries will not be (re)-installed"
else
  print info "Installing binaries..."
  if [[ $OS == "linux" ]]; then
    run "sct_download_data -d binaries_debian -o $SCT_DIR/$BIN_DIR"
  elif [[ $OS == "linux_centos6" ]]; then
    run "sct_download_data -d binaries_centos -o $SCT_DIR/$BIN_DIR"
  elif [[ $OS == "osx" ]]; then
    run "sct_download_data -d binaries_osx -o $SCT_DIR/$BIN_DIR"
  else
    die "Unsupported OS $OS: can't install binaries."
  fi
fi
print info "All requirements installed!"

# Install data
if [[ $NO_DATA_INSTALL ]]; then
  print warning "WARNING: data/ will not be (re)-install"
else
  # forcing activation if python is not reinstalled
  run ". $SCT_DIR/$PYTHON_DIR/bin/activate $SCT_DIR/$PYTHON_DIR"
  # Download data
  print info "Installing data..."
  run "rm -rf $SCT_DIR/$DATA_DIR"
  run "mkdir -p $SCT_DIR/$DATA_DIR"
  run "cd $SCT_DIR/$DATA_DIR"
  for data in PAM50 gm_model optic_models pmj_models deepseg_sc_models deepseg_gm_models deepseg_lesion_models c2c3_disc_models; do
    run "sct_download_data -d $data"
  done
fi


# ----------------------------------------------------------------------------------------------------------------------
# Validate installation
# ----------------------------------------------------------------------------------------------------------------------

# Deactivating conda
. $SCT_DIR/$PYTHON_DIR/bin/deactivate >/dev/null 2>&1

# In case of previous SCT installation (4.0.0-beta.1 or before), remove sct_env declaration in bashrc
print info "In case an old version SCT is already installed (4.0.0-beta.1 or before), remove 'sct_env' declaration in RC file"
if [[ $OS == "osx" ]]; then
  sed -ie '/sct_env/ s/^#*/#/' $RC_FILE_PATH
else
  sed -e '/sct_env/ s/^#*/#/' -i $RC_FILE_PATH
fi

# update PATH environment
while [[ ! $add_to_path =~ ^([Yy](es)?|[Nn]o?)$ ]]; do
  print question "Do you want to add the sct_* scripts to your PATH environment? [y]es/[n]o: "
  read add_to_path
done

if [[ $add_to_path =~ ^[Yy] ]]; then
  edit_shellrc
else
  print info "Not adding $SCT_DIR to \$PATH.
You can always add it later or call SCT functions with full path $SCT_DIR/$BIN_DIR/sct_xxx"
fi

# run sct_check_dependencies
print info "Validate installation..."
# We run the sct_check_dependencies in the TMP_DIR so the tmp.XXX output
# it creates is cleaned properly
if sct_check_dependencies; then
  if [[ $add_to_path =~ ^[Nn] ]]; then
    print info "To use SCT, please update your environment by running:
$DISPLAY_UPDATE_PATH"
  else
    print info "Open a new Terminal window to load environment variables, or run:
source $RC_FILE_PATH"
  fi
else
  die "Installation validation Failed!\n
Please copy the historic of this Terminal (starting with the command install_sct) and paste it in the SCT Help forum (create a new discussion):\n
http://forum.spinalcordmri.org/c/sct"
fi
