#!/bin/bash
#------------------------------------------------------------------------------
# =========                 |
# \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
#  \\    /   O peration     | Website:  https://openfoam.org
#   \\  /    A nd           | Copyright (C) 2025 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
#     foamMergeCase
#
# Description
#     Copy a case from a given source directory and merge differences into it.
#     Differences are specified in dictionaries laid out on disk in the same
#     way as in the source case. This allows for variants of a case to be
#     created without replicating common parts of the configuration.
#
#     For example, if the source case is configured to use a RAS turbulence
#     model, but the variant is to run laminar, then a
#     system/momentumTransport.orig file can be created for the variant which
#     contains only the following:
#
#         // Set the momentum transport model to laminar simulationType
#         laminar;
#
#         // Remove the (now) redundant RAS settings for clarity
#         ~RAS;
#
#     Files specifying the differences are, by default, located in the current
#     case directory. These must have a .orig extension to prevent them from
#     being overwritten by the merge operation.
#
#     Optionally, additional variant case directories can be supplied, in which
#     case multiple variations will be applied successively to the source case
#     files.
#
#     The foamUnMergeCase script can be used, with identical arguments, to
#     reverse the merge operation and recover the original configuration with
#     only the variant files present.
#
#------------------------------------------------------------------------------
usage() {
    cat<<USAGE

Usage: ${0##*/} <source case> <variant case 1> ... <variant case N>

There is often a need to run several CFD simulations which are generally similar
but include small differences.  The foamMergeCase script is designed to help
maintain cases in these circumstances.  It allows users to create a 'variant'
case that contains file differences from a complete, 'source' case.

The file differences in the variant case are organised in the same directory
structure as normal.  Differences are stored in files given a '.orig' extension
and only need to include the modified entries.  foamMergeCase can then be
executed from within the variant case directory using the source case directory
provided as an argument.  The source case files are copied into the variant
case, merging the changes from the '.orig' file(s).

An accompanying foamUnMergeCase script removes files that the foamMergeCase
script copies from the source case, resetting the case back to its original
variant form.  The variant case should include their own Allrun and Allclean
scripts which include the respective foamMergeCase and foamUnMergeCase commands.

The following example creates a variant of the 'pitzDaily' case named
'pitzDailyWater', changing the fluid from air to water by changing kinematic
viscosity nu to 1e-6.  To do this, the user should first copy the 'pitzDaily'
case into their working directory and delete files they do not wish to modify
during the merge.

  cp -r \$FOAM_TUTORIALS/incompressibleFluid/pitzDaily pitzDailyWater
  cd pitzDailyWater
  rm -rf 0 system constant/momentumTransport
  mv constant/physicalProperties constant/physicalProperties.orig

The user can then remove the 'viscosityModel' entry from
constant/physicalProperties.orig and set the 'nu' entry to 1e-6, either by
editing the file or by running the following 'foamDictionary' commands.

  foamDictionary -entry "viscosityModel" -remove constant/physicalProperties.orig
  foamDictionary -set "nu=1e-6" constant/physicalProperties.orig

To complete the automation of this variant case, the user should add the
following foamMergeCase command before the 'runApplication blockMesh' command in
the Allrun script.

  runApplication foamMergeCase \$FOAM_TUTORIALS/incompressibleFluid/pitzDaily

The user can now run the case by executing the Allrun script with './Allrun'.
To be able to reset the case back to its original form, the user should first
copy a standard Allclean script into the case directory as follows.

  foamGet Allclean

Then they should add the equivalent 'foamUnMergeCase' command at the end of the
file, i.e. after the 'cleanCase' function is called.

  foamUnMergeCase \$FOAM_TUTORIALS/incompressibleFluid/pitzDaily

The case can now be reset by running the Allclean script with'./Allclean'

USAGE
}

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

isCaseValid() {
    foamListTimes -case "$1" >/dev/null 2>&1
}

while [ "$#" -gt 0 ]
do
    case "$1" in
    -h | -help)
        usage && exit 0
        ;;
    -*)
        error "unknown option: '$*'"
        ;;
    *)
        break
        ;;
    esac
done

[ $# -ge 1 ] || error "Incorrect arguments specified"

# Set the source directory
isCaseValid "$1" || error "'$1' is not a valid case directory"
srcDir="$1"
shift

# Set the variant directories
varDirs=()
while [ "$#" -gt 0 ]
do
    [ -d "$1" ] || error "'$1' is not a valid directory"
    varDirs+=("$1")
    shift
done

# Get the initial time
t0=$(foamDictionary "$srcDir/system/controlDict" -entry startTime -value)

# Check that all affected files in this case have .orig extensions so that
# nothing gets permanently overridden
for srcOrVarDir in "$srcDir" "${varDirs[@]}"
do
    while read -r file
    do
        thisFileNoOrig="./${file%*.orig}"

        if [ -f "$thisFileNoOrig" ]
        then
            error "Cannot not merge into non-orig file '$thisFileNoOrig'"
        fi

    done < <(
        cd "$srcOrVarDir" && \
        find ./"$t0" constant system \
        \( -path constant/polyMesh -prune -o -path constant/fvMesh -prune \) \
        -o -type f -print 2>/dev/null
    )
done

# Copy files from the source case
while read -r file
do
    srcFile="$srcDir/$file"
    thisFileNoOrig="./${file%*.orig}"
    thisDir="$(dirname "$thisFileNoOrig")"

    # Don't copy if there is an orig-variant of this file
    [ -f "$srcFile.orig" ] && continue

    # Make the directory
    [ -d "$thisDir" ] || mkdir -p "$thisDir"

    # Copy the source file into the case
    cp "$srcFile" "$thisFileNoOrig"

done < <(
    cd "$srcDir" && \
    find ./"$t0" constant system \
    \( -path constant/polyMesh -prune -o -path constant/fvMesh -prune \) \
    -o -type f -print 2>/dev/null
)

# Merge in settings from the variant directories
prevDirs=("$srcDir")
for varDir in "${varDirs[@]}"
do
    prevDirs+=("$varDir")

    while read -r file
    do
        varFile="$varDir/$file"
        thisFileNoOrig="./${file%*.orig}"
        thisDir="$(dirname "$thisFileNoOrig")"

        # Don't merge or copy if there is a previous orig-variant of this file
        prevFile=
        for prevDir in "${prevDirs[@]}"
        do
            if [ -f "$prevDir/$file.orig" ]
            then
                prevFile="$prevDir/$file.orig"
            fi
        done
        [ -z "$prevFile" ] || continue

        # Make the directory
        [ -d "$thisDir" ] || mkdir -p "$thisDir"

        # If the file exists in this case then merge into it,
        # and if not then copy to create it
        if [ -f "$thisFileNoOrig" ]
        then
            foamDictionary -dict -merge "$varFile" "$thisFileNoOrig"
        else
            cp "$varFile" "$thisFileNoOrig"
        fi

    done < <(
        cd "$varDir" && \
        find ./"$t0" constant system \
        \( -path constant/polyMesh -prune -o -path constant/fvMesh -prune \) \
        -o -type f -print 2>/dev/null
    )
done

# Merge in settings from this directory
while read -r file
do
    thisFileOrig="./$file"
    thisFileNoOrig="./${file%*.orig}"

    # If the file exists in this case then merge into it
    if [ -f "$thisFileNoOrig" ]
    then
        foamDictionary -dict -merge "$thisFileOrig" "$thisFileNoOrig"
    fi

done < <(find ./"$t0" constant system -type f -name "*.orig" 2>/dev/null)

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