#!/bin/sh
#------------------------------------------------------------------------------
# =========                 |
# \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
#  \\    /   O peration     |
#   \\  /    A nd           | www.openfoam.com
#    \\/     M anipulation  |
#-------------------------------------------------------------------------------
#    Copyright (C) 2015 OpenFOAM Foundation
#    Copyright (C) 2019-2023 OpenCFD Ltd.
#------------------------------------------------------------------------------
# License
#     This file is part of OpenFOAM, distributed under GPL-3.0-or-later.
#
# Script
#     foamMonitor
#
# Description
#     Monitor data with Gnuplot from time-value(s) graphs written by OpenFOAM
#     e.g. by functionObjects
#     - requires gnuplot, gnuplot_x11, sed, awk
#
#------------------------------------------------------------------------------
printHelp() {
    cat<<USAGE

Usage: ${0##*/} [OPTIONS] <file>
Options:
  -g | -grid            Draw grid lines
  -i | -idle <time>     Stop if <file> unchanging for <time> sec (default = 60)
  -l | -logscale        Plot y-axis data on log scale
  -r | -refresh <time>  Refresh display every <time> sec (default = 10)
  -x | -xrange <range>  Set <range> of x-axis data, format "[0:1]"
  -y | -yrange <range>  Set <range> of y-axis data, format "[0:1]"
  -h | -help            Display short help and exit

Monitor data with Gnuplot from time-value(s) graphs written by OpenFOAM
e.g. by functionObjects. For example,

    foamMonitor -l postProcessing/residuals/0/residuals.dat

USAGE
    exit 0  # A clean exit
}


# Report error and exit
die()
{
    exec 1>&2
    echo
    echo "Error encountered:"
    while [ "$#" -ge 1 ]; do echo "    $1"; shift; done
    echo
    echo "See '${0##*/} -help' for usage"
    echo
    exit 1
}


# Count the number of scalars between ( and ) to deduce the field dimensions
count_components() {
    _text="$1"

    # Extract text between the first ( and )
    _matches="$(echo "$_text" | sed -n 's/^[^(]*(\([^)]*\)).*$/\1/p')"

    # Use grep to find numbers (doubles and 0) between ( and ), then count them
    _count="$(echo "$_matches" | grep -Eo '\b[0-9]+\.?[0-9]*[eE]?[-+]?[0-9]+\b|\b0\b' | wc -l)"

    echo "$_count"
}


# Set Gnuplot header
plotFileHeader() {
    TITLE="$1"
    NCOLOURS="$2"
    SIZE="1200,627"
    cat<<EOF
set term x11 1 font "helvetica,17" linewidth 1.5 persist noraise size $SIZE
$LOGSCALE
$XRANGE
$YRANGE
$GRID
set title "$TITLE"
set xlabel "$XLABEL"
set key outside
set linetype cycle "$NCOLOURS"
plot \\
EOF
}


# Set Gnuplot footer
plotFileFooter() {
    cat<<EOF
pause $REFRESH
reread
EOF
}


# Count number of tokens in a variable
howMany() {
    ( set -f; set -- $1; echo $# )
}


#-------------------------------------------------------------------------------
IDLE=60
REFRESH=10
LOGSCALE=""
XRANGE=""
YRANGE=""
GRID=""
GNUPLOT=$(which gnuplot)
[ ! "$GNUPLOT" = "" ] || die "foamMonitor requires Gnuplot installed"
command -v sed >/dev/null || die "foamMonitor requires sed installed"
command -v awk >/dev/null || die "foamMonitor requires awk installed"

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

# Parse options
while [ "$#" -gt 0 ]
do
    case "$1" in
    -h | -help*)
        printHelp
        ;;
    -i | -idle)
        [ "$#" -ge 2 ] || die "'$1' option requires an argument"
        if [ -n "${2##*[!0-9]*}" ]
        then
            IDLE=$2
        else
            die "Argument of '$1' is not an integer: '$2'"
        fi
        shift 2
        ;;
    -l | -logscale)
        LOGSCALE="set logscale y"
        shift 1
        ;;
    -r | -refresh)
        [ "$#" -ge 2 ] || die "'$1' option requires an argument"
        if [ -n "${2##*[!0-9]*}" ]
        then
            REFRESH=$2
        else
            die "Argument of '$1' is not an integer: '$2'"
        fi
        shift 2
        ;;
    -x | -xrange)
        [ "$#" -ge 2 ] || die "'$1' option requires an argument"
        XRANGE="set xrange $2"
        shift 2
        ;;
    -y | -yrange)
        [ "$#" -ge 2 ] || die "'$1' option requires an argument"
        YRANGE="set yrange $2"
        shift 2
        ;;
    -g | -grid)
        GRID="set grid"
        shift 1
        ;;
    -*)
        die "unknown option: '$*'"
        ;;
    *)
        break
        ;;
    esac
done


[ "$#" -eq 1 ] || die "Incorrect arguments specified"
[ -f "$1" ]    || die "File $1 does not exit"
FILE="$1"


# Get KEYS from header
KEYS=$(grep -E '^#' "$FILE" | tail -1)

[ "$KEYS" = "" ] && KEYS="# Step"
NKEYS=$(howMany "$KEYS")
NCOLS=$(grep -m 1 '^[^#]' "$FILE" | awk '{ print NF }')
NCOMPS=$(count_components "$(grep -m 1 '^[^#]' "$FILE")")

# With full column labels, NKEYS = NCOLS + 1, since it includes "#"

# If NKEYS > NCOLS + 1, REMOVE EXCESS KEYS
NCOLSPONE=$((NCOLS+1))
[ "$NKEYS" -gt "$NCOLSPONE" ] && KEYS=$(echo "$KEYS" | cut -d" " -f1-$NCOLSPONE)

# Remove # and Time keys
XLABEL=$(echo "$KEYS" | cut -d " " -f2)
KEYS=$(echo "$KEYS" | cut -d " " -f3-)
NKEYS=$(howMany "$KEYS")


# Create the legend items
TEMPKEYS=""

for i in $KEYS
do
    case "$NCOMPS" in
        0)  # scalar
            TEMPKEYS="$TEMPKEYS $i"
            ;;
        3)  # vector
            for j in x y z
            do
                TEMPKEYS="$TEMPKEYS ${i}_${j}"
            done
            ;;
        6)  # symmetric tensor
            for j in xx xy xz yy yz zz
            do
                TEMPKEYS="$TEMPKEYS ${i}_${j}"
            done
            ;;
        9)  # tensor
            for j in xx xy xz yx yy yz zx zy zz
            do
                TEMPKEYS="$TEMPKEYS ${i}_${j}"
            done
            ;;
        *)  # handle other cases if needed
            echo "Unsupported number of components: $NCOMPS"
            exit 1
            ;;
    esac
done

KEYS=$TEMPKEYS


# Create plots
GPFILE=$(mktemp)
PLOT_TITLE="$(echo "$FILE" | awk -F'/' '{print $(NF-2) "/" $NF}')"
NUM_ELEMENTS="$((NKEYS * NCOMPS))"

plotFileHeader "$PLOT_TITLE" "$NUM_ELEMENTS" > "$GPFILE"

i=1
j=1
for field in $KEYS
do
    i=$((i+1))

    # Reject any keys consisting of 'iter|converged|solver' words
    case "$field" in
        *"iter"*|*"converged"*|*"solver"*)
            continue
            ;;
    esac

    # Reject any parentheses, and configure gnuplot for the underscore character
    PLOTLINE="\"< sed 's/[()]//g' $FILE\" u 1:${i} w l dt ${j} t \"$(echo "$field" | sed 's/_/\\\\_/g')\""
    if [ "$i" -lt "$NCOLS" ]
    then
       PLOTLINE="$PLOTLINE, \\"
    fi
    echo "$PLOTLINE" >> "$GPFILE"

    # Change the dash type after every 5 non-rejected lines
    [ $(((i-1) % 5)) -eq 0 ] && j=$((j+1))
done

# Scrap ', \' characters from the tail of plot command, if necessary
if tail -n 1 "$GPFILE" | grep -q ', \\$'; then
    { head -n -1 "$GPFILE"; tail -n 1 "$GPFILE" | sed 's/, \\$//'; } > tmp.gp
    mv -f -- tmp.gp "$GPFILE"
fi

plotFileFooter >> "$GPFILE"


touch "$FILE"
$GNUPLOT "$GPFILE" &
PID=$!

while true
do
    MODTIME="$(stat --format=%Y "$FILE")"
    IDLEAGO=$(($(date +%s)-IDLE))
    test "$MODTIME" -gt "$IDLEAGO" || break
    sleep "$REFRESH"
done


kill -9 $PID
rm -f "$GPFILE"

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