#!/usr/bin/env bash
####!/usr/bin/bash -vx
# sifdecoder: script to decode a sif file
#
#  N. Gould, D. Orban & Ph. Toint, November 7th, 2000
#  This version April 6th, 2024

display_short_help() {
    echo ' Use: sifdecoder [-A architecture] [-sp] [-h] [-c] [-f] [-b] [-a j]'
    echo '                       [-p package] [-s size] [-o j] [-m] [-st j]'
    echo '                       [-show] [-param name=value[,name=value...]]'
    echo '                       [-force] probname[.SIF]'
}

display_long_help() {
    display_short_help
    echo "where options:"
    echo "     -A     : specify the architecture. (Default: use $MYARCH)"
    echo "     -h     : print this help and stop execution"
    echo "     -o     : 0 for silent mode, 1 for brief description"
    echo "              of the stages executed. (Default: -o 0)"
    echo "     -m     : check for memory leaks using valgrind"
    echo "     -f     : use automatic differentiation in Forward mode"
    echo "     -b     : use automatic differentiation in Backward mode"
    echo "     -a     : 1 use the older HSL automatic differentiation "
    echo "                package AD01"
    echo "              2 use the newer HSL automatic differentiation "
    echo "                package AD02"
    echo "              (Default: -a 2)"
    echo "     -p     : decode for a particular package"
    echo "              1=LANCELOT, 2=BARIA, 3=CUTEst (default)"
    echo "     -c     : check derivatives for errors using finite"
    echo "              differences (default: do not check)"
    echo "     -s     : rough size of problem for array initialization"
    echo "              0=debug, 1=small, 2=medium, 3=large (default)"
    echo "     -sp    : decode problem in single precision (default: double)"
    echo "     -qp    : decode problem in quadruple precision (default: double)"
    echo "     -st    : starting point vector to be used"
    echo "              (Default: -st 1)"
    echo "     -show  : displays possible parameter settings for"
    echo "              probname[.SIF]. Other options are ignored"
    echo "     -param : cast probname[.SIF] against explicit parameter"
    echo "              settings. Several parameter settings may be"
    echo "              given as a comma-separated list following"
    echo "              -param or using several -param flags."
    echo "              Use -show to view possible settings."
    echo "     -force : forces setting of a parameter to the given value"
    echo "              even if this value is not specified in the file."
    echo "              This option should be used with care."
    echo "              (Default: do not enforce)."
    echo ""
    echo "  probname    probname.SIF is the name of the file containing"
    echo "              the SIF file for the problem of interest."
}

# Environment check

if [[ -z "$ARCHDEFS" ]]; then
  if [[ ! -d "$PWD/../archdefs" ]]; then
    echo ' The environment variable ARCHDEFS is not set and the directory
 ../../archdefs does not exist. Install the archdefs package,
 set $ARCHDEFS to the archdefs directory and re-run.'
    exit 1
  else
    export ARCHDEFS=$PWD/../archdefs
  fi
fi

. $ARCHDEFS/bin/helper_functions

if [[ ${SIFDECODE+set} != 'set' ]]; then
    error 'The SIFDECODE environment variable is not set.'
    exit 1
fi

if [[ ${MASTSIF+set} != 'set' ]]; then
    warning 'MASTSIF is not set. Problems may not be discovered.'
fi

#  Directory for temporary files

TMP="/tmp"

#  the command that invokes the fortran 95 compiler

FORTRAN="f95"

#  compiler flags for linking

FFLAGS=""
CUDAFLAGS=""

#  flags for compiling the fortran 77 problem-dependent roiutines

PROBFLAGS="-c -fixed"

# Obtain number of arguments

let last=$#
if (( last < 1 )); then
    display_short_help
    exit 1
fi

#  =========================
#  Variables for each option
#  =========================

# the architecture to be used

ARCH=""

# OUTPUT = 0 (summary output), = 1 (detailed output from decoder)

let OUTPUT=0

#   automatic = 0 (provided), = 1 (automatic forward), = 2 (automatic backward)

let automatic=0

#   ad0 = 1 (AD01 used), = 2 (AD02 used)

let ad0=2

# FORCE = 0 (do not enforce -param settings), = 1 (enforce settings)

let FORCE=0

# MEMCHECK = 0 (do not grind the code for memory leaks), = 1 (grind it)

MEMCHECK=0

# DERIVCHECK = 0 (do not check provided derivatives), = 1 (check them)

DERIVCHECK=0

#  throwback to LANCELOT days (1=LANCELOT, 2=BARIA, 3=CUTEst, other=3)

let PACKAGE=3

#  rough size of problem (0=debug,1=small, 2=medium, 3=large, other=3)

let size=2

#  specify the precision of the output files (single=32,double=64,quadruple=128)

let realpr=64

#  starting point vector to be used; if st > # starting vectors, st = 1

let st=1

#  -------------------
#  Interpret arguments
#  -------------------

let i=1
let nbparam=0   # counts the number of parameters passed using -param
PARAMLIST_RAW=( "" )

while (( i <= last )); do
  opt=${!i}
  if [[ "$opt" == '-h' || "$opt" == '--help' ]]; then
      display_long_help
      exit 0
  elif [[ "$opt" == '-A' ]]; then
    (( i++ ))
    ARCH=${!i}
  elif [[ "$opt" == '-o' ]]; then
    (( i++ ))
    let OUTPUT=${!i}
  elif [[ "$opt" == '-m' ]]; then
    MEMCHECK=1
    VALGRIND="-v --tool=memcheck --leak-check=full --show-reachable=yes \
      --trace-children=yes --show-reachable=yes --log-file=valgrind_sifdecoder"
  elif [[ "$opt" == '-p' ]]; then
    (( i++ ))
    let PACKAGE=${!i}
  elif [[ "$opt" == '-s' ]]; then
    (( i++ ))
    let size=${!i}
  elif [[ "$opt" == '-sp' ]]; then
    let realpr=32
  elif [[ "$opt" == '-qp' ]]; then
    let realpr=128
  elif [[ "$opt" == '-st' ]]; then
    (( i++ ))
    let st=${!i}
  elif [[ "$opt" == '-c' ]]; then
    DERIVCHECK=1
  elif [[ "$opt" == '-f' ]]; then
    let automatic=1
  elif [[ "$opt" == '-b' ]]; then
    let automatic=2
  elif [[ "$opt" == '-a' ]]; then
    (( i++ ))
    if [[  ${!i} == '1' || ${!i} == '2'  ]]; then
        let ad0=${!i}
    else
        error "error processing -a flag"
        exit 6
    fi
  elif [[ "$opt" == '-param' ]]; then
    # parameters can either be separated by -param flags
    # or by commas within a -param flag, e.g.:
    # -options... -param par1=val1,par2=val2 -otheroptions... -param par3=val3
    (( i++ ))
    PARAMLIST_RAW=( "$PARAMLIST_RAW ${!i}" )
  elif [[ "$opt" == '-show' ]]; then
    let SHOWPARAMS=1
    (( i++ ))
  elif [[ "$opt" == '-force' ]]; then
    let FORCE=1
   (( i++ ))
  fi
  (( i++ ))
done

#  -------------------------------------
#  Set architecture-dependent parameters
#  -------------------------------------

if [[ -z "$ARCH" ]]; then
#  echo "ARCH not set"
  if [[ -z "$MYARCH" ]]; then
    error ' no architecture specified and environment variable MYARCH is unset.
 Either specify architecture with -A option or set variable MYARCH.
 and re-run.'
    exit 2
  else
    ARCH=${MYARCH}
  fi
fi

eval "`cat $SIFDECODE/bin/sys/$ARCH`"

# process parameter settings
PARAMLIST=( "" )
for val in ${PARAMLIST_RAW[*]};
do
  # check if the array element looks like a parameter setting
  if [[ $val =~ .*=.* ]]; then
    true  # relax
  else
      error "error processing -param flag"
      exit 5
  fi
  PARAMLIST+=(`echo ${val} | tr ',' ' '`)  # change commas to spaces
done
(( nbparam = ${#PARAMLIST[*]} - 1 ))

#  allow file clobbering

set +C

#  -------------------
#  Create problem name
#  -------------------

let last=$#
PROBLEM=${!last}
PROBNAME=`basename $PROBLEM .SIF`
PROBDIR=`dirname $PROBLEM`

#if [[  "$PROBDIR" == ""  ]]; then
#    probDirNotGiven="true"
#    PROBDIR='.'
#fi

problemLoc=${PROBDIR}/${PROBNAME}.SIF
[[ (! -e $problemLoc) && ("$PROBDIR" == ".") ]] && probDirNotGiven='true' || probDirNotGiven='false'

# Specify correct path for problemLoc

let lookInMastSif=0
if [[ (! -e "$problemLoc") && ("$PROBDIR" == ".") && (${MASTSIF+set} == 'set') ]]; then
    let lookInMastSif=1
    problemLoc=${MASTSIF}/${PROBNAME}.SIF
fi

# See whether the specified SIF file exists

if [[ ! -e "$problemLoc" ]]; then
  [[ $PROBDIR == "$PROBLEM" ]] && PROBDIR=$PWD
  error "file $PROBNAME.SIF is not known in directories
 $PROBDIR or \$MASTSIF"
  exit 2
fi

# See if the -show flag is present

if (( SHOWPARAMS != 0 )); then
    SHOWDOTAWK=${SIFDECODE}/bin/show.awk
    if (( OUTPUT == 1 )); then
    echo "possible parameter settings for $PROBNAME are:"
    fi
    $GREP '$-PARAMETER' ${problemLoc} | $AWK -f $SHOWDOTAWK
    exit 8
# Note: the 'exit 8' command is used if -show is given on the command-line
# of a sd* interface. If the exit code were 0, the interface would go on,
# invoking the solver it interfaces.
fi

# Check if -param arguments have been passed and process them

if ((  nbparam > 0  )); then
    # part parameter names from their value
    let p=0
    PARLIST=( ${PARAMLIST[@]} ) #`echo $PARAMLIST:q`
    PARNAMELIST=( ${PARAMLIST[@]} ) #`echo $PARAMLIST:q`
    PARVALUELIST=( ${PARAMLIST[@]} ) #`echo $PARAMLIST:q`
    while ((  p < nbparam  )); do
        PARNAMELIST[$p]=`echo ${PARLIST[$p]} | $AWK -F= '{print $1}'`
        PARVALUELIST[$p]=`echo ${PARLIST[$p]} | $AWK -F= '{print $2}'`
        (( p++ ))
    done

    PARAMDOTAWK=${SIFDECODE}/bin/param.awk
    # substitute the chosen parameter values in the SIF file
    let p=0

    # the easiest looks like creating a sed script which operates
    # all the necessary changes at once. We fill this sed script
    # as each parameter setting is examined in turn.

    # I give it a name depending on the current pid
    # hopefully, this is a unique name.

    sedScript=$TMP/$$_${PROBNAME}.sed
    sifFile=$TMP/$$_${PROBNAME}.SIF
    echo '' > $sedScript

    while ((  p < nbparam  )); do
        # see if parameter number p can be set to value number p
        # if so, retrieve the number of the matching line in the file
        # and the numbers of the lines which should be commented out.
        matchingLines="0"
        nonMatchingLines="0"

        matchingLines=`$GREP -n '$-PARAMETER' ${problemLoc} | $AWK -F'$' '{print $1}' | $AWK -v pname=${PARNAMELIST[$p]} -v pval=${PARVALUELIST[$p]} -v doesmatch=1 -f ${PARAMDOTAWK}`

    nonMatchingLines=`$GREP -n '$-PARAMETER' ${problemLoc} | $AWK -F'$' '{print $1}' | $AWK -v pname=${PARNAMELIST[$p]} -v pval=${PARVALUELIST[$p]} -v doesmatch=0 -f ${PARAMDOTAWK}`

    if [[  "$nonMatchingLines" != ""  ]]; then
        if (( OUTPUT == 1 )); then
            echo "lines number $nonMatchingLines will be commented out"
        fi
        for l  in  $nonMatchingLines; do
            echo "$l s/^ /\*/" >> $sedScript
        done
    fi

    let failed=0

    if [[  "$matchingLines" == ""  ]]; then
        if (( FORCE == 0 )); then
            warning "Failed setting ${PARNAMELIST[$p]} to ${PARVALUELIST[$p]} -- skipping"
            let failed=1
        else
            # get the number of the first line defining the parameter
            # and the parameter type (IE, RE, ...)
            fline=`$GREP -n '$-PARAMETER' ${problemLoc} | $GREP ${PARNAMELIST[$p]} | $HEAD -1 | $AWK -F: '{print $1}'`
            type=`$GREP '$-PARAMETER' ${problemLoc} | $GREP ${PARNAMELIST[$p]} | $HEAD -1 | $AWK '{print $1}' | $SED -e 's/^[ ]*\*//'`
            (( OUTPUT == 1 )) && echo "Forcing parameter ${PARNAMELIST[$p]} on line $fline of type $type to value ${PARVALUELIST[$p]}"
            echo "$fline s/^.*"'$'"/ ${type} ${PARNAMELIST[$p]}                   ${PARVALUELIST[$p]}/" >> $sedScript
        fi
    else
        (( OUTPUT == 1 )) && echo "${PARNAMELIST[$p]} will be set to ${PARVALUELIST[$p]} on line $matchingLines"
        # change the leading star to a whitespace on the matching lines
        # if there was no leading star, this has no effect.
        for ml  in  $matchingLines; do
            echo "$ml s/^\*/ /" >> $sedScript
        done
    fi

    (( p++ ))
    done

    # the sed script is ready; we now use it to cast the SIF file
    # note that the cast problem and the sed script have similar names
    if (( failed == 0 )); then
        $SED -f $sedScript $problemLoc > $sifFile
    else
        $LN -s $problemLoc $sifFile
    fi
fi

# If necessary, create a symbolic link between the current directory
# and the problem file
# Since the SIF decoder does not want file names longer than 10 chars,
# take the last 10 numbers of the process id, in an attempt to have a
# unique name.

if [[  "$probDirNotGiven" == "true" && $nbparam == 0  ]]; then
    TEMPNAME=$PROBNAME
    if ((  lookInMastSif == 0  )); then
        link="false"
    else
        link="true"
        $LN -s ${problemLoc} ./$TEMPNAME.SIF
    fi
elif ((  nbparam > 0  )); then
    link="true"
    TEMPNAME=`echo $$ | $AWK 'BEGIN{nb=10}{l=length($1); if(l<nb){start=1} else{start=l-nb+1} print substr($1,start,nb)}'`
    $RM $TEMPNAME.SIF
    $LN -s $sifFile $TEMPNAME.SIF
else
    link="true"
    TEMPNAME=`echo $$ | $AWK 'BEGIN{nb=10}{l=length($1); if(l<nb){start=1} else{start=l-nb+1} print substr($1,start,nb)}'`
    $RM $TEMPNAME.SIF
    $LN -s $PROBDIR/${PROBNAME}.SIF ./$TEMPNAME.SIF
fi

# Define the path to the decoder

DECODER=${SIFDECODE}/objects/${ARCH}/run_sifdecode

if [[ ! -x $DECODER  ]]; then
    error "No SIF decoder sifdec found in
 $SIFDECODE/objects/$ARCH/run_sifdecode
 Terminating execution."
    exit 4
fi

if (( OUTPUT == 1 )); then
  echo -e 'convert the sif file into data and routines suitable for optimizer...\n'
  echo -e 'problem details will be given\n'
fi

[[ -e EXTERN.f ]] && $RM EXTERN.f

# construct input file for the decoder

#sdinput=sifdecoder.$$.input
sdinput='SIFDECODE.CNF'

echo $TEMPNAME   > $sdinput
echo $PACKAGE   >> $sdinput
echo $OUTPUT    >> $sdinput
echo $PROBNAME  >> $sdinput
echo $automatic >> $sdinput
echo $ad0       >> $sdinput
echo $realpr    >> $sdinput
echo $size      >> $sdinput
echo $st        >> $sdinput

# Finally, decode the problem

if [[ $MEMCHECK == "1" ]]; then
  which valgrind > /dev/null 2>&1
  if [[ $? == "0" ]]; then
    valgrind $VALGRIND $DECODER < $sdinput
  else
    warning 'no memory checking available, sorry ...\n'
    #$DECODER < $sdinput
    $DECODER
  fi
else
  #$DECODER < $sdinput
  $DECODER
fi

# Clean up

$RM $sdinput
if ((  nbparam > 0  )); then
    $RM $sedScript
    $RM $sifFile
fi

if [[  $link == "true"  ]]; then
    $RM $TEMPNAME.SIF
fi

if [[ ! -e OUTSDIF.d ]]; then
  error 'Error exit from decoding stage. terminating execution.'
  exit 3
fi

#  Rename files

if (( automatic != 0  )); then
  [[ -e ELFUND.f  ]] && $MV ELFUND.f ELFUN.f
  [[ -e GROUPD.f  ]] && $MV GROUPD.f GROUP.f
  [[ -e EXTERA.f  ]] && $MV EXTERA.f EXTER.f
  [[ -e ELFUND_s.f  ]] && $MV ELFUND_s.f ELFUN_s.f
  [[ -e GROUPD_s.f  ]] && $MV GROUPD_s.f GROUP_s.f
  [[ -e EXTERA_s.f  ]] && $MV EXTERA_s.f EXTER_s.f
  [[ -e ELFUND_q.f  ]] && $MV ELFUND_q.f ELFUN_q.f
  [[ -e GROUPD_q.f  ]] && $MV GROUPD_q.f GROUP_q.f
  [[ -e EXTERA_q.f  ]] && $MV EXTERA_q.f EXTER_q.f
fi

if [[ -e EXTER.f  ]]; then
 [[ ! -s EXTER.f  ]] && $RM EXTER.f
fi
if [[ -e EXTER_s.f  ]]; then
 [[ ! -s EXTER_s.f  ]] && $RM EXTER_s.f
fi

#  Record the type of derivatives used in the decoding

echo $automatic $ad0 > AUTOMAT.d

#  If required, check the provided derivatives are correct

if [[ $DERIVCHECK == "1" ]]; then

# ensure that the current test problem has been compiled

  [[ -e RANGE.o ]] && $RM RANGE.o
  [[ -e ELFUN.o ]] && $RM ELFUN.o
  [[ -e GROUP.o ]] && $RM GROUP.o
  [[ -e EXTER.o ]] && $RM EXTER.o

  NSUB=( "ELFUN.o GROUP.o RANGE.o" )
  [[ -s EXTER.f ]] && NSUB=( "$NSUB EXTER.o" )

  for i  in  $NSUB; do
    if [[ ! -e $i ]]; then
      j=`basename $i .o`
      cp ${j}.f ${j}.f90
      $FORTRAN $PROBFLAGS ${j}.f90
      if [[ $? != 0 ]]; then
        exit 9
      fi
      $RM ${j}.f90
    fi
  done

#  create the executable

 OBJMAIN=$SIFDECODE/objects/$ARCH
 ${FORTRAN} ${FFLAGS} -o check_derivs $OBJMAIN/check_derivs_main.o \
  $NSUB -L$OBJMAIN -lsifdecode

#  check the derivatives

  echo " "
  echo " Checking supplied derivatives"
  echo " "

  check_derivs
fi

exit 0
