#----------------------------------*-sh-*--------------------------------------
# =========                 |
# \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
#  \\    /   O peration     |
#   \\  /    A nd           | www.openfoam.com
#    \\/     M anipulation  |
#------------------------------------------------------------------------------
#     Copyright (C) 2015-2016 OpenFOAM Foundation
#     Copyright (C) 2018-2021 OpenCFD Ltd.
#------------------------------------------------------------------------------
# License
#     This file is part of OpenFOAM, distributed under GPL-3.0-or-later.
#
# Script
#     wmakeFunctions
#
# Description
#     Support functions for wmake infrastructure.
#     For example, check environment, find .dep and .o files, various
#     wrappers when making libraries.
#
#------------------------------------------------------------------------------

# Ensure these variables are always defined
MakeDir=Make
: "${Script:=wmakeFunctions}"

# Environment defaults
if [ -z "$WM_DIR" ] && [ -n "$WM_PROJECT_DIR" ]
then
    WM_DIR="$WM_PROJECT_DIR/wmake"
fi
[ -n "$WM_DIR" ] && export WM_DIR


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

# Check environment variables
checkEnv()
{
    local check failed

    # Possibly redundant check for WM_DIR
    for check in WM_PROJECT_DIR WM_DIR WM_OPTIONS
    do
        eval test -n "\$$check" || failed="$failed $check"
    done

    if [ -n "$failed" ]
    then
        echo "$Script error: OpenFOAM environment variable(s) not set" 1>&2
        echo "   $failed" 1>&2
        exit 1
    fi
}


# (api) from WM_DIR/rules/General/general
# - extract WM_VERSION = OPENFOAM=<digits>
getApiOpenFOAM()
{
    local rulesFile="$WM_DIR/rules/General/general"
    local value

    if [ -n "$WM_DIR" ] && [ -f "$rulesFile" ]
    then
        value="$(sed -ne 's@^ *WM_VERSION *= *OPENFOAM=\([0-9][0-9]*\).*@\1@p' "$rulesFile" 2>/dev/null)"
    fi

    if [ -n "$value" ]
    then
        echo "$value"
    else
        echo "Could not extract OpenFOAM API value" 1>&2
        return 1
    fi
}


# Set FOAM_MODULE_PREFIX according to
# - absolute/relative path
# - predefined type (u,user | g,group | o,openfoam)
setModulePrefix()
{
    export FOAM_MODULE_PREFIX="$1"
    case "$FOAM_MODULE_PREFIX" in
    # Prefix: user
    (u | user)      FOAM_MODULE_PREFIX="${FOAM_USER_LIBBIN%/*}" ;;

    # Prefix: group
    (g | group)     FOAM_MODULE_PREFIX="${FOAM_SITE_LIBBIN%/*}" ;;

    # Prefix: openfoam (other)
    (o | openfoam)  FOAM_MODULE_PREFIX="${FOAM_LIBBIN%/*}" ;;

    # Prefix: false | none (ie, 'disabled')
    (false | none)  FOAM_MODULE_PREFIX=false ;;

    # Prefix: directory (absolute or relative)
    (*)
        : "${FOAM_MODULE_PREFIX:=/usr/local}"  # Fallback (autoconf-like)

        # Require absolute path
        [ "${FOAM_MODULE_PREFIX#/}" != "${FOAM_MODULE_PREFIX}" ] || \
            FOAM_MODULE_PREFIX="${PWD}/${FOAM_MODULE_PREFIX}"
        ;;
    esac

    # Avoid potential conflicts
    unset FOAM_MODULE_APPBIN FOAM_MODULE_LIBBIN
    echo "Module prefix = ${FOAM_MODULE_PREFIX:-[]}" 1>&2
}


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

# Return the absolute (physical) path for a directory or
# for a file's parent directory
# expandPath dirName
# expandPath fileName
#
# Output:
# - the expanded path name
expandPath()
{
    if [ -d "$1" ]
    then
        (cd "$1" && pwd -P)
    elif [ -n "$1" ]
    then
        (cd "$(dirname "$1")" && pwd -P)
    else
        pwd -P
    fi
}


# Find the target directory, which contains a Make/ directory
# search upwards in its parent directories, but stopping
# when it hits the project root, home, or the file-system root
#
# findTarget dirName
#
# Output:
# - the relative target directory
#
# Global variables used:
# - WM_PROJECT_DIR, HOME
findTarget()
{
    local project=$(expandPath "$WM_PROJECT_DIR")
    local home=$(expandPath "$HOME")
    local reldir="${1:-.}"
    local absdir=$(expandPath "$reldir")

    while [ -n "$absdir" ]
    do
        case "$absdir" in
        ("$project" | "$home" | /)
            break
            ;;
        esac

        if [ -d "$reldir/Make" ]
        then
            echo "$reldir"
            return 0
        else
            # Check parent directory
            absdir="${absdir%/*}"
            reldir="$reldir/.."
        fi
    done

    echo "Error: no Make directory for $(expandPath "$1")" 1>&2
    echo 1>&2
    return 1
}


# Change to 'MakeDir' parent
# - uses 'MakeDir' for its input
#
# Side-effects:
# - unsets targetType
cdSource()
{
    local dir
    if [ ! -d "$MakeDir" ]
    then
        echo "$Script: '$MakeDir' directory does not exist in $PWD" 1>&2
        echo "    Searching up directories tree for Make directory" 1>&2

        dir=$(findTarget .) || exit 1 # Fatal
        cd "$dir" 2>/dev/null || {
            echo "$Script error: could not change to directory '$dir'" 1>&2
            exit 1
        }
        unset targetType
    fi

    [ -r "$MakeDir"/files ] || {
        echo "$Script error: file '$MakeDir/files' does not exist in $PWD" 1>&2
        exit 1
    }
}


# Find the object directory
# findObjectDir dirName
# findObjectDir fileName
#
# Output:
# - the objectsDir
#
# Global variables used:
# - FOAM_BUILDROOT, WM_PROJECT_DIR, WM_OPTIONS
findObjectDir()
{
    local project="$(expandPath "$WM_PROJECT_DIR")"
    local absdir="$(expandPath "${1:-.}")"
    local appDir relativeDir objectsDir

    # Treat project/ builds as out-of-source
    relativeDir="${absdir#${project}/}"
    if [ "$relativeDir" != "$absdir" ]
    then
        if [ -n "$FOAM_BUILDROOT" ] && [ -w "$FOAM_BUILDROOT" ]
        then
            objectsDir="${FOAM_BUILDROOT}/build/${WM_OPTIONS}/${relativeDir}"
        elif [ -w "$WM_PROJECT_DIR" ]
        then
            objectsDir="${WM_PROJECT_DIR}/build/${WM_OPTIONS}/${relativeDir}"
        fi
    fi

    # Default (local) build directory
    if [ -z "$objectsDir" ]
    then
        if [ -d "$absdir/Make" ]
        then
            objectsDir="${absdir}/Make/${WM_OPTIONS}"
        else
            relativeDir="$absdir"
            appDir=.
            [ -d Make ] || appDir=$(findTarget .) || exit 1 # Fatal
            absdir=$(expandPath "$appDir"/.)

            relativeDir="${relativeDir#${absdir}}"
            objectsDir="${appDir}/Make/${WM_OPTIONS}${relativeDir}"
        fi
    fi

    echo "$objectsDir"
}


# Find the object directory and remove it
# removeObjectDir dirName
# removeObjectDir fileName
#
# Output:
# - NONE
#
# Global variables used:
# - WM_PROJECT_DIR, WM_OPTIONS
removeObjectDir()
{
    local objectsDir="$(findObjectDir "${1:-.}")"
    if [ -d "$objectsDir" ]
    then
        rm -rf "$objectsDir" 2>/dev/null
    fi
}


# Save build/configure parameter information (dependency) into sentinel file
#
# 1 - sentinelFile
# 2... build/configure parameters
#
storeDependency()
{
    local sentinel="$1"
    local depend
    shift

    if [ -n "$sentinel" ]
    then
        mkdir -p "$(dirname "$sentinel")"

        echo '# Build/configure parameters' >| "$sentinel"

        for depend
        do
            echo "-- $depend"
        done >> "$sentinel"
    fi
    return 0
}


# Check sentinel file(s) to handle changed build/configure parameters
# such as paraview / vtk version changes
#
# 1 - sourceDir
# 2... build/configure parameters
#
# The additional test for "CMakeCache.txt" helps for cmake projects and
# has no adverse affect for others
#
sameDependency()
{
    local sourceDir="$1"
    shift
    local objectsDir
    local compare=0

    # Where generated files are stored
    objectsDir=$(findObjectDir "$sourceDir") || exit 1 # Fatal
    local sentinel="$objectsDir/ConfigParameters"

    if [ -f "$sentinel" ]
    then
        # Create an .update version for comparison
        storeDependency   "${sentinel}.update" $@
        cmp "${sentinel}" "${sentinel}.update" >/dev/null 2>&1
        compare=$?

        if [ "$compare" -ne 0 ]
        then
            echo "build/configure parameters changed between builds" 1>&2
            ## cat "${sentinel}.update" 1>&2
        fi

    else
        # No sentinel file: First time, or failed compilation?
        if [ -f "$objectsDir/CMakeCache.txt" ]
        then
            echo "previous build was incomplete" 1>&2
            compare=1
        fi
    fi

    echo "$sentinel"
    return "$compare"
}


# Build a mpi-versioned library (targetType)
# - use sentinel file(s) to handle paraview version changes
# compile into qualified directory
# use sentinel file(s) to handle version changes
# 1 - libName
# 2... build/configure information
#
# Global variables used:
# - WM_OPTIONS, WM_MPLIB, FOAM_MPI
#
# Requires that WM_MPLIB contain an "MPI" string
wmakeLibMpi()
{
    local libName="$1"
    shift

    case "$WM_MPLIB" in (*MPI* | *mpi*)
    (
        WM_OPTIONS="$WM_OPTIONS$WM_MPLIB"

        # Where generated files are stored
        objectsDir="$(findObjectDir "$libName")" || exit 1 # Fatal

        # Something changed
        sentinel=$(sameDependency "$libName" "MPLIB=$WM_MPLIB" "MPI=$FOAM_MPI" $@) || \
            wclean "$libName"

        echo "wmake $targetType $libName (mpi=$WM_MPLIB:$FOAM_MPI)"
        wmake $targetType "$libName" && \
        storeDependency "$sentinel" "MPLIB=$WM_MPLIB" "MPI=$FOAM_MPI" $@
    )
    ;;
    esac
}


# Clean an mpi-versioned library
#
# Global variables used:
# - WM_OPTIONS, WM_MPLIB
#
# Requires that WM_MPLIB contain an "MPI" string
wcleanLibMpi()
{
    case "$WM_MPLIB" in (*MPI* | *mpi*)
    (
        WM_OPTIONS="$WM_OPTIONS$WM_MPLIB"

        for libName
        do
            wclean "$libName"
        done
    )
    ;;
    esac
}


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

# depToSource depFile
#
# Output:
# - the sourceFile corresponding to depFile
#
# Global variables used:
# - WM_OPTIONS
# - WM_MPLIB
if [ -n "$BASH_VERSION" ]
then
    depToSource()
    {
        local sourceFile="${1%.dep}"
        sourceFile="${sourceFile/build\/${WM_OPTIONS}\//}"
        sourceFile="${sourceFile/build\/${WM_OPTIONS}${WM_MPLIB}\//}"
        sourceFile="${sourceFile/Make\/${WM_OPTIONS}\//}"
        sourceFile="${sourceFile/Make\/${WM_OPTIONS}${WM_MPLIB}\//}"

        echo "$sourceFile"
    }
else
    depToSource()
    {
        echo "${1%.dep}" | sed \
            -e "s%build/${WM_OPTIONS}/%%"             \
            -e "s%build/${WM_OPTIONS}${WM_MPLIB}/%%"  \
            -e "s%Make/${WM_OPTIONS}/%%"              \
            -e "s%Make/${WM_OPTIONS}${WM_MPLIB}/%%"
    }
fi


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