#
# Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3 only, as
# published by the Free Software Foundation.
#
# This code 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
# version 3 for more details (a copy is included in the LICENSE file that
# accompanied this code).
#
# You should have received a copy of the GNU General Public License version
# 3 along with this work; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
# or visit www.oracle.com if you need additional information or have any
# questions.
#
# Helper functions for llvm-cc et al
# Global variables are set by these functions

if [ -n "$FASTR_LLVM_TOOLMODE" ]
then
  run_mode=$FASTR_LLVM_TOOLMODE
else
  run_mode="run"
fi

function runit() {
  if [ $run_mode == "echo" ]
  then
    echo "$@"
  elif [ $run_mode == "echorun" ]
  then
    echo "$@"
    "$@"
  else
    "$@"
  fi
}

# Input: all the arguments to the original command line
# Global variables set:
# llvm_ir_file: name of file containing LLVM IR, e.g. foo.bc
# llvm_file_ext: extension of above, e.g. .bc
# llvm_args: processed arguments to pass to llvm tool, e.g. clang

function analyze_args() {
  llvm_args_tmp=()	
  llvm_args_tmp+=("-g")
  if [ $fortran -eq 1 ]
  then
    llvm_file_ext='.ll'
  else
    llvm_file_ext='.bc'
  fi

  is_link=0
  out_file_opt=()
  llvm_ir_file=""
  llvm_ir_file_opt=()
  c_opt_found=0

  while [[ $# -gt 0 ]]
  do
    case $1 in
      -c)
        c_opt_found=1
        llvm_args_tmp+=("$1")
      ;;
      -o)
        shift
        p=$1
        f=`basename $p`
        d=`dirname $p`
        ext=${f##*.}
        if [ $ext == 'so' ] || [ $ext == 'dylib' ] || [ $ext == 'sol' ] || [ $ext == 'dylibl' ]
        then
          is_link=1
        elif [ $ext == 'o' ] || [ $ext == 'bc' ]
		then
			llvm_ir_file=${d}/${f%%.*}
		    llvm_ir_file="${llvm_ir_file}${llvm_file_ext}"
         	llvm_ir_file_opt=("-o" $llvm_ir_file)
        else
          out_file_opt=("-o" "$p")
        fi
      ;;
      *)
        llvm_args_tmp+=("$1")
      ;;
    esac
    shift
  done

  llvm_args=()
  if [ $fortran -eq 1 ]
  then
    if [ $c_opt_found -eq 1 ]
    then
      llvm_args+=("-S") 
      llvm_args+=("${llvm_ir_file_opt[@]}")
      llvm_args+=("${llvm_args_tmp[@]}")
    else
      llvm_args+=("${out_file_opt[@]}")
      llvm_args+=("${llvm_args_tmp[@]}")
    fi
  else
    if [ $c_opt_found -eq 1 ]
    then
      llvm_args+=("-emit-llvm") 
      llvm_args+=("${llvm_ir_file_opt[@]}")
	  llvm_args+=("${llvm_args_tmp[@]}")
    else
      llvm_args+=("${out_file_opt[@]}")
      llvm_args+=("${llvm_args_tmp[@]}")
    fi
  fi
  
}

# Input arguments:
# llvm_tool: name of tool to find
# Global variables set:
# path to tool (defaults to plain ${llvm_tool}, assumed to be on the PATH)

function get_llvm_tool() {
  if [ -n "${FASTR_LLVM_TOOLS}" ]
  then
    llvm_tool_bin=${FASTR_LLVM_TOOLS}/${llvm_tool}
  else
    llvm_tool_uc=`echo ${llvm_tool} | tr /a-z/ /A-Z/ | tr /+/ /P/`
    x=FASTR_LLVM_${llvm_tool_uc}
    if [ -n  "${!x}" ]
    then
      llvm_tool_bin=${!x}
    else
      llvm_tool_bin=${llvm_tool}
    fi
  fi
}

function mem2reg_opt() {
   llvm_tool="opt"
   get_llvm_tool
   runit ${llvm_tool_bin} -mem2reg $llvm_ir_file -o ${llvm_ir_file}.opt
   rc=$?
   if [ $rc -eq 0 ]
   then
     runit mv ${llvm_ir_file}.opt $llvm_ir_file
   fi
}

# Input: all the arguments to the original command line
function create_bc_lib() {
  bcfiles=""
  lib=""
  statlibs="$R_PACKAGE_DIR/statlibs"
  linkedLibs="LIBS"
  > $linkedLibs
  while [[ $# -gt 0 ]]
  do
    case $1 in
      -o)
        shift
		    lib="$1l"
        ;;
      -l*)
		    linkedLib=`echo $1 | cut -c 3-`
        statLibFound='0'
        if [ -f "$statlibs" ]; then
          statLibFound=`cat "$statlibs" | grep "lib${linkedLib}.al" | wc -l`
        fi
        if [ $statLibFound == '0' ]
		    then
	          echo $linkedLib >> $linkedLibs
		    fi
        ;;
      -*)
        # ignore other options
        ;;
      *)
        f=$(basename $1)
		ext=${f##*.}
		if [ $ext == 'o' ] || [ $ext == 'bc' ]
		then
	  		fn="$(dirname $1)/${f%%.*}.bc"
			bcfiles+="$fn "
		fi
        ;;
    esac
    shift
  done

# we do not have the luxury of controlling the name of the entry (unlike in python)
# it will be the pathname, which we will reduce to a module name on input in FastR

# link the bitcode files into a single one using llvm-link before zipping it
  llvm_tool=llvm-link
  get_llvm_tool
  echo "Linking $lib.bc from LLVM modules: $bcfiles"
  runit $llvm_tool_bin $bcfiles -o $lib.bc
  runit zip -r $lib $lib.bc $linkedLibs
  rm $lib.bc
  if [ -d "$R_PACKAGE_DIR/libs" ]; then
    cp $lib "$R_PACKAGE_DIR/libs"
  fi
}

# It appends objects constituting a static library to the list of extra object files.
# This list is maintained in $R_PACKAGE_DIR/libobjects and is later read in llvm-cc and
# llvm-c++ to append the object files from the list to other objects when making
# the package dynamic library. This mechanism is a workaround for the fact that the llvm
# linker does not support linking to static libraries, as the standard linker does
# through the -l and -L options.
function create_bc_archive() {

  # Create a CSV file containing all object files constituting the archive (i.e. the static library).
  # The CSV has two columns: the object name in the archive and the full path of the object file.
  # The object name columns serves as the join column when joining with the set of unique
  # symbols objects (see below) to get the final list of objects to be linked with
  # the package dynamic library in the end.
  shift
  archname="$1l"
  arargs="rcs $archname "
  shift

  archObjCSVtmp="archived_objects.tmp"
  archObjCSV="archived_objects.csv"
  exportedObjCSV="exported_objects.csv"
  > $archObjCSV

  while [[ $# -gt 0 ]]
  do
    case $1 in
     *)
        f=$1
     	ext=${f##*.}
	if [ $ext == 'o' ]
	then
	  fullPath=`echo $(cd $(dirname $1) && pwd -P)/$(basename $1)`
	  fn=${f%%.*}.bc
	  arargs+="$fn "
	  echo "$(basename $fn) $fullPath" >> $archObjCSVtmp
	else
	  arargs+="$1 "
	fi
     ;;
    esac
    shift
  done

  sort -d --field-separator=' ' --key=1 $archObjCSVtmp > $archObjCSV

  # Create the archive (via llvm-ar) that is then used to read the symbol table via llvm-nm.
  # The symbol table allows for selecting a set of objects exporting unique symbols.
  llvm-ar $arargs

  # This command extracts a set of objects from the archive that do not export duplicate symbols
  llvm-nm -print-file-name $archname | grep "\-\-\-\-" | awk -F ' ' '{print $1" "$4}' | sort -d --field-separator=' ' --key=2 -u | awk -F ':' '{print $2}' | sort -d -u > $exportedObjCSV

  # Join the archived objects CSV with the unique symbols objects CSV to get the list of objects to be statically linked to the package dynamic library.
  # Append the result to the global list of extra object files.
  join -t " " -1 1 -2 1 $archObjCSV $exportedObjCSV | awk -F ' ' '{print $2}' >> "$R_PACKAGE_DIR/libobjects"

  # Record the archive (static library) name to distinguish between LLVM static libs and native libs when linking (via -l)
  echo $archname >> "$R_PACKAGE_DIR/statlibs"
}

# A simple try-catch mimicking apparatus

function try()
{
    [[ $- = *e* ]]; SAVED_OPT_E=$?
    set -e
}

function throw()
{
    exit $1
}

function catch()
{
    export ex_code=$?
    (( $SAVED_OPT_E )) && set +e
    return $ex_code
}

function recordError()
{
	echo "***************************************************************"
	echo "The LLVM build failed. Only the native libraries will be built."
	echo "***************************************************************"

	errorFlagFile="$R_PACKAGE_DIR/errorFlag"
	touch $errorFlagFile
}

function errorOccurred()
{
	errorFlagFile="$R_PACKAGE_DIR/errorFlag"
	[[ -f $errorFlagFile ]]
}