#!/bin/sh
#---------------------------------*- sh -*-------------------------------------
# =========                 |
# \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
#  \\    /   O peration     | Website:  https://openfoam.org
#   \\  /    A nd           |
#    \\/     M anipulation  |
#------------------------------------------------------------------------------
# Script
#     RunFunctions
#
# Description
#     Helper functions for running cases from Allrun scripts in coupledporousmedia
#     (foam-extend-5.0 compatible).
#------------------------------------------------------------------------------

resolveCaseDictFile()
{
    local dictFile="$1"
    local baseFile="$dictFile"

    case "$dictFile" in
        *.gz) baseFile=${dictFile%.gz} ;;
        *.bz2) baseFile=${dictFile%.bz2} ;;
        *.xz) baseFile=${dictFile%.xz} ;;
        *.zst) baseFile=${dictFile%.zst} ;;
        *.zstd) baseFile=${dictFile%.zstd} ;;
    esac

    for candidate in "$dictFile" "$baseFile" "$baseFile.gz" "$baseFile.bz2" "$baseFile.xz" "$baseFile.zst" "$baseFile.zstd"
    do
        if [ -f "$candidate" ]; then
            echo "$candidate"
            return 0
        fi
    done

    return 1
}

readCaseDict()
{
    local readFile="$1"

    case "$readFile" in
        *.gz)
            gzip -cd "$readFile"
            ;;
        *.bz2)
            bzip2 -cd "$readFile"
            ;;
        *.xz)
            xz -cd "$readFile"
            ;;
        *.zst|*.zstd)
            if command -v zstd >/dev/null 2>&1; then
                zstd -cdq "$readFile"
            elif command -v unzstd >/dev/null 2>&1; then
                unzstd -cq "$readFile"
            else
                return 1
            fi
            ;;
        *)
            cat "$readFile"
            ;;
    esac
}

#
# Extract application name from system/controlDict
#
getApplication()
{
    local dictFile="${1:-system/controlDict}"
    local appName
    local readFile

    readFile=$(resolveCaseDictFile "$dictFile")

    if [ -z "$readFile" ]; then
        echo ""
        return 1
    fi

    case "$readFile" in
        *.gz|*.bz2|*.xz|*.zst|*.zstd)
            ;;
        *)
            if command -v foamDictionary >/dev/null 2>&1; then
        appName=$(foamDictionary -entry application -value "$readFile" 2>/dev/null)
        appName=${appName%;}
        appName=${appName#\"}
        appName=${appName%\"}
        if [ -n "$appName" ]; then
            echo "$appName"
            return 0
        fi
            fi
            ;;
    esac

    readCaseDict "$readFile" 2>/dev/null | awk '
    BEGIN {
        capture = 0
    }
    {
        line = $0
        sub(/\/\/.*/, "", line)

        if (capture == 1)
        {
            n = split(line, tok, /[ \t]+/)
            for (i = 1; i <= n; i++)
            {
                if (tok[i] != "")
                {
                    val = tok[i]
                    gsub(/;/, "", val)
                    gsub(/"/, "", val)
                    if (val != "")
                    {
                        print val
                        exit
                    }
                }
            }
            next
        }

        if (line ~ /^[ \t]*application([ \t;]|$)/)
        {
            sub(/^[ \t]*application[ \t]*/, "", line)
            if (line == "" || line ~ /^[ \t]*;[ \t]*$/)
            {
                capture = 1
                next
            }

            n = split(line, tok, /[ \t]+/)
            for (i = 1; i <= n; i++)
            {
                if (tok[i] != "")
                {
                    val = tok[i]
                    gsub(/;/, "", val)
                    gsub(/"/, "", val)
                    if (val != "")
                    {
                        print val
                        exit
                    }
                }
            }
            capture = 1
        }
    }
    '
}


#
# Extract numberOfSubdomains from decomposeParDict
#
getNumberOfProcessors()
{
    local dictFile="${1:-system/decomposeParDict}"
    local readFile

    readFile=$(resolveCaseDictFile "$dictFile")

    if [ -z "$readFile" ]; then
        echo 1
        return 0
    fi

    local nProcs
    nProcs=$(readCaseDict "$readFile" 2>/dev/null | awk '
        /^[ \t]*numberOfSubdomains[ \t]+/ {
            val=$2
            gsub(/;/, "", val)
            print val
            exit
        }
    ')

    if [ -n "$nProcs" ]; then
        echo "$nProcs"
    else
        echo 1
    fi
}


#
# Run given application with logfile output.
# The preexistence of the log file prevents rerunning.
#
runApplication()
{
    local appRun logFile logMode logCompress logZip logOpt

    while [ $# -gt 0 ] && [ -z "$appRun" ]
    do
        case "$1" in
            -a|-append)
                logMode=append
                ;;
            -o|-overwrite)
                logMode=overwrite
                ;;
            -m|-nolog)
                logOpt=nolog
                ;;
            -c|-compress)
                logCompress=compress
                ;;
            -s|-suffix)
                logFile=".$2"
                shift
                ;;
            '')
                ;;
            *)
                appRun="$1"
                ;;
        esac
        shift
    done

    if [ -z "$appRun" ]; then
        echo "runApplication: missing application name"
        return 1
    fi

    local appName="${appRun##*/}"

    if [ "$appName" = "decomposePar" ]; then
        local dictFile="system/controlDict"
        local bcLib="${COUPLED_BC_LIB:-libfiniteVolumeAddon.so}"

        if [ -f "$dictFile" ]; then
            if ! grep -q "$bcLib" "$dictFile"; then
                if grep -Eq '^[[:space:]]*libs[[:space:]]*\(' "$dictFile" \
                    || grep -Eq '^[[:space:]]*libs[[:space:]]*$' "$dictFile"
                then
                    echo "Warning: $dictFile already has libs entry, but not $bcLib"
                    echo "Please add \"$bcLib\" manually into libs(...) in $dictFile"
                else
                    local tmpDict="${dictFile}.tmp.$$"
                    awk -v libName="$bcLib" '
                        BEGIN { inserted = 0 }
                        /^[[:space:]]*functions[[:space:]]*\(/ && inserted == 0 {
                            print "libs"
                            print "("
                            print "    \"" libName "\""
                            print ");"
                            print ""
                            inserted = 1
                        }
                        { print }
                        END {
                            if (inserted == 0)
                            {
                                print ""
                                print "libs"
                                print "("
                                print "    \"" libName "\""
                                print ");"
                            }
                        }
                    ' "$dictFile" > "$tmpDict" && mv "$tmpDict" "$dictFile"

                    echo "Auto-added $bcLib to libs(...) in $dictFile for decomposePar"
                fi
            fi
        fi
    fi

    if [ "$logCompress" = compress ]; then
        if [ "$logMode" = append ]; then
            echo "Compression and append cannot be selected simultaneously; running append without compression"
        elif [ "$logOpt" = nolog ]; then
            echo "Compression and nolog cannot be selected simultaneously; running nolog without compression"
        else
            logZip=".gz"
        fi
    fi

    logFile="log.$appName$logFile$logZip"

    if [ -f "$logFile" ] && [ -z "$logMode" ]; then
        echo "$appName already run on $PWD: remove log file '$logFile' to re-run"
        return 0
    fi

    echo "Running $appRun on $PWD"

    if [ "$logMode" = append ]; then
        if [ "$logOpt" = nolog ]; then
            "$appRun" "$@" 1>/dev/null 2>>"$logFile"
        else
            "$appRun" "$@" </dev/null >>"$logFile" 2>&1
        fi
    elif [ "$logCompress" = compress ] && [ "$logOpt" != nolog ]; then
        "$appRun" "$@" </dev/null 2>&1 | gzip -9 >"$logFile"
    else
        if [ "$logOpt" = nolog ]; then
            "$appRun" "$@" 1>/dev/null 2>"$logFile"
        else
            "$appRun" "$@" </dev/null >"$logFile" 2>&1
        fi
    fi
}


#
# Run given application in parallel with logfile output.
# The preexistence of the log file prevents rerunning.
#
runParallel()
{
    local appRun logFile logMode nProcs logCompress logZip logOpt
    local appArgs="-parallel"

    if [ -z "${MPIRUN}" ]; then
        MPIRUN=mpirun
    fi

    while [ $# -gt 0 ] && [ -z "$appRun" ]
    do
        case "$1" in
            -a|-append)
                logMode=append
                ;;
            -o|-overwrite)
                logMode=overwrite
                ;;
            -m|-nolog)
                logOpt=nolog
                ;;
            -c|-compress)
                logCompress=compress
                ;;
            -s|-suffix)
                logFile=".$2"
                shift
                ;;
            -n|-np)
                nProcs="$2"
                shift
                ;;
            -decomposeParDict)
                appArgs="$appArgs $1 $2"
                nProcs=$(getNumberOfProcessors "$2")
                shift
                ;;
            '')
                ;;
            *)
                appRun="$1"
                ;;
        esac
        shift
    done

    if [ -z "$appRun" ]; then
        appRun=$(getApplication system/controlDict)
    fi

    if [ -z "$appRun" ]; then
        echo "runParallel: missing application name (could not read 'application' from system/controlDict)"
        return 1
    fi

    [ -n "$nProcs" ] || nProcs=$(getNumberOfProcessors system/decomposeParDict)

    local appName="${appRun##*/}"

    if [ "$logCompress" = compress ]; then
        if [ "$logMode" = append ]; then
            echo "Compression and append cannot be selected simultaneously; running append without compression"
        elif [ "$logOpt" = nolog ]; then
            echo "Compression and nolog cannot be selected simultaneously; running nolog without compression"
        else
            logZip=".gz"
        fi
    fi

    logFile="log.$appName$logFile$logZip"

    if [ -f "$logFile" ] && [ -z "$logMode" ]; then
        echo "$appName already run on $PWD: remove log file '$logFile' to re-run"
        return 0
    fi

    echo "Running $appRun ($nProcs processes) on $PWD"

    if [ "$logMode" = append ]; then
        if [ "$logOpt" = nolog ]; then
            echo "Running with no log option"
            $MPIRUN -np "$nProcs" $MPIARGS "$appRun" $appArgs "$@" 1>/dev/null 2>>"$logFile"
        else
            $MPIRUN -np "$nProcs" $MPIARGS "$appRun" $appArgs "$@" </dev/null >>"$logFile" 2>&1
        fi
    elif [ "$logCompress" = compress ] && [ "$logOpt" != nolog ]; then
        $MPIRUN -np "$nProcs" $MPIARGS "$appRun" $appArgs "$@" </dev/null 2>&1 | gzip -9 >"$logFile"
    else
        if [ "$logOpt" = nolog ]; then
            echo "Running with no log option"
            $MPIRUN -np "$nProcs" $MPIARGS "$appRun" $appArgs "$@" 1>/dev/null 2>"$logFile"
        else
            $MPIRUN -np "$nProcs" $MPIARGS "$appRun" $appArgs "$@" </dev/null >"$logFile" 2>&1
        fi
    fi
}


#
# Ensure OpenFOAM headers in case files contain a given version.
# Scans 0.orig (if present), 0, constant and system, handling plain and .gz files.
#
updateCaseOpenFoamHeadersVersion()
{
    local targetVersion="2.0"

    if [ -n "$2" ]; then
        targetVersion="$2"
    elif [ -n "$1" ] && [ ! -d "$1" ]; then
        targetVersion="$1"
    fi

    set --
    [ -d "0.orig" ] && set -- "$@" "0.orig"
    [ -d "0" ] && set -- "$@" "0"
    [ -d "constant" ] && set -- "$@" "constant"
    [ -d "system" ] && set -- "$@" "system"

    if [ $# -eq 0 ]; then
        echo "updateCaseOpenFoamHeadersVersion: no case directories found (expected: 0.orig, 0, constant, system)"
        return 1
    fi

    update_foamfile_version_entry()
    {
        local inputFile="$1"
        local outputFile="$2"
        local version="$3"

        awk -v version="$version" '
            BEGIN {
                seenFoamFile = 0
                insertedVersion = 0
            }

            {
                if (!seenFoamFile && $0 ~ /^[[:space:]]*FoamFile[[:space:]]*$/) {
                    seenFoamFile = 1
                    print
                    next
                }

                if (seenFoamFile && !insertedVersion && $0 ~ /^[[:space:]]*\{[[:space:]]*$/) {
                    print
                    print "    version " version ";"
                    insertedVersion = 1
                    next
                }

                print
            }
        ' "$inputFile" > "$outputFile"
    }

    find "$@" -type f | while IFS= read -r filePath
    do
        case "$filePath" in
            *.gz)
                local tmpInputFile tmpOutputFile
                tmpInputFile="$(mktemp)"
                tmpOutputFile="$(mktemp)"

                gzip -dc "$filePath" > "$tmpInputFile"

                if grep -q '^[[:space:]]*FoamFile[[:space:]]*$' "$tmpInputFile" \
                    && ! grep -q "^[[:space:]]*version[[:space:]]*${targetVersion}[[:space:]]*;" "$tmpInputFile"; then
                    update_foamfile_version_entry "$tmpInputFile" "$tmpOutputFile" "$targetVersion"
                    gzip -c "$tmpOutputFile" > "$filePath"
                fi

                rm -f "$tmpInputFile" "$tmpOutputFile"
                ;;
            *)
                if grep -q '^[[:space:]]*FoamFile[[:space:]]*$' "$filePath" \
                    && ! grep -q "^[[:space:]]*version[[:space:]]*${targetVersion}[[:space:]]*;" "$filePath"; then
                    local tmpOutputFile
                    tmpOutputFile="$(mktemp)"
                    update_foamfile_version_entry "$filePath" "$tmpOutputFile" "$targetVersion"
                    mv "$tmpOutputFile" "$filePath"
                fi
                ;;
        esac
    done
}

# Backward-compatible alias.
updatePolyMeshHeadersVersion()
{
    updateCaseOpenFoamHeadersVersion "$@"
}

checkReturn ()
{
    if [ $# -lt 3 ]; then
        printInfo "ERROR! usage: checkReturn <error code>  <msg 1>  <msg 2>"
        exit 1
    fi
    if [ $1 -eq 0 ]
    then
        echo ""
        echo " $2: OK! "
        echo ""
    else
        echo ""
        echo " $2: ERROR! "
        echo ""
        echo "$3" >> ./logErrors.out
        exit 1
    fi
}
