#!/bin/sh
#------------------------------------------------------------------------------
# =========                 |
# \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
#  \\    /   O peration     | Website:  https://openfoam.org
#   \\  /    A nd           | Copyright (C) 2018-2021 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
#     foamInfo
#
# Description
#     Prints description and usage of an application, a script, or a model
#     (including boundary conditions, function objects and fvModels).
#
#------------------------------------------------------------------------------
usage() {
    cat<<USAGE

Usage: ${0##*/} [OPTIONS] <name>
options:
  -all     | -a        list all tutorials that use <name> (otherwise maximum 10)
                       and all models of the same family (otherwise maximum 20)
  -browser | -b <name> output C++ source guide web page with specified browser,
                       e.g. foamInfo -browser "firefox"
  -help    | -h        print the usage
  -keyword | -k        uses <name> as a keyword, rather than an exact match
  -web     | -w        output C++ source guide web page with the browser
                       specified in the global controlDict file

Prints the following for an application, a script, or a model
(including boundary conditions, function objects and fvModels).
- File: the location of the relevant source code header file.
- Description details from the header file.
- Usage details from the header file.
- Models: lists other models belonging to the same family, where applicable.
- Examples: a list of relevant cases from the tutorials directory.

By default, finds an exact match with <name> or something beginning with <name>,
which is case-insensitive on the first character.

Otherwise the user can try a broader keyword match with "-keyword | -k" option.

Examples
  foamInfo simpleFoam
  foamInfo buoyantReactingFoam
  foamInfo fixedValue
  foamInfo -k fixedValue
  foamInfo -a turbulentIntensityKineticEnergyInlet
  foamInfo symmetry
  foamInfo -k wallFunction
  foamInfo kEpsilon
  foamInfo -k kEpsilon
  foamInfo RAS
  foamInfo hPolynomial
  foamInfo -k contactAngle
  foamInfo Function1
  foamInfo square
  foamInfo fixedTemperatureConstraint
  foamInfo surfaces
  foamInfo foamNewBC

USAGE
}

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

capitaliseWord () {
    echo "$1" | awk '{$1=toupper(substr($1,0,1))substr($1,2)}1'
}

# (1) case-sensitive match; (2) case-insensitive match; (3) keyword match
findModelFiles() {
    find "$FOAM_SRC" "$FOAM_APP" \
        -name "$1" \
        -iname "$2" \
        ! -iname "$3" \
        ! -name "*Fwd.H" \
        ! -name "*Fields.H" \
        ! -name "*I.H" -type f
}

findFiles() {
    _pre="$1"

    # Application
    _out="$(find "$FOAM_APP" -name "${_pre}.C" -type f)"
    # If same file with .H extension exists, then .C is not an application
    [ "$_out" ] && \
        [ "$(find "$FOAM_APP" -name "${_pre}.H" -type f | wc -c)" -ne 0 ] && unset _out

    # Script
    _out="$(find "$FOAM_APP/../bin" -name "${_pre}" -type f | \
            sed 's@applications/../@@g' ) $_out"

    # Model
    # case-sensitive match
    _models="$(findModelFiles "${_pre}*.H" "*" "") \
             $(findModelFiles "$(capitaliseWord "$_pre")*.H" "*" "")"

    # case-insensitive match
    [ "$_models" ] || \
        _models="$(findModelFiles "*" "${_pre}*.H" "")"

    # keyword match
    [ "$_models" ] && ! [ "$KEYWORD" ] || \
          _models="$_models $(findModelFiles "*" "*${_pre}*.H" "${_pre}*.H")"

    _out="$_models $_out"

    for _t in scalar vector tensor
    do
        echo "$_pre" | grep -oq "$_t" && \
            _mod="$(echo "$_pre" | sed "s/${_t}//g")" && \
            _out="$(find "$FOAM_SRC" -name "${_mod}*.H" -type f) $_out"
    done

    # Function
    _out="$(find "$FOAM_ETC" -name "${_pre}" -type f) $_out"

    # Remove whitespace
    echo "$_out" | xargs -n 1 | sort -u | awk 'NF'
}

nArgs() {
    ! [ "$1" ] && echo 0 && return 0
    echo "$1" | xargs -n 1 | wc -l

    return 0
}

listArgs() {
    _i=0
    _suggest=""
    _pri=100

    for _a in $1
    do
        _i=$(( _i + 1))
        echo "${_i}) $_a" >&2

        # Prioritise suggestion locations
        _tests="\
            fields/fvPatchFields/ \
            fvMesh/fvPatches/ \
            caseDicts/postProcessing/"

        _n=0
        for _t in $_tests
        do
            _n=$(( _n + 1))
            [ "$_n" -lt "$_pri" ] && \
                echo "$_a" | grep -q "$_t" && _suggest="$_i" && _pri="$_n"
        done
    done

    echo "$_suggest"
}

setFile() {
    _files="$1"
    _n="$2"
    echo "$_files" | xargs -n 1 | awk -v n="${_n}" 'NR==n'
}

modelsInFamily () {
    _familyDir="$1"
    _family="${_familyDir##*/}"

    ! [ -d "$_familyDir" ] && return 0

    _modelDirs="$(find "$_familyDir" -maxdepth 1 -mindepth 1 -type d)"

    [ "$_modelDirs" ] || return 0

    _hasTypeName=""
    grep -rqw TypeName "$_familyDir" && _hasTypeName="yes"

    for _mDir in $_modelDirs
    do
        # Exclude base class
        _mName="${_mDir##*/}"
        echo "$_family" | grep -q "$_mName" && continue

        # Model family with TypeName defined
        [ "$_hasTypeName" ] && \
            _file="$(find "$_mDir" -name "*.H" -exec grep -l TypeName {} \;)" && \
            TypeName "$_file"

        [ "$_hasTypeName" ] && continue

        # Model family without TypeName defined
        # Exclusions
        echo "Make lnInclude" | \
            grep -w "$_mName" && continue

        echo "$_mName"
    done | sort
}

# Argument: .H file
TypeName () {
    # First sed captures FvPatch, PolyPatch, PointPatch
    _model="$(grep -Es "TypeName\(\"*[a-Z,0-9,:_()]*\"*\);" "$1" | \
            sed 's@[FP][a-z]*Patch::typeName_()@@g' | \
            sed 's@[\t ]*TypeName("*\([a-Z,0-9,:]*\)"*);@\1@')"

    [ "$_model" ] && echo "$_model" && return 0

    return 1
}

modelName () {
    _file="$1"

    # Get model from TypeName in file
    TypeName "$_file" && return 0

    # Otherwise use the directory name
    _modelDir="${_file%/*}"
    echo "${_modelDir##*/}"

    return 0
}

printInfo() {
    _file="$1"
    _all="$2"

    echo "File"
    printf "    %s\n\n" "$_file"

    # Description
    sed -e "s/^#.//g" -e "s/^#$//g" "$_file" | \
        sed -n '/^Description/,/^[^ ]/p' | sed \$d

    # Usage
    sed -n '/^Usage/,/^[^ ]/p' "$_file" | sed \$d

    # Model
    echo "$_file" | grep -q "$FOAM_SRC" || return 0

    _model="$(modelName "$_file")" || return 0
    _familyDir="$(dirname "$(dirname "$_file")")"
    _family="${_familyDir##*/}"

    _models="$(modelsInFamily "$_familyDir")"
    _nMo="$(nArgs "$_models")"

    [ "$_nMo" -eq 0 ] && return 0

    printf "Model\n"
    echo "$_family" | grep -q "$_model" && \
        printf "    This appears to be the '%s' family of models.\n" \
               "$_family" || \
        printf "    This appears to be the '%s' model of the '%s' family.\n" \
               "$_model" "$_family"

    printf "    The models in the '%s' family are:\n" "$_family"

    _nMoD=20
    [ "$_all" = "yes" ] && _nMoD=1000

    [ "$_nMo" -gt "$_nMoD" ] && [ "$_all" = "no" ] && \
        echo "    *** Listing $_nMoD out of $_nMo;" \
             "run with \"-a\" option to list all ***"
    echo "$_models" | awk -v nMoD="$_nMoD" 'FNR <= nMoD {print "    + " $1}'

    printf "\n"

    return 0
}

webInfo() {
    # basename of file
    _file="$(basename "$1")"

    echo "$_file" | grep -q ".[CH]" ||
        error "No web documentation for file \"$_file\""

    _pre="$(echo "$_file" | sed 's/\./_8/g')"
    _url="https://cpp.openfoam.org/${VER}/${_pre}.html"

    echo "Running \"$BROWSER $_url\""
    $BROWSER "$_url"

    return 0
}

showInfo() {
    _file="$1"
    _all="$2"
    _web="$3"

    # Return 1 for web so examples are not printed
    [ -n "$_web" ] && webInfo "$_file" && return 1
    printInfo "$_file" "$_all"
}

findSolverExamples() {
    _pre="$1"
    _out=""

    # Solvers
    _app="$(find "$FOAM_TUTORIALS" -name "$_pre")"
    _dirs="$(find "$_app" -name "system" -type d)"
    for _d in $_dirs
    do
        _out="${_d%/*} $_out"
    done

    # Remove whitespace
    echo "$_out" | xargs -n 1
}

findUtilityExamples() {
    _pre="$1"
    _out=""

    # Applications
    _scripts="$(find "$FOAM_TUTORIALS" -type f -name "All*")"
    for _f in $_scripts
    do
        grep -rq "$_pre" "$_f" && _out="${_f%/*} $_out"
    done

    # Remove whitespace
    echo "$_out" | xargs -n 1 | sort -u
}

findModelExamples() {
    _pre="$1"
    _out=""

    # Field files
    _dirs="$(find "$FOAM_TUTORIALS" \
              -type d -name "0*" -o -name "constant" -o -name "system")"
    for _d in $_dirs
    do
        # Space and semicolon pick up exact matches
        grep -rq " ${_pre};" "$_d" && _out="${_d%/*} $_out"
    done

    # Remove whitespace
    echo "$_out" | xargs -n 1 | sort -u
}

findAllExamples() {
    _pre="$1"
    _type="$2"

    case "$_type" in
        model) findModelExamples "$_pre" ;;
        utility|script) findUtilityExamples "$_pre" ;;
        solver) findSolverExamples "$_pre" ;;
    esac
}

showTypeExamples() {
    _pre="$1"
    _type="$2"
    _all="$3"
    _examples=""

    [ -z "$_pre" ] && echo "Examples: none found" && return 1

    _nExD=10
    [ "$_all" = "yes" ] && _nExD=1000

    _examples="$(findAllExamples "$_pre" "$_type")"

    [ -z "$_examples" ] && \
        echo "Examples: cannot find any tutorials using \"$_pre\"" && \
        return 1

    _nEx="$(nArgs "$_examples")"

    echo "Examples using \"$_pre\""
    [ "$_nEx" -gt "$_nExD" ] && [ "$_all" = "yes" ] && \
        echo "    *** Listing $_nExD out of $_nEx;" \
             "run with \"-a\" option to list all ***"
    echo "$_examples" | awk -v nExD="$_nExD" 'FNR <= nExD {print "    " $1}'
}

showExamples() {
    _pre="$1"
    _file="$2"
    _all="$3"

    echo "$_file" | grep -q ".H" && _type=model && \
        _pre="$(basename "${file%/*}")" && \
        echo "$_pre" | grep -iq include && _pre=""
    ! [ "$_type" ] && echo "$_file" | grep -q "$FOAM_SRC/../bin" && _type=script
    ! [ "$_type" ] && echo "$_file" | grep -q "$FOAM_UTILITIES" && _type=utility
    ! [ "$_type" ] && echo "$_file" | grep -q "$FOAM_SOLVERS" && _type=solver

    [ -n "$_type" ] && showTypeExamples "$_pre" "$_type" "$_all" && return 0
    return 0
}

web=""
all="no"
# Global controlDict file
controlDict="$(foamEtcFile controlDict 2> /dev/null)"
BROWSER="$(grep docBrowser "$controlDict" 2> /dev/null | cut -d "\"" -f2)"
KEYWORD=""

while [ "$#" -gt 0 ]
do
    case "$1" in
    -a | -all)
        all="yes"
        shift
        ;;
    -b | -browser)
        [ "$#" -ge 2 ] || error "'$1' option requires an argument"
        BROWSER="$2"
        web="yes"
        shift 2
        ;;
    -h | -help)
        usage && exit 0
        ;;
    -k | -keyword)
        KEYWORD="yes"
        shift
        ;;
    -w | -web)
        web="yes"
        shift
        ;;
    -*)
        error "invalid option '$1'"
        ;;
    *)
        break
        ;;
    esac
done

# Check if browsing is possible
VER="${WM_PROJECT_VERSION%%.*}"

[ -n "$web" ] &&  \
    ! command -v "$BROWSER" >/dev/null 2>&1 && \
        error "Cannot run browser application: $BROWSER"
[ -n "$web" ] && [ -z "$VER" ] && \
    error "Cannot establish OpenFOAM version: \$WM_PROJECT_VERSION not set"

[ "$VER" = "dev" ] || VER="v${VER}"

[ $# -gt 1 ] && error "$# arguments \"$*\" specified: only 1 permitted"
[ $# -eq 1 ] || error "Missing argument: no application, model, script, etc provided"
prefix="$1"

files="$(findFiles "$prefix")"
[ -z "$files" ] && error "Nothing found with \"$prefix\" in the name"

nFiles="$(nArgs "$files")"
nFile=""
if [ "$nFiles" -eq 1 ]
then
    nFile=1
else
    echo "Multiple items with \"$prefix\" found:"
    suggest="$(listArgs "$files")"

    printf "\n%s" "Enter file number (1-$nFiles) to obtain description "
    [ -n "$suggest" ] && printf "%s" "(suggest $suggest) "
    printf "%s" ": "

    read -r nFile
    [ -z "$nFile" ] && [ -n "$suggest" ] && nFile="$suggest"
    [ -z "$nFile" ] && \
        echo "Cannot specify nothing; re-run and enter a file number" && exit 1
    ! [ "$nFile" -eq "$nFile" ] 2>/dev/null && \
        echo "\"$nFile\" is not a number between 1 and $nFiles" && exit 1
    { [ "$nFile" -lt 1 ] || [ "$nFile" -gt "$nFiles" ] ; } && \
        echo "\"$nFile\" is not a number between 1 and $nFiles" && exit 1
fi

file="$(setFile "$files" "$nFile")"
showInfo "$file" "$all" "$web" && showExamples "$prefix" "$file" "$all"
