#!/bin/bash

help () {
    cat <<- EOF >&2
	Usage: iocsh [options] [files] [macro=value] ...
	Start an EPICS iocsh and load files
	
	Options:
	 -?, -h, --help  Show this page and exit.
	 -v, --version   Show version and exit.
	 -win, --win     Run Windows softIOC via wine.
	 -32             Force 32 bit version (on 64 bit systems).
	 --nopva         Do not use PVA (in EPICS 7)
	 -x[.z[.y]]      Select EPICS base version x.z.y (e.g. 3.14.8, 3.15, 7).
	 -d, --debug     Run IOC with gdb.
	 -dv             Run IOC with valgrind.
	 -dp             Run IOC with perf record.
	 -i bashscript   Source bashscript before starting ioc.
	 -c 'cmd args'   Ioc shell command.
	 -s 'prog m=v'   Sequencer program (and arguments), run with 'seq'.
	                 This forces an 'iocInit' before running the program.
	 -r module[,ver] Module (optionally with version) loaded via 'require'.
	 -n name         Name of the IOC, used for prompt and \${IOC} variable.
	                 Default: dirname if parent dir is "ioc" otherwise hostname.
	 @file           More arguments are read from file.
	
	Supported filetypes:
	 *.db, *.dbt, *.template  loaded via 'dbLoadRecords'
	 *.subs, *.subst          loaded via 'dbLoadTemplate'
	 *.dbd                    loaded via 'dbLoadDatabase'
	 *.so                     loaded via 'dlload' (or 'ld' before 3.14.12)
	All other files are executed as startup scripts by the EPICS shell.
	After a file you can specify substitutions like m1=v1 m2=v1 for that file.
	
	If a readable file iocsh.init exists in ./, cfg/, or /etc/, it is sourced
	when iocsh starts (see also -i option). Only the first one found is sourced.
	This allows to set environment variables etc before EPICS starts.
	In this context, the variables \${IOC}, \${BASE}, and \${EPICS_HOST_ARCH} are
	already set to the ioc name, the path to the used EPICS installation and the
	used architecture (which may have been modified by -32 or -win).
	
	If the environment variable NOPVA is not empty, it works like --nopva
	except if set to 0, n, no, f, false or off (case insensitive).
	
	The environment variable LOADER can be used to run EPICS inside another
	program. Internally this is used for wine, gdb, valgrind, etc.
	
	A file EPICSVERSION or cfg/EPICSVERSION may contain a default EPICS version
	like 3.14.8, 3.15, or 7 but the command line option takes precedence.
	
	Examples:
	  iocsh st.cmd
	  iocsh my_database.template P=XY M=3
	  iocsh -r my_module,version -c 'initModule()'
	  iocsh -3.15.4 -dp st.cmd
	  iocsh -c 'var requireDebug 1' st.cmd
EOF
    exit
}

version () {
    {
    echo "iocsh by Dirk Zimoch"
    } >&2
    exit
}

# realpath and readlink are not available on all systems, let's try what works...
rp() {
  ( realpath "$1" || readlink -f "$1" || readlink "$1" || (cd -P "$1" && echo $PWD) || (x=$(\ls -ld "$1") && echo ${x##* }) || echo $1 ) 2>/dev/null
}

# if EPICS_HOST_ARCH is not set guess it
if [ -z "$EPICS_HOST_ARCH" ]
then
    EPICS_HOST_ARCH=$(basename $(dirname $(rp $(which caRepeater 2>/dev/null)) 2>/dev/null) 2>/dev/null)
    if [ -n "$EPICS_HOST_ARCH" ]
    then
        echo "Guessing EPICS_HOST_ARCH=$EPICS_HOST_ARCH" >&2
    else
        echo "EPICS_HOST_ARCH is not set" >&2
        exit 1
    fi
fi

if read BASE < EPICSVERSION || read BASE < cfg/EPICSVERSION
then
    unset EPICS_BASE
fi 2> /dev/null

# IOC name derives from hostname
# (trailing possible '\r' under cygwin)
IOC=$(hostname|tr -d '\r')
# trailing possible domain name
IOC=${IOC%%.*}
# or get IOC name from start directory following PSI convention
D=$(basename $(dirname $PWD))
if [ "${D#*_}" = "ioc" ]
then
    IOC=$(basename $PWD)
fi
export IOC

while true
do
    case $1 in
        ( -win | --win )
            LOADER="$LOADER wine"
            export WINEDEBUG=fixme-all
            export WINEDLLOVERRIDES="mscoree,mshtml="
            export WINEPREFIX=$HOME/.wine-$EPICS_HOST_ARCH
            [[ $(wine --version) = wine-6.* ]] && stty -isig 2> /dev/null # or CTRL-C hangs up on wine 6
            EPICS_HOST_ARCH=windows-x64
            ;;
        ( -32 )
            EPICS_HOST_ARCH=${EPICS_HOST_ARCH%_64}
            ;;
        ( -[1-9]* )
            unset EPICS_BASE
            BASE=${1#-}
            ;;
        ( -nopva | --nopva )
            NOPVA=1
            ;;
        ( -n | --name )
            shift
            IOC="$1"
            ;;
        ( -d | -dg | --debug )
            LOADER="gdb --eval-command run --args $LOADER"
            ;;
        ( -dv )
            LOADER="valgrind --leak-check=full $LOADER"
            ;;
        ( -dp )
            LOADER="perf record $LOADER"
            ;;
        ( -h | "-?" | -help | --help )
            help
            ;;
        ( -v | -ver | --ver | -version | --version )
            version
            ;;
        ( * ) break
        ;;
    esac
    shift
done

# Either EPICS or EPICS_BASE should be set to the install directory
if [ -z "$EPICS_BASE" ]
then
    if [ -z "$EPICS" ]
    then
        # look for some standard install directories
        for EPICS in /usr/local/epics /opt/epics /epics
        do
            if [ -d "$EPICS" ]
            then
                break
            fi
        done
        if [ ! -d "$EPICS" ]
        then
            EPICS=$(dirname $(dirname $(dirname $(dirname $(ldd $(which caRepeater) | awk '/libca/ {print $3}')))))
            echo "Guessing EPICS=$EPICS"
        fi
        if [ ! -d "$EPICS" ]
        then
            echo "Cannot find EPICS installation directory." >&2
            echo "Try setting EPICS environment variable." >&2
            exit 1
        fi
    fi
    if [ -z "$BASE" ]
    then
        EPICS_BASE=$(\ls -1vrd $EPICS/base/bin/{${EPICS_HOST_ARCH},${EPICS_HOST_ARCH%_64}} 2>/dev/null | head -n1)
    else
        # find highest (requested) EPICS version that supports our architecture (or its 32 bit version)
        EPICS_BASE=$(\ls -1vrd $EPICS/base-$BASE/bin/{${EPICS_HOST_ARCH},${EPICS_HOST_ARCH%_64}} 2>/dev/null | head -n1)
        if [ -z "$EPICS_BASE" ]
        then
            unset FILTER
            if [ -f "$EPICS/ignore" ]
            then
                FILTER=$(echo "|grep -Ev '/base-("$(sed < $EPICS/ignore ':b;$!N;s/\n/|/;tb;s/\./\\./g')")/'")
            fi
            EPICS_BASE=$(eval \ls -1vrd $EPICS/base-$BASE*/bin/{${EPICS_HOST_ARCH},${EPICS_HOST_ARCH%_64}} 2>/dev/null $FILTER | head -n1)
        fi
    fi
    if [ -z "$EPICS_BASE" ]
    then
        if [ -z "$(\ls -1vrd $EPICS/base-$BASE*/ 2>/dev/null)" ]
        then
            echo "No EPICS $BASE installed." >&2
            exit 1
        fi
        echo EPICS $BASE not available for EPICS_HOST_ARCH=$EPICS_HOST_ARCH. >&2
        exit 1
    fi
    # maybe we need to change from 64 bit to 32 bit
    if [ "$EPICS_HOST_ARCH" != "${EPICS_BASE#*/bin/}" ]
    then
        EPICS_HOST_ARCH=${EPICS_BASE#*/bin/}
        echo "No 64 bit version in ${EPICS_BASE%bin*}." >&2
        echo "Switching to 32 bit version $EPICS_HOST_ARCH." >&2
    fi
    EPICS_BASE=$(rp ${EPICS_BASE%bin*})
fi
if [ ! -d "$EPICS_BASE" ]
then
    echo "Cannot find EPICS_BASE directory." >&2
    echo "Try setting EPICS_BASE environment variable to full path" >&2
    exit 1
fi

LIBDIR=lib
LIBPREFIX=lib
LIBPOSTFIX=.so
EXEPOSTFIX=

case $(uname) in
    ( Darwin )
        LIBPOSTFIX=.dylib
    ;;
esac

if [[ "$EPICS_HOST_ARCH" == win* ]]
then
    LIBDIR=bin
    LIBPREFIX=
    LIBPOSTFIX=.dll
    EXEPOSTFIX=.exe
fi

# Get actual EPICS BASE version, either from CONFIG_BASE_VERSION (text) file or from version string in libCom.so
# Version may have 3 or 4 digits. We make a (4*2 digit) BASECODE too for easier comparison.
# How many digits the drivers use is another question.
if [ -f "$EPICS_BASE/configure/CONFIG_BASE_VERSION" ]
then
    eval $(awk -F '[ \t]*=[ \t]*' '
        /^[ \t]*EPICS_VERSION[ \t]*=/ {v=$2}
        /^[ \t]*EPICS_REVISION[ \t]*=/ {r=$2}
        /^[ \t]*EPICS_MODIFICATION[ \t]*=/ {m=$2+0}
        /^[ \t]*EPICS_PATCH_LEVEL[ \t]*=/ {p=$2+0}
        END {print "BASE3="v"."r"."m";BASE4="v"."r"."m"."p";BASECODE="v*1000000+r*10000+m*100+p}
    ' < $EPICS_BASE/configure/CONFIG_BASE_VERSION)
elif [ -f "$EPICS_BASE/$LIBDIR/$EPICS_HOST_ARCH/${LIBPREFIX}Com$LIBPOSTFIX" ]
then
    strings $EPICS_BASE/$LIBDIR/$EPICS_HOST_ARCH/${LIBPREFIX}Com$LIBPOSTFIX | grep "EPICS R[0-9]"
    eval $(strings $EPICS_BASE/$LIBDIR/$EPICS_HOST_ARCH/${LIBPREFIX}Com$LIBPOSTFIX | awk -F'[.R]' '
        /EPICS R[0-9]/ {print "BASE3="$2"."$3"."$4+0";BASE4="$2"."$3"."$4+0"."$5+0";BASECODE="$2*1000000+$3*10000+$4*100+$5 }')
else
    echo "Cannot guess EPICS base version." >&2
    exit 1;
fi

# Check how many digits of BASE we need to find the drivers
for B in $BASE $BASE4 $BASE3 ${EPICS_BASE#*/base-}
do
    if [ -d "${EPICS_MODULES:=/ioc/modules}/${REQUIRE:=require}" ]
    then # new module pool model
        REQUIRE_LIB=$(ls -1rv $EPICS_MODULES/$REQUIRE/${REQUIRE_VERSION:-*.*.*}/R$B/lib/$EPICS_HOST_ARCH/$LIBPREFIX$REQUIRE$LIBPOSTFIX 2>/dev/null | head -n 1)
        REQUIRE_DBD=${REQUIRE_LIB%/lib/*}/dbd/$REQUIRE.dbd
    else # old driver pool model
        REQUIRE=misc${REQUIRE_VERSION:+-}$REQUIRE_VERSION
        REQUIRE_LIB=$INSTBASE/iocBoot/R$B/$EPICS_HOST_ARCH/$LIBPREFIX$REQUIRE$LIBPOSTFIX
        REQUIRE_DBD=$INSTBASE/iocBoot/R$B/dbd/$REQUIRE.dbd
    fi
    if [ -n "$REQUIRE_LIB" ]
    then
        BASE=$B
        break
    fi
done

# Check for 64 bit versions, default to 32 bit
if [ ! -d "$EPICS_BASE/bin/${EPICS_HOST_ARCH}" -a -d "$EPICS_BASE/bin/${EPICS_HOST_ARCH%_64}" ]
then
    echo "No 64 bit EPICS installation found. Defaulting to 32 bit" >&2
    EPICS_HOST_ARCH=${EPICS_HOST_ARCH%_64}
fi
export EPICS_HOST_ARCH

# setup search path for require
ODIR=O.${BASE}_$EPICS_HOST_ARCH
EPICS_DRIVER_PATH=.:bin/$EPICS_HOST_ARCH:bin:snl:../snl:$ODIR:src/$ODIR:snl/$ODIR:../snl/$ODIR:${EPICS_DRIVER_PATH#:}

#Special PSI: find installation base for libs from working directory
D=$(rp $PWD)
I=${D%/iocBoot/*}
if [ "$I" != "$D" ]
then
    INSTBASE=$I
fi

EPICS_DRIVER_PATH=${EPICS_DRIVER_PATH%:}:${EPICS_MODULES}:${INSTBASE:=/work}/iocBoot/R$BASE/$EPICS_HOST_ARCH
export INSTBASE

# convert for win32-x86 arch
if [ "${EPICS_HOST_ARCH#win32-}" != "$EPICS_HOST_ARCH" ]
then
    EPICS_DRIVER_PATH=$(cygpath -wp $EPICS_DRIVER_PATH)
    DBD=$(cygpath -wp $DBD)
fi
if [ "${EPICS_HOST_ARCH#cygwin-}" != "$EPICS_HOST_ARCH" ]
then
    DBD=$(cygpath -wp $DBD)
fi
export EPICS_DRIVER_PATH

# Call init script after IOC name, EPICS_HOST_ARCH and EPICS_BASE are set.
for dir in . cfg /etc
do
    if [ -r "$dir/iocsh.init" ]
    then
        source $dir/iocsh.init
        break
    fi
done

loadFiles () {
while [ "$#" -gt 0 ]
do
    file=$1
    case $file in
        ( -[1-9]* | -32 | -nopva | --nopva | -win | --win | -d | -dg | --debug | -dv | -dp | -n | --name )
            echo "Option $file must be used earlier" >&2
            exit 1
            ;;
        ( -h | "-?" | -help | --help )
            help
            ;;
        ( -v | -ver | --ver | -version | --version )
            version
            ;;
        ( @* )
            loadFiles $(cat ${file#@})
            ;;
        ( -i )
            shift
            source $1
            ;;
        ( -c )
            shift
            case $1 in
            ( seq* )
                if [ "$init" != NO ]
                then
                    echo "iocInit"
                    init=NO
                fi
                ;;
            ( iocInit )
                init=NO
                ;;
            esac
            echo $1
            ;;
        ( -s )
            shift
            if [ "$init" != NO ]
            then
                echo "iocInit"
                init=NO
            fi
            echo "seq $1"
            ;;
        ( -r )
            shift
            echo "require $1"
            ;;
        ( -* )
            echo "Unknown option $1" >&2
            echo "Try: $(basename $0) --help" >&2
            exit 1
           ;;
        ( *$LIBPOSTFIX )
            if [ "$BASECODE" -ge 3141200 ]
            then
                echo "dlload \"$file\""
            else
                echo "ld \"$file\""
            fi
            ;;
        ( *=* )
            echo -n $file | awk -F '=' '{printf "epicsEnvSet %s '\''%s'\''\n", $1, $2}'
            ;;
        ( * )
            subst=""
            while [ "$#" -gt 1 ]
            do
                case $2 in
                    ( *=* )
                        subst="$subst,$2"; shift
                        ;;
                    ( * )
                        break
                        ;;
                esac
            done
            subst=${subst#,}
            case $file in
                ( *.db | *.template)
                    echo "dbLoadRecords '$file','$subst'"
                    ;;
                ( *.subs | *.subst )
                    echo "dbLoadTemplate '$file','$subst'"
                    ;;
                ( *.dbd )
                    # some dbd files must be loaded before main to take effect
                    echo "dbLoadDatabase '$file','$DBD','$subst'"
                    ;;
                ( * )
                    if [ "$BASECODE" -ge 3150000 ]
                    then
                        echo "iocshLoad '$file','$subst'"
                    else
                        echo -n $subst | awk -F '=' -v 'RS=,' '{printf "epicsEnvSet %s '\''%s'\''\n", $1, $2}'
                        echo "< '$file'"
                    fi
                    if grep -q iocInit $file; then init=NO; fi
                    ;;
            esac
            ;;
    esac
    shift
done
}

startup=${IOCSH_TMP:-/tmp}/iocsh.startup.$$

# clean up and kill the softIoc when killed by any signal
cleanup() {
    trap EXIT
    rm -f $startup
    stty sane 2> /dev/null
    echo
}
trap "kill -s SIGTERM 0; cleanup" EXIT

{
echo "# date=\"$(date)\""
echo "# user=\"${USER:-$(whoami)}\""
for var in IOC PWD BASE EPICS_HOST_ARCH SHELLBOX EPICS_CA_ADDR_LIST EPICS_DRIVER_PATH
do
    echo "# $var=\"${!var}\""
done

if [ "$BASECODE" -ge 3141200 ]
then
    case $(tr A-Z a-z <<< "$NOPVA") in (0|n|no|f|false|off) unset NOPVA; esac
    if [ -z "$NOPVA" -a -x "$EPICS_BASE/bin/$EPICS_HOST_ARCH/softIocPVA$EXEPOSTFIX" ]
    then
        EXE=$EPICS_BASE/bin/$EPICS_HOST_ARCH/softIocPVA$EXEPOSTFIX
        ARGS="-D $EPICS_BASE/dbd/softIocPVA.dbd"
        echo "dlload $EPICS_BASE/$LIBDIR/$EPICS_HOST_ARCH/${LIBPREFIX}nt$LIBPOSTFIX"
        echo "dlload $EPICS_BASE/$LIBDIR/$EPICS_HOST_ARCH/${LIBPREFIX}pvDatabase$LIBPOSTFIX"
    else
        EXE=$EPICS_BASE/bin/$EPICS_HOST_ARCH/softIoc$EXEPOSTFIX
        ARGS="-D $EPICS_BASE/dbd/softIoc.dbd"
    fi
    LDCMD="dlload"
else
    # get rid of the compiled-in rpath because at PSI that is a link pointing to current EPICS version.
    LOADER="$LOADER /lib/ld-linux.so.2"
    LOADERARGS="--library-path $EPICS_BASE/$LIBDIR/$EPICS_HOST_ARCH --inhibit-rpath ''"
    APP=ioc
    EXE=$EPICS_EXTENSIONS/bin/$EPICS_HOST_ARCH/$APP
    DBD=$EPICS_EXTENSIONS/dbd
    LDCMD="ld"
    echo "dbLoadDatabase \"$APP.dbd\",\"$DBD\""
    echo "${APP}_registerRecordDeviceDriver(pdbbase)"
fi

# use WINE to run softIo, and convert EPICS_DRIVER_PATH to windows format
if [[ "$EXEPOSTFIX" == ".exe" ]]
then
    EPICS_DRIVER_PATH_WIN=
    while read -d ':' p; do
        EPICS_DRIVER_PATH_WIN="$EPICS_DRIVER_PATH_WIN;${p/#\//z:/}"
    done <<< "$EPICS_DRIVER_PATH:"
    EPICS_DRIVER_PATH=${EPICS_DRIVER_PATH_WIN#;}
fi

if [ ! -x "$EXE" ];
then
    echo "$EXE not found or not executable." >&2
    exit 1
fi

if [ ! -f "$REQUIRE_LIB" ]
then
    echo "Library ${REQUIRE_LIB:-$LIBPREFIX$REQUIRE$LIBPOSTFIX} not found." >&2
    echo "Command 'require' is not available." >&2
else
    echo "$LDCMD $REQUIRE_LIB"
    echo "dbLoadDatabase $REQUIRE_DBD"
    echo "${REQUIRE%-*}_registerRecordDeviceDriver"
    echo "require misc ${MISC_VERSION:-ifexists}"
fi

loadFiles "$@"
if [ "$init" != NO ]
then
    echo "iocInit"
fi
echo 'epicsEnvSet IOCSH_PS1,"${IOC}> "'
} > $startup

# convert startup script file name for win32-x86
if [ "${EPICS_HOST_ARCH#win32-}" != "$EPICS_HOST_ARCH" ]
then
    startup=`cygpath -w $startup`
fi

if [ "${EPICS_HOST_ARCH#win32-}" != "$EPICS_HOST_ARCH" -o "${EPICS_HOST_ARCH#cygwin-}" != "$EPICS_HOST_ARCH" ]
then
    PATH=$INSTBASE/iocBoot/R$BASE/$EPICS_HOST_ARCH:$EPIC_BASE/bin/$EPICS_HOST_ARCH:$EPICS_BASE/../seq/bin/$EPICS_HOST_ARCH:$PATH
fi

echo $EXE $ARGS $startup
#enable core dumps
ulimit -c unlimited
eval "$LOADER $LOADERARGS $EXE" $ARGS "$startup" 2>&1
STATUS=$?
cleanup
exit $STATUS
