#!/bin/bash
#
#  File: helena
#

gcc='/usr/bin/gcc'
shcc='/usr/bin/oshcc'
shrun='/usr/bin/oshrun'

helenaDir=$HOME/.helena

#  by default we compile the generated code with gcc
CC=$gcc

RUN="./helena-checker"

#  executable names
lnaGenerator=helena-generate-lna
pnmlGenerator=helena-generate-pnml
dveGenerator=helena-generate-dve
checkerGenerator=helena-generate-checker
configGenerator=helena-generate-config
propertyGenerator=helena-generate-property
bitStreamGenerator=helena-generate-bit-stream
reporter=helena-report
reportsMerger=helena-merge-reports

xmlReportFile=report.xml
xmlReportTmpFile=report.xml.tmp
xmlGraphReportFile=rg-report.xml
graphFile=graph.dat
version=3.0
versionFile=VERSION

#  options to pass for basic compilation
CCopt="-pthread -lm"

commonFiles="bit_stream"
checkerSrcFiles="bfs bfs_queue bit_stream buchi common comm_shmem
context darray ddfs_comm dbfs_comm delta_ddd dfs dfs_stack errors
event graph heap harray htbl list observer papi_stats por_analysis
prop reduction rwalk state simulator workers"

exitWithError () {
    rm -rf $tmpDir &> /dev/null
    exit 1
}

error () {
    echo "error: "$* > /dev/stderr
    exitWithError
}

msg () {
    [ $verbose -eq 1 ] && echo $*
}

getModelDir () {
    if [ z$modelDir != z ]
    then
	echo $modelDir
    elif [ z$language = z ]
    then
	error "\$language not set"
    elif [ z$model = z ]
    then
	error "\$model not set"
    else
	echo $helenaDir/models/$language/$model
    fi
}

checkExecutable () {
    ex=$1
    which $ex &> /dev/null
    if [ $? -ne 0 ]
    then
	msg="executable '"$ex"' could not be located"
	msg=$msg" in your PATH environment variable."
        error $msg
	exitWithError
    fi
}

createDirectory () {
    dir=$1
    [ -d $dir ] && return
    msg "create directory '"$dir"'"
    mkdir $dir &> /dev/null
    [ ! -d $dir ] && error "could not create directory '"$dir"'"
}

createEnvironment () {
    if [ -d $helenaDir -a -e $helenaDir/$versionFile ]
    then
	v=$(cat $helenaDir/$versionFile)
	if [ "$v" != "$version" ]
	then
	    echo "new version of Helena ($v -> $version)"
	    echo "-> reinitialisation of directory $helenaDir"
	    rm -rf $helenaDir &> /dev/null
	fi
    fi
    createDirectory $helenaDir
    createDirectory $helenaDir/common
    createDirectory $helenaDir/models
    createDirectory $helenaDir/models/lna
    createDirectory $helenaDir/models/dve
    createDirectory $helenaDir/models/pnml
    echo $version > $helenaDir/$versionFile
}

compileCFiles () {
    local dir=$(getModelDir)/src
    pushd $dir &> /dev/null
    cp $helenaDir/common/* $(getModelDir)/src/ &> /dev/null
    links=$initLinks
    for f in $modelSrcFiles $checkerSrcFiles
    do
	[ ! -e $f.c ] && continue
	links=$links" "$f".o"
	if [ ! -e $f.o ]
	then
	    compileCmd="$CC -c $f.c"
	    msg "   > compile file "$f.c" ("$compileCmd")"
	    eval $compileCmd		
	    if [ ! $? -eq 0 ]
	    then
		error "compilation of file "$dir"/"$f".c failed"
	    fi
	fi
    done
    compileCmd="$CC $links -o helena-checker main.c"
    msg "   > compile file main.c ("$compileCmd")"
    eval $compileCmd
    if [ ! $? -eq 0 ]
    then
	error "compilation of file "$dir"/main.c failed"
    fi
    #  copy common files in the common directory
    for f in $commonFiles
    do
	if [ -e $f.c -a -e $f.h -a -e $f.o ]
	then
	    cp $f.c $f.h $f.o $helenaDir/common/
	fi
    done
    popd &> /dev/null
}

createMakefile () {
    local dir=$(getModelDir)/src
    pushd $dir &> /dev/null
    links=$initLinks
    makefile=Makefile
    targets=""
    (echo "CC = $CC"
     echo "CC_VG = $CC_VG"
     echo "CC_GP = $CC_GP"
     echo
     for f in $modelSrcFiles $checkerSrcFiles
     do
	 [ ! -e $f.c ] && continue
         echo -e "$f.o: $f.h $f.c"
         echo -e "\t\$(CC) -c $f.c"
         echo
         echo -e $f"_vg.o: $f.h $f.c"
         echo -e "\t\$(CC_VG) -c -o "$f"_vg.o $f.c"
         echo
         echo -e $f"_gp.o: $f.h $f.c"
         echo -e "\t\$(CC_GP) -c -o "$f"_gp.o $f.c"
         echo
         links=$links" $f.o"
         linksvg=$linksvg" "$f"_vg.o"
         linksgp=$linksgp" "$f"_gp.o"
     done
     echo "all: $links"
     echo -e "\t\$(CC) $links -o helena-checker main.c\n"
     echo "all_vg: $linksvg"
     echo -e "\t\$(CC_VG) $linksvg -o helena-checker-vg main.c\n"
     echo "all_gp: $linksgp"
     echo -e "\t\$(CC_GP) $linksgp -o helena-checker-gp main.c\n"
     echo "run: all"
     echo -e "\t$RUN\n"
     echo "report:"
     echo -e "\tmake all_gp"
     echo -e "\t./helena-checker-gp &> /dev/null"
     echo -e "\techo \"======================\" >> report.txt"
     echo -e "\techo \"==  GNUPROF REPORT  ==\" >> report.txt"
     echo -e "\techo \"======================\" >> report.txt"
     echo -e "\tgprof ./helena-checker >> report.txt"
     echo -e "\techo \"\" >> report.txt"
     echo -e "\techo \"\" >> report.txt"
     echo -e "\techo \"\" >> report.txt"
     echo -e "\techo \"=======================\" >> report.txt"
     echo -e "\techo \"==  VALGRIND REPORT  ==\" >> report.txt"
     echo -e "\techo \"=======================\" >> report.txt"
     echo -e "\tmake all_vg"
     echo -e "\tvalgrind ./helena-checker-vg &>> report.txt"
     echo -e ""
     echo -e "clean:"
     echo -e "\trm helena-checker* *.o *~") > $makefile
    popd &> /dev/null
}

generateConfig () {
    checkExecutable $configGenerator
    $configGenerator $* $tmpDir || exitWithError
}

generateBitStreamLib () {
    checkExecutable $bitStreamGenerator
    $bitStreamGenerator $tmpDir || exitWithError
}

generateLNA () {
    checkExecutable $lnaGenerator
    $lnaGenerator $* $tmpDir || exitWithError
}

generateDVE () {
    checkExecutable $dveGenerator
    $dveGenerator $* $tmpDir || exitWithError
}

generatePNML () {
    checkExecutable $pnmlGenerator
    $pnmlGenerator $* $tmpDir || exitWithError
}

generateChecker () {
    checkExecutable $checkerGenerator
    $checkerGenerator $tmpDir || exitWithError
}

collectReportFiles () {
    for i in $(seq 0 1000000)
    do
        grep "^\[xml-$i]" $xmlReportTmpFile | \
            sed "s/\[xml-[0-9]*\]//g" > report-$i.xml
        [ ! -s report-$i.xml ] && rm report-$i.xml && break
    done
}

launchChecker () {
    local dir=$(getModelDir)/src
    [ "$machineFile" != "" ] && cp "$machineFile" $dir/machinefile
    pushd $dir &> /dev/null
    msg "Launching search ($RUN comp-time $ccTime) ..."
    if [ $distributed -eq 0 ]
    then
        $RUN comp-time $ccTime
    else
        $RUN comp-time $ccTime | tee $xmlReportTmpFile | grep -v "^\[xml"
        collectReportFiles
        checkExecutable $reportsMerger
        $reportsMerger report-*.xml
    fi
    for f in $xmlReportFile $xmlGraphReportFile $graphFile
    do
        if [ -e $f ]
        then
	    mv $f ..
        fi
    done
    popd &> /dev/null
}

updateModelDir () {
    dir=$(getModelDir)
    rm -rf $dir/report.xml &> /dev/null

    rmEverything=0
    #  condition 1 = model file has not changed
    if [ -e $dir/model -a "$(diff $modelFile $dir/model)" != "" ]
    then
        rmEverything=1
    else
        # condition 2 all = model source files are the same
        for f in $modelSrcFiles
        do
            diff $tmpDir/$f.c $dir/src/$f.c &> /dev/null
            [ $? != 0 ] && rmEverything=1
        done
    fi
    #  conditions 1 and 2 are met => we do not delete model source files
    #  otherwise we delete all the content of the source file
    if [ $rmEverything = 1 ]
    then
        rm -rf $dir/src/* &> /dev/null        
    else
        for f in $checkerSrcFiles
        do
            rm -rf $dir/src/$f* &> /dev/null
        done
    fi
}

###############################################################################

#####
#  options
verbose=0
distributed=0
reportFile=""
machineFile=""
algo=""
progressLevel=10

for arg in $*
do
    idx=$(expr index $arg =)
    case $arg in
	-v|--verbose) verbose=1 ;;
	-h|--help) $configGenerator -h dummy dummy ; exit 0 ;;
	-h=*|--help=*) $configGenerator $arg dummy dummy ; exit 0 ;;
	-V|--version) $configGenerator -V dummy dummy ; exit 0 ;;
	-md=*|--model-directory=*)
	    modelDir=${arg:$idx}
            ;;
	-A=*|--algo=*)
	    algo=$(echo ${arg:$idx} | tr "[a-z]" "[A-Z]")
	    if [ "$algo" = DDFS -o "$algo" = DBFS ]
	    then
		CC=$shcc
		distributed=1
	    fi
	    ;;
	-o=*|--report-file=*)
	    reportFile=${arg:$idx}
	    ;;
        -g=*|--progress=*)
            opt=${arg:0:$idx - 1}
            val=$(echo ${arg:$idx} | tr "[a-z]" "[A-Z]")
            case "$val" in
                NO-COMPILE) progressLevel=1 ;;
                NO-CHECK)   progressLevel=2 ;;
                NO-REPORT)  progressLevel=3 ;;
                *) error "invalid value for option $opt: $val" ;;
            esac
            ;;
        -wp|--with-papi)
            CCopt=$CCopt" -lpapi"
            ;;
    esac
done
if [ $distributed -eq 1 ]
then
    for arg in $*
    do
        case $arg in
	    -mf=*|--machine-file=*)
	        idx=$(expr index $arg =)
	        machineFile=${arg:$idx}
                [ ! -e "$machineFile" ] && \
                    error "machine file '$machineFile' could not be found"
                RUN=$shrun" -machinefile machinefile "$RUN
                ;;
        esac
    done
    [ z"$machineFile" = z ] && \
        error "algorithm $algo needs a machine file (option \
                --machine-file=FILE)"
fi

#####
#  check that model file exists and is readable
modelFile=$arg
[ "$modelFile" = "" ] &&  error "model file expected"
[ ! -f $modelFile -o ! -r $modelFile ] && \
    error "file $modelFile does not exist or is not readable"

createEnvironment

#####
#  generate model source files
tmpDir=$(mktemp -d)
msg "Generating model source files ..."
generateConfig $*
generateBitStreamLib
generateChecker
if [ -f model-options ]
then
    modelOptions=$(cat model-options)
    rm -rf model-options &> /dev/null
else
    modelOptions=""
fi
extension=$(echo $modelFile | awk -F "." '{print $NF}')
case $extension in
    lna)
	language=lna
	generateLNA $modelOptions $modelFile
	;;
    dve)
	language=dve
	CCopt=$CCopt" -fpack-struct"
	generateDVE $modelFile
	;;
    pnml)
	language=pnml
	CCopt=$CCopt" -fpack-struct"
	generatePNML $modelFile
	;;
    *)
	error "cannot determine type of input model"
	;;
esac

#####
#  get the model name, the C files generated for the model and the
#  object files generated for the model
if [ -f $tmpDir/MODEL -a -s $tmpDir/MODEL ]
then
    model=$(cat $tmpDir/MODEL)
else
    file=$(basename $modelFile)
    model=${file%.*}
fi
if [ -f $tmpDir/SRC_FILES -a -s $tmpDir/SRC_FILES ]
then
    modelSrcFiles=$(cat $tmpDir/SRC_FILES)
else
    modelSrcFiles=""
fi
if [ -f $tmpDir/OBJ_FILES -a -s $tmpDir/OBJ_FILES ]
then
    modelObjFiles=$(cat $tmpDir/OBJ_FILES)
else
    modelObjFiles=""
fi

#####
#  create the model directory and put in it files generated for the
#  model
if [ -d $(getModelDir) -a -d $(getModelDir)/src ]
then
    updateModelDir $(getModelDir)
else
    createDirectory $(getModelDir)
    createDirectory $(getModelDir)/src
fi
cp $modelFile $(getModelDir)/model
mv $tmpDir/* $(getModelDir)/src
rm -rf $tmpDir

#####
#  copy files to link (passed to helena with option --link) in the
#  source directory of the model
initLinks=$modelObjFiles
num=0
for arg in $*
do
    case $arg in
	-L=*|--link=*)
            idx=$(expr index $arg =)
	    inFile=${arg:$idx}
	    f=user_file_$num.o
	    num=$((num + 1))
	    cp $inFile $(getModelDir)/src/$f
	    initLinks=$initLinks" "$f
	    ;;
    esac
done

#####
#  copy source files to the model directory
msg "Copying files to model directory ($(getModelDir)/src/) ..."
cp $helenaDir/common/* $(getModelDir)/src/ &> /dev/null
CC_GP=$CC" "$CCopt" -pg"
CC_VG=$CC" "$CCopt" -O0 -g"
CC=$CC" "$CCopt" -O3"
createMakefile

#####
#  compilation
[ $progressLevel -lt 2 ] && exit 0
msg "Compiling source files ..."
startSec=$(date +"%s")
startNan=$(date +"%N")
compileCFiles
endSec=$(date +"%s")
endNan=$(date +"%N")
ccTime=$(echo "scale=2;$endSec-$startSec+($endNan-$startNan)/1000000000" | bc)

#####
#  execution
[ $progressLevel -lt 3 ] && exit 0
launchChecker

#####
#  reporting
if [ -e $(getModelDir)/$xmlReportFile ]
then
    if [ ! -z "$reportFile" ]
    then
	cp $(getModelDir)/$xmlReportFile $reportFile
    fi
    [ $progressLevel -lt 4 ] && exit 0
    checkExecutable $reporter
    $reporter $(getModelDir)/$xmlReportFile
fi
exit 0
