#!/bin/bash
#------------------------------------------------------------------------------
# =========                 |
# \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
#  \\    /   O peration     | Website:  https://openfoam.org
#   \\  /    A nd           | Copyright (C) 2020-2024 OpenFOAM Foundation
#    \\/     M anipulation  |
#------------------------------------------------------------------------------
# License
#     This file is part of OpenFOAM.
#
#     OpenFOAM is free software: you can redistribute it and/or modify it
#     under the terms of the GNU General Public License as published by
#     the Free Software Foundation, either version 3 of the License, or
#     (at your option) any later version.
#
#     OpenFOAM 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
#     for more details.
#
#     You should have received a copy of the GNU General Public License
#     along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
#
# Script
#     foamGenerateBashCompletion
#
# Description
#     Generate the bash_completion file
#
#------------------------------------------------------------------------------

usage() {
    cat<<USAGE

Usage: ${0##*/}

USAGE
}

error() {
    while [ "$#" -ge 1 ]; do echo "$1"; shift; done
    usage
    exit 1
}

file=$WM_PROJECT_DIR/etc/config.sh/bash_completion

case "$1" in
(-h | -help)
    usage && exit 0
    ;;
(-l | -local)
    file=${file##*/}
    shift 1
    ;;
-*)
    error "$1 is not a valid option/filename"
    ;;
esac

#------------------------------------------------------------------------------

banner () {
    cat<<EOF
#----------------------------------*-sh-*--------------------------------------
# =========                 |
# \\\\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
#  \\\\    /   O peration     | Website:  https://openfoam.org
#   \\\\  /    A nd           | Copyright (C) 2017-$(date +%Y) OpenFOAM Foundation
#    \\\\/     M anipulation  |
#------------------------------------------------------------------------------
# License
#     This file is part of OpenFOAM.
#
#     OpenFOAM is free software: you can redistribute it and/or modify it
#     under the terms of the GNU General Public License as published by
#     the Free Software Foundation, either version 3 of the License, or
#     (at your option) any later version.
#
#     OpenFOAM 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
#     for more details.
#
#     You should have received a copy of the GNU General Public License
#     along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
#
# File
#     etc/config.sh/bash_completion
#
# Description
#     Bash [TAB] completion file from OpenFOAM
#     Sourced from /etc/bashrc
#
#------------------------------------------------------------------------------
# shellcheck disable=SC2155,SC2207,SC2086

EOF
}

optionFunctions () {
    cat<<EOF
_region ()
{
    find . -maxdepth 3 -name polyMesh -type d 2> /dev/null | \\
        awk -F '/' '{print \$(NF-1)}' | \\
        grep -v constant | \\
        sort -u | xargs
}

_mesh ()
{
    for d in constant system
    do
        find "\$d/meshes" -mindepth 1 -maxdepth 1 -type d 2> /dev/null | \\
        awk -F '/' '{print \$(NF)}'
    done | sort -u | xargs
}

_solver ()
{
    foamToC -table solver | sed '1,/Contents/d' | awk '{print \$1}' | xargs
}

_table ()
{
    [ -z "\${1// }" ] && \\
        foamToC -tables | \\
        sed '1,/Run-time/d' | \\
        sed 's/[[:space:]]\{4\}//' | grep -v ' ' && exit 0
    foamToC -tables | sed '1,/Run-time/d' | tr -d " "
}

_func ()
{
    foamPostProcess -list | sed -n '/^(/,/^)/p' | grep -v '[()]'
}

_optSet () {
    _used="\$1"
    _opts="\$2"

    for _o in \${_opts//-/\\\\-}
    do
        echo "\$_used" | grep -wq "\$_o" && return 0
    done

    return 1
}

_removeOpts () {
    _opts="\$1"
    _removes="\$2"

    for _r in \${_removes//-/\\\\-}
    do
        _opts="\${_opts/\$_r/}"
    done

    echo "\$_opts"
}

_infoArgs ()
{
    foamToC -all | \\
       grep -wx '[^ _]* [^ ]*.so' | \\
       awk '{print \$1}' | \\
       tr '<>,' '\n' | \\
       sort -u | \\
       xargs
}

_findArgs () {
    _dir="\$1"

    [ -d "\$_dir" ] || return 1

    _name="*"
    echo "\$_dir" | grep -wqE "^(\$FOAM_SRC|\$FOAM_APP)" && _name="*.[CHL]"

    find "\$_dir" -name "\$_name" -type f | \\
        awk -F/ '{print \$NF}' | \\
        sort -u | \\
        xargs
}

_unitArgs ()
{
    _opts="-help"

    # If '-dimension' is not selected, it is always an option
    echo "\$@" | grep -q -- "-d" || _opts="\$_opts -dimension"

    # If '-all' or '-list' is selected, the only other possible option is '-dimension'
    echo "\$@" | grep -q -- "-[al]" && echo "\$_opts" | xargs -n 1 | sort && return 0

    # If '-all' or '-list' are not selected, they are options
    _opts="\$_opts -all -list"

    echo "\$@" | grep -q -- "-d" && _args="-d"
    _opts="\$_opts \$(foamUnits -list \$_args | xargs -n 1 | grep '\\[' | tr -d '\\[\\]')"
    echo "\$_opts" | xargs -n 1 | sort
}

EOF
}

declareLocals () {
    cat <<EOF
    local cur="\${COMP_WORDS[COMP_CWORD]}"
    local prev="\${COMP_WORDS[COMP_CWORD-1]}"
    local line=\${COMP_LINE}
    local used=\$(echo "\$line" | grep -oE "\-[a-zA-Z]+ ")
EOF
}

caseStart () {
    cat <<EOF
    [ "\$COMP_CWORD" = 1 ] || \\
    case "\$prev" in
EOF
}

caseEnd () {
    cat <<EOF
    esac
    COMPREPLY=( \$(compgen -W "\${opts}" \$extra -- \${cur}) )
EOF
}

isScript () {
    file "$(command -v "$1")" | grep -q "POSIX shell script"
}

_foamFind ()
{
    cat <<EOF
_foamFind_ ()
{
$(declareLocals)

    opts="\\
        -applications \\
        -dir \\
        -edit \\
        -files \\
        -help \\
        -isearch \\
        -numbers \\
        -modules \\
        -options \\
        -print \\
        -search \\
        -tutorials"

    for o in \$used ; do opts="\${opts/\$o/}" ; done

    # use only one of '-edit', '-isearch', '-search', '-print'
    local actions="-edit -isearch -search -print"
    _optSet "\$used" "\$actions" && opts="\$(_removeOpts "\$opts" "\$actions")"

    # use only one of '-applications', '-dir', '-modules', '-tutorials'
    local paths="-applications -dir -modules -tutorials"
    _optSet "\$used" "\$paths" && opts="\$(_removeOpts "\$opts" "\$paths")"

    local dir=\$FOAM_SRC
    echo "\$used" | grep -wq "\-applications" && dir=\$FOAM_APP
    echo "\$line" | grep -wq "\-dir" && \\
        dir="\$(echo "\$line" | grep -ow "\-dir[\t ]*[^ ]*" | cut -d" " -f2)"
    echo "\$used" | grep -wq "\-modules" && dir=\$FOAM_MODULES
    echo "\$used" | grep -wq "\-tutorials" && dir=\$FOAM_TUTORIALS

    echo "\$line" | grep -wq "[^ ]*.[CHL]" || opts="\$opts \$(_findArgs "\$dir")"
    extra=""

$(caseStart)
        -dir)
            opts="" ; extra="-d" ;;
       *) ;;
$(caseEnd)
}
complete -o filenames -o nospace -F _foamFind_ foamFind

EOF
}

# shellcheck disable=SC2154
_foamGet () {
    cat <<EOF
_foamGet_ ()
{
$(declareLocals)

    _searchDirs () {
        _dirs="\\
            \${HOME}/.OpenFOAM/\$WM_PROJECT_VERSION \\
            \${HOME}/.OpenFOAM"
        [ -n "\$WM_PROJECT_SITE" ] && _dirs=" \\
                \$WM_PROJECT_SITE/\$WM_PROJECT_VERSION/etc \\
                \$WM_PROJECT_SITE/etc"
        _dirs+=" \\
            \$WM_PROJECT_INST_DIR/site/\$WM_PROJECT_VERSION/etc \\
            \$WM_PROJECT_INST_DIR/site/etc \\
            \$FOAM_ETC/caseDicts"

        _files=""
        for _d in \$_dirs
        do
            [ -d "\$_d" ] && \\
                _files="\$_files \$(find "\$_d" -type f ! -name "*.*" -exec basename {} \;)"
        done
        echo "\${_files}" | xargs -n 1 | sort -u
    }

    opts="-case -ext -help -no-ext -target -region"
    for o in \$used ; do opts="\${opts/\$o/}" ; done
    extra=""
    opts="\${opts} \$(_searchDirs)"

$(caseStart)
        -case|-target)
            opts="" ; extra="-d" ;;
        -ext)
            opts="" ; extra="" ;;
        -region)
            opts="\$(_region)"; extra="" ;;
       -*) ;;
        *)
            case "\${COMP_WORDS[COMP_CWORD-2]}" in
                -case|-ext|-target|-region) ;;
                *) opts=""; extra="" ;;
            esac
            ;;
$(caseEnd)
}
complete -o filenames -o nospace -F _foamGet_ foamGet

EOF
}

# shellcheck disable=SC2154
_foamCloneCase () {
    cat <<EOF
_foamCloneCase_ ()
{
$(declareLocals)

    opts="-add -help -latestTime -no-orig -no-scripts \
          -processor -startFrom -template"
    for o in \$used ; do opts="\${opts/\$o/}" ; done
    extra="-d"

$(caseStart)
$(caseEnd)
}
complete -o filenames -o nospace -F _foamCloneCase_ foamCloneCase

EOF
}

# shellcheck disable=SC2154
_surfaceTransformPoints () {
    cat<<EOF
_surfaceTransformPoints_ ()
{
$(declareLocals)

    opts="-doc -help -srcDoc"
    for o in \$used ; do opts="\${opts/\$o/}" ; done
    extra="-d -f"

$(caseStart)
       -*) ;;
        *)
            case "\${COMP_WORDS[COMP_CWORD-2]}" in
                *) opts="";     extra="-d -f" ;;
            esac
            ;;
$(caseEnd)
}
complete -o filenames -o nospace -F _surfaceTransformPoints_ surfaceTransformPoints

EOF
}

# shellcheck disable=SC2154
_foamInfo () {
    cat<<EOF
_foamInfo_ ()
{
$(declareLocals)

    opts="-all -browser -help -keyword -web"
    for o in \$used ; do opts="\${opts/\$o/}" ; done
    opts="\$opts \$(_infoArgs) \$(ls \$FOAM_APPBIN)"
    extra=""

$(caseStart)
        -browser)
            opts="" ; extra="" ;;
       *) ;;
$(caseEnd)
}
complete -o filenames -o nospace -F _foamInfo_ foamInfo

EOF
}

# shellcheck disable=SC2154
_foamUnits () {
    cat<<EOF
_foamUnits_ ()
{
$(declareLocals)

    opts="\$(_unitArgs "\$used")"
    extra=""

$(caseStart)
        -*) ;;
        *) opts="" ;;
$(caseEnd)
}
complete -o filenames -o nospace -F _foamUnits_ foamUnits

EOF
}

#------------------------------------------------------------------------------

apps="$(ls "$FOAM_APPBIN") \
      $(find "$WM_PROJECT_DIR/bin" -maxdepth 1 -type f | sort) \
      $(find "$WM_PROJECT_DIR/wmake" -maxdepth 1 -type f | sort)"

specialApps="\
    foamFind \
    foamGet \
    foamCloneCase \
    foamInfo \
    foamUnits \
    surfaceTransformPoints"

banner > "$file"
optionFunctions >> "$file"

for app in $apps
do
    appName="${app##*/}"

    echo "Configuring $appName"

    # If a special configurations is defined for this app, use it and continue
    echo "$specialApps" | \
        grep -qsw "$appName" && "_$appName" >> "$file" && continue

    # Get arguments. Anything on the usage line in <> or [] brackets.
    argList=$($app -help | \
           grep ^Usage | \
           awk -F '[]>]' '{for(i=1;i<=NF;++i) print $i}' | \
           awk -F ' [<[]' '{print $2}')

    # Categorise arguments ...

    # File arguments. Entries without "output", ending in "file".
    inputFileArgs=$(echo "$argList" | while read -r line
                    do
                        echo "$line" | grep -v output | grep -E "file$"
                    done)
    nInputFileArgs=$(echo "$inputFileArgs" | sed '/^$/d' | wc -l)

    # Directory arguments. Entries without "output", including "case" or "dir*".
    inputDirArgs=$(echo "$argList" | while read -r line
                   do
                       echo "$line" | grep -v output | grep -Ei "(case|dir).*"
                   done)
    nInputDirArgs=$(echo "$inputDirArgs" | sed '/^$/d' | wc -l)

    # Options. Anything in the between "options" and the next empty line.
    optList=$($app -help | \
              sed -n '/^options/,/^$/p' | \
              grep -E "^[\t ]*-" | \
              tr -s " ")
    [ -z "$optList" ] && continue

    # Categorise options ...

    arglessOpts="" # options without arguments
    dirOpts=""     # options with directory arguments
    fileOpts=""    # options with file arguments
    handlerOpts="" # file handler options -fileHandler
    rangesOpts=""  # ranges options -time
    argOpts=""     # options with unspecified arguments
    specialOpts="" # -solver, -table, -func, -region, -mesh

    while read -r line
    do
        # Clear the variable
        unset next

        # Get the option
        opt=$(echo "$line" | cut -d " " -f1 | tr -d " ")

        # Ignore "-solver" if app is a script
        [ "$opt" = "-solver" ] && isScript "$appName" && continue

        # Deal with quoted option arguments for -libs
        [ "$opt" = "-libs" ] && next=libList

        case "$opt" in
            -solver|-table|-func|-region|-mesh)
                specialOpts="$specialOpts $opt"
                continue
                ;;
        esac

        # Get the adjacent string in <> brackets
        [ "$next" ] || \
        next=$(echo "$line" | \
            sed 's/ \<.*\>.*//g' | \
            awk -F '>' '{print $1}' | \
            awk -F ' <' '{print $2}' |
            tr -d \) | \
            tr -d \()

        # Categorise by the the last word in the string
        case "${next##* }" in
            "")      arglessOpts="$arglessOpts $opt" ;;
            dir)     dirOpts="$dirOpts $opt" ;;
            file)    fileOpts="$fileOpts $opt";;
            handler) handlerOpts="$handlerOpts $opt";;
            ranges)  rangesOpts="$rangesOpts $opt";;
            *)       argOpts="$argOpts $opt";;
        esac
    done<<< "$optList"

    # Combine options into a single list
    # shellcheck disable=SC2086
    allOpts=$(printf '%s\n' \
        $arglessOpts $dirOpts $fileOpts $handlerOpts $rangesOpts $argOpts $specialOpts | \
        sort)

    # Write the completion function ...
    # shellcheck disable=SC2086
    {
        echo "_${appName}_ ()"
        echo "{"
        declareLocals
        echo ""

        # shellcheck disable=SC2027,SC2086
        echo "    opts=\""$allOpts"\""
        echo "    for o in \$used ; do opts=\"\${opts/\$o/}\" ; done"

        if [ ! "$nInputFileArgs" = 0 ]
        then
            echo "    extra=\"-d -f\""
        elif [ ! "$nInputDirArgs" = 0 ]
        then
             echo "    extra=\"-d\""
        else
             echo "    extra=\"\""
        fi

        echo ""
        caseStart

        if [ -n "$dirOpts" ]
        then
            printf "        %s)\n" "$(echo $dirOpts | tr " " "|")"
            echo "            opts=\"\" ; extra=\"-d\" ;;"
        fi

        if [ -n "$fileOpts" ]
        then
            printf "        %s)\n" "$(echo $fileOpts | tr " " "|")"
            echo "            opts=\"\" ; extra=\"-d -f\" ;;"
        fi

        if [ -n "$handlerOpts" ]
        then
            printf "        %s)\n" "$(echo $handlerOpts | tr " " "|")"
            echo "            opts=\"uncollated collated masterUncollated\" ; extra=\"\" ;;"
        fi

        if [ -n "$rangesOpts" ]
        then
            printf "        %s)\n" "$(echo $rangesOpts | tr " " "|")"
            echo "            opts=\"\$(foamListTimes -withZero 2> /dev/null)\" ; extra=\"\" ;;"
        fi

        if [ -n "$argOpts" ]
        then
            printf "        %s)\n" "$(echo $argOpts | tr " " "|")"
            echo "            opts=\"\" ; extra=\"\" ;;"
        fi

        for sopt in $specialOpts
        do
            printf "        %s) opts=\"\$(_%s \${cur})\" ;;\n" "$sopt" "${sopt#-}"
        done

        # Set argOpts to all options with arguments
        argOpts="$argOpts $dirOpts $fileOpts $handlerOpts $rangesOpts"

        # Get the max of nInputFileArgs and nInputDirArgs
        nMaxFileDirArgs=$nInputFileArgs
        [ "$nInputDirArgs" -gt "$nMaxFileDirArgs" ] && \
            nMaxFileDirArgs=$nInputDirArgs

        # Stop optional arguments once mandatory arguments are entered
        case "$nMaxFileDirArgs" in
            0) echo "       *) ;;"  ;;
            1)
                echo "       -*) ;;"
                echo "        *)"
                if [ -z "${argOpts// }" ]
                then
                    echo "            opts=\"\"; extra=\"\""
                else
                    echo "            case \"\${COMP_WORDS[COMP_CWORD-2]}\" in"
                    printf "                %s) ;;\n" \
                        "$(echo $argOpts | tr " " "|")"
                    echo "                *) opts=\"\"; extra=\"\" ;;"
                    echo "            esac"
                fi
                echo "            ;;"
                ;;
            *)
                echo "       -*) ;;"
                echo "        *) opts=\"\";;"
                ;;
        esac

        caseEnd
        echo "}"

        echo "complete -o filenames -o nospace -F _${appName}_ ${appName}"
        echo ""

    } >> "$file"
done

cat<<\EOF >> "$file"
#------------------------------------------------------------------------------
EOF


#------------------------------------------------------------------------------
