/*---------------------------------------------------------------------------*\
  =========                 |
  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
   \\    /   O peration     |
    \\  /    A nd           | www.openfoam.com
     \\/     M anipulation  |
-------------------------------------------------------------------------------
    Copyright (C) 2011-2016 OpenFOAM Foundation
    Copyright (C) 2016-2023 OpenCFD Ltd.
-------------------------------------------------------------------------------
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/>.

Application
    createBaffles

Group
    grpMeshManipulationUtilities

Description
    Makes internal faces into boundary faces. Does not duplicate points, unlike
    mergeOrSplitBaffles.

    Note: if any coupled patch face is selected for baffling the opposite
    member has to be selected for baffling as well.

    - if the patch already exists will not override it nor its fields
    - if the patch does not exist it will be created together with 'calculated'
      patchfields unless the field is mentioned in the patchFields section.
    - any 0-sized patches (since faces have been moved out) will get removed

\*---------------------------------------------------------------------------*/

#include "argList.H"
#include "Time.H"
#include "polyTopoChange.H"
#include "polyModifyFace.H"
#include "polyAddFace.H"
#include "ReadFields.H"
#include "volFields.H"
#include "surfaceFields.H"
#include "fvMeshMapper.H"
#include "faceSelection.H"

#include "fvMeshTools.H"
#include "topoSet.H"
#include "processorPolyPatch.H"
#include "processorMeshes.H"

using namespace Foam;

// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

label addPatch
(
    fvMesh& mesh,
    const word& patchName,
    const word& groupName,
    const dictionary& patchDict
)
{
    const polyBoundaryMesh& pbm = mesh.boundaryMesh();

    if (pbm.findPatchID(patchName) == -1)
    {
        autoPtr<polyPatch> ppPtr
        (
            polyPatch::New
            (
                patchName,
                patchDict,
                0,
                pbm
            )
        );
        auto& pp = *ppPtr;
        pp.addGroup(groupName);


        // Add patch, create calculated everywhere
        fvMeshTools::addPatch
        (
            mesh,
            pp,
            dictionary(),   // do not set specialised patchFields
            fvPatchFieldBase::calculatedType(),
            true            // parallel sync'ed addition
        );
    }
    else
    {
        Info<< "Patch '" << patchName
            << "' already exists.  Only "
            << "moving patch faces - type will remain the same"
            << endl;
    }

    return pbm.findPatchID(patchName);
}


// Filter out the empty patches.
void filterPatches(fvMesh& mesh, const wordHashSet& addedPatchNames)
{
    // Remove any zero-sized ones. Assumes
    // - processor patches are already only there if needed
    // - all other patches are available on all processors
    // - but coupled ones might still be needed, even if zero-size
    //   (e.g. processorCyclic)
    // See also logic in createPatch.
    const polyBoundaryMesh& pbm = mesh.boundaryMesh();

    labelList oldToNew(pbm.size(), -1);
    label newPatchi = 0;
    forAll(pbm, patchi)
    {
        const polyPatch& pp = pbm[patchi];

        if (!isA<processorPolyPatch>(pp))
        {
            if
            (
                isA<coupledPolyPatch>(pp)
             || returnReduceOr(pp.size())
             || addedPatchNames.contains(pp.name())
            )
            {
                // Coupled (and unknown size) or uncoupled and used
                oldToNew[patchi] = newPatchi++;
            }
        }
    }

    forAll(pbm, patchi)
    {
        const polyPatch& pp = pbm[patchi];

        if (isA<processorPolyPatch>(pp))
        {
            oldToNew[patchi] = newPatchi++;
        }
    }


    const label nKeepPatches = newPatchi;

    // Shuffle unused ones to end
    if (nKeepPatches != pbm.size())
    {
        Info<< endl
            << "Removing zero-sized patches:" << endl << incrIndent;

        forAll(oldToNew, patchi)
        {
            if (oldToNew[patchi] == -1)
            {
                Info<< indent << pbm[patchi].name()
                    << " type " << pbm[patchi].type()
                    << " at position " << patchi << endl;
                oldToNew[patchi] = newPatchi++;
            }
        }
        Info<< decrIndent;

        fvMeshTools::reorderPatches(mesh, oldToNew, nKeepPatches, true);
        Info<< endl;
    }
}


void modifyOrAddFace
(
    polyTopoChange& meshMod,
    const face& f,
    const label facei,
    const label own,
    const bool flipFaceFlux,
    const label newPatchi,
    const label zoneID,
    const bool zoneFlip,

    bitSet& modifiedFace
)
{
    if (modifiedFace.set(facei))
    {
        // First usage of face. Modify.
        meshMod.setAction
        (
            polyModifyFace
            (
                f,                          // modified face
                facei,                      // label of face
                own,                        // owner
                -1,                         // neighbour
                flipFaceFlux,               // face flip
                newPatchi,                  // patch for face
                false,                      // remove from zone
                zoneID,                     // zone for face
                zoneFlip                    // face flip in zone
            )
        );
    }
    else
    {
        // Second or more usage of face. Add.
        meshMod.setAction
        (
            polyAddFace
            (
                f,                          // modified face
                own,                        // owner
                -1,                         // neighbour
                -1,                         // master point
                -1,                         // master edge
                facei,                      // master face
                flipFaceFlux,               // face flip
                newPatchi,                  // patch for face
                zoneID,                     // zone for face
                zoneFlip                    // face flip in zone
            )
        );
    }
}


// Create faces for fZone faces. Usually newMasterPatches, newSlavePatches
// only size one but can be more for duplicate baffle sets
void createFaces
(
    const bool internalFacesOnly,
    const fvMesh& mesh,
    const faceZone& fZone,
    const labelUList& newMasterPatches,
    const labelUList& newSlavePatches,
    polyTopoChange& meshMod,
    bitSet& modifiedFace,
    label& nModified
)
{
    const polyBoundaryMesh& pbm = mesh.boundaryMesh();

    forAll(newMasterPatches, i)
    {
        const label newMasterPatchi = newMasterPatches[i];
        const label newSlavePatchi = newSlavePatches[i];

        // Pass 1. Do selected side of zone
        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        for (label facei = 0; facei < mesh.nInternalFaces(); ++facei)
        {
            const label zoneFacei = fZone.whichFace(facei);

            if (zoneFacei != -1)
            {
                if (!fZone.flipMap()[zoneFacei])
                {
                    // Use owner side of face
                    modifyOrAddFace
                    (
                        meshMod,
                        mesh.faces()[facei],    // modified face
                        facei,                  // label of face
                        mesh.faceOwner()[facei],// owner
                        false,                  // face flip
                        newMasterPatchi,        // patch for face
                        fZone.index(),          // zone for face
                        false,                  // face flip in zone
                        modifiedFace            // modify or add status
                    );
                }
                else
                {
                    // Use neighbour side of face.
                    // To keep faceZone pointing out of original neighbour
                    // we don't need to set faceFlip since that cell
                    // now becomes the owner
                    modifyOrAddFace
                    (
                        meshMod,
                        mesh.faces()[facei].reverseFace(),  // modified face
                        facei,                      // label of face
                        mesh.faceNeighbour()[facei],// owner
                        true,                       // face flip
                        newMasterPatchi,            // patch for face
                        fZone.index(),              // zone for face
                        false,                      // face flip in zone
                        modifiedFace                // modify or add status
                    );
                }

                ++nModified;
            }
        }


        // Pass 2. Do other side of zone
        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        for (label facei = 0; facei < mesh.nInternalFaces(); ++facei)
        {
            const label zoneFacei = fZone.whichFace(facei);

            if (zoneFacei != -1)
            {
                if (!fZone.flipMap()[zoneFacei])
                {
                    // Use neighbour side of face
                    modifyOrAddFace
                    (
                        meshMod,
                        mesh.faces()[facei].reverseFace(),  // modified face
                        facei,                          // label of face
                        mesh.faceNeighbour()[facei],    // owner
                        true,                           // face flip
                        newSlavePatchi,                 // patch for face
                        fZone.index(),                  // zone for face
                        true,                           // face flip in zone
                        modifiedFace                    // modify or add
                    );
                }
                else
                {
                    // Use owner side of face
                    modifyOrAddFace
                    (
                        meshMod,
                        mesh.faces()[facei],    // modified face
                        facei,                  // label of face
                        mesh.faceOwner()[facei],// owner
                        false,                  // face flip
                        newSlavePatchi,         // patch for face
                        fZone.index(),          // zone for face
                        true,                   // face flip in zone
                        modifiedFace            // modify or add status
                    );
                }
            }
        }


        // Modify any boundary faces
        // ~~~~~~~~~~~~~~~~~~~~~~~~~

        // Normal boundary:
        // - move to new patch. Might already be back-to-back baffle
        // you want to add cyclic to. Do warn though.
        //
        // Processor boundary:
        // - do not move to cyclic
        // - add normal patches though.

        // For warning once per patch.
        labelHashSet patchWarned;

        forAll(pbm, patchi)
        {
            const polyPatch& pp = pbm[patchi];
            const bool isCoupled = pp.coupled();

            if
            (
                isCoupled
             && (
                    pbm[newMasterPatchi].coupled()
                 || pbm[newSlavePatchi].coupled()
                )
            )
            {
                // Do not allow coupled faces to be moved to different
                // coupled patches.
            }
            else if (isCoupled || !internalFacesOnly)
            {
                forAll(pp, i)
                {
                    label facei = pp.start()+i;

                    label zoneFacei = fZone.whichFace(facei);

                    if (zoneFacei != -1)
                    {
                        if (!isCoupled && patchWarned.insert(patchi))
                        {
                            WarningInFunction
                                << "Found boundary face (patch " << pp.name()
                                << ") in faceZone " << fZone.name()
                                << " to convert to baffle patches "
                                << pbm[newMasterPatchi].name() << "/"
                                << pbm[newSlavePatchi].name() << nl
                                << "    Set internalFacesOnly to true in the"
                                << " createBaffles control dictionary if you"
                                << " don't wish to convert boundary faces."
                                << endl;
                        }

                        modifyOrAddFace
                        (
                            meshMod,
                            mesh.faces()[facei],        // modified face
                            facei,                      // label of face
                            mesh.faceOwner()[facei],    // owner
                            false,                      // face flip
                            fZone.flipMap()[zoneFacei]
                          ? newSlavePatchi
                          : newMasterPatchi,            // patch for face
                            fZone.index(),              // zone for face
                            fZone.flipMap()[zoneFacei], // face flip in zone
                            modifiedFace                // modify or add
                        );

                        ++nModified;
                    }
                }
            }
        }
    }
}


int main(int argc, char *argv[])
{
    argList::addNote
    (
        "Makes internal faces into boundary faces.\n"
        "Does not duplicate points."
    );

    argList::addOption("dict", "file", "Alternative createBafflesDict");
    #include "addOverwriteOption.H"
    #include "addRegionOption.H"

    argList::noFunctionObjects();  // Never use function objects

    #include "setRootCase.H"
    #include "createTime.H"
    #include "createNamedMesh.H"


    const bool overwrite = args.found("overwrite");

    const word oldInstance = mesh.pointsInstance();

    const word dictName("createBafflesDict");
    #include "setSystemMeshDictionaryIO.H"

    bool internalFacesOnly(false);

    bool noFields(false);

    PtrList<faceSelection> selectors;
    {
        Info<< "Reading baffle criteria from " << dictIO.name() << nl << endl;
        IOdictionary dict(dictIO);

        internalFacesOnly = dict.get<bool>("internalFacesOnly");
        noFields = dict.getOrDefault("noFields", false);

        const dictionary& selectionsDict = dict.subDict("baffles");

        selectors.resize(selectionsDict.size());

        label nselect = 0;
        for (const entry& dEntry : selectionsDict)
        {
            if (dEntry.isDict())
            {
                selectors.set
                (
                    nselect,
                    faceSelection::New(dEntry.keyword(), mesh, dEntry.dict())
                );

                ++nselect;
            }
        }

        selectors.resize(nselect);
    }


    if (internalFacesOnly)
    {
        Info<< "Not converting faces on non-coupled patches." << nl << endl;
    }


    // Read objects in time directory
    IOobjectList objects(mesh, runTime.timeName());

    // Read vol fields.
    Info<< "Reading geometric fields" << nl << endl;
    PtrList<volScalarField> vsFlds;
    if (!noFields) ReadFields(mesh, objects, vsFlds);

    PtrList<volVectorField> vvFlds;
    if (!noFields) ReadFields(mesh, objects, vvFlds);

    PtrList<volSphericalTensorField> vstFlds;
    if (!noFields) ReadFields(mesh, objects, vstFlds);

    PtrList<volSymmTensorField> vsymtFlds;
    if (!noFields) ReadFields(mesh, objects, vsymtFlds);

    PtrList<volTensorField> vtFlds;
    if (!noFields) ReadFields(mesh, objects, vtFlds);

    // Read surface fields.

    PtrList<surfaceScalarField> ssFlds;
    if (!noFields) ReadFields(mesh, objects, ssFlds);

    PtrList<surfaceVectorField> svFlds;
    if (!noFields) ReadFields(mesh, objects, svFlds);

    PtrList<surfaceSphericalTensorField> sstFlds;
    if (!noFields) ReadFields(mesh, objects, sstFlds);

    PtrList<surfaceSymmTensorField> ssymtFlds;
    if (!noFields) ReadFields(mesh, objects, ssymtFlds);

    PtrList<surfaceTensorField> stFlds;
    if (!noFields) ReadFields(mesh, objects, stFlds);




    // Creating (if necessary) faceZones
    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    for (const faceSelection& selector : selectors)
    {
        const word& name = selector.name();

        if (mesh.faceZones().findZoneID(name) == -1)
        {
            mesh.faceZones().clearAddressing();
            const label zoneID = mesh.faceZones().size();

            mesh.faceZones().emplace_back(name, zoneID, mesh.faceZones());
        }
    }


    // Select faces
    // ~~~~~~~~~~~~

    //- Per face zoneID it is in and flip status.
    labelList faceToZoneID(mesh.nFaces(), -1);
    boolList faceToFlip(mesh.nFaces(), false);
    for (faceSelection& selector : selectors)
    {
        const word& name = selector.name();
        const label zoneID = mesh.faceZones().findZoneID(name);

        selector.select(zoneID, faceToZoneID, faceToFlip);
    }

    // Add faces to faceZones
    labelList nFaces(mesh.faceZones().size(), Zero);
    for (const label zoneID : faceToZoneID)
    {
        if (zoneID != -1)
        {
            ++nFaces[zoneID];
        }
    }

    for (const faceSelection& selector : selectors)
    {
        const word& name = selector.name();
        const label zoneID = mesh.faceZones().findZoneID(name);

        label& n = nFaces[zoneID];
        labelList addr(n);
        boolList flip(n);
        n = 0;
        forAll(faceToZoneID, facei)
        {
            label zone = faceToZoneID[facei];
            if (zone == zoneID)
            {
                addr[n] = facei;
                flip[n] = faceToFlip[facei];
                ++n;
            }
        }

        Info<< "Created zone " << name
            << " at index " << zoneID
            << " with " << returnReduce(n, sumOp<label>()) << " faces" << endl;

        mesh.faceZones().set
        (
            zoneID,
            new faceZone
            (
                name,
                std::move(addr),
                std::move(flip),
                zoneID,
                mesh.faceZones()
            )
        );
    }



    // Count patches to add
    // ~~~~~~~~~~~~~~~~~~~~
    wordHashSet bafflePatches;
    {
        for (const faceSelection& selector : selectors)
        {
            const dictionary& dict = selector.dict();
            const word& groupName = selector.name();
            const dictionary* patchesDictPtr = dict.findDict("patches");

            if (patchesDictPtr)
            {
                for (const entry& dEntry : *patchesDictPtr)
                {
                    const word patchName(dEntry.dict().get<word>("name"));

                    bafflePatches.insert(patchName);
                }
            }
            else
            {
                const word masterName(groupName + "_master");
                bafflePatches.insert(masterName);

                const word slaveName(groupName + "_slave");
                bafflePatches.insert(slaveName);
            }
        }
    }


    // Create baffles
    // ~~~~~~~~~~~~~~
    // Is done in multiple steps
    // - create patches with 'calculated' patchFields
    // - move faces into these patches
    // - change the patchFields to the wanted type
    // This order is done so e.g. fixedJump works:
    // - you cannot create patchfields at the same time as patches since
    //   they do an evaluate upon construction
    // - you want to create the patchField only after you have faces
    //   so you don't get the 'create-from-nothing' mapping problem.


    // Pass 1: add patches
    // ~~~~~~~~~~~~~~~~~~~

    // wordHashSet addedPatches;
    {
        const polyBoundaryMesh& pbm = mesh.boundaryMesh();
        for (const faceSelection& selector : selectors)
        {
            const dictionary& dict = selector.dict();
            const word& groupName = selector.name();
            const dictionary* patchesDictPtr = dict.findDict("patches");

            if (patchesDictPtr)
            {
                for (const entry& dEntry : *patchesDictPtr)
                {
                    const dictionary& dict = dEntry.dict();

                    const word patchName(dict.get<word>("name"));

                    if (pbm.findPatchID(patchName) == -1)
                    {
                        dictionary patchDict = dict;
                        patchDict.set("nFaces", 0);
                        patchDict.set("startFace", 0);

                        // Note: do not set coupleGroup if constructed from
                        //       baffles so you have freedom specifying it
                        //       yourself.
                        //patchDict.set("coupleGroup", groupName);

                        addPatch(mesh, patchName, groupName, patchDict);
                    }
                    else
                    {
                        Info<< "Patch '" << patchName
                            << "' already exists.  Only "
                            << "moving patch faces - type will remain the same"
                            << endl;
                    }
                }
            }
            else
            {
                const dictionary& patchSource = dict.subDict("patchPairs");
                const word masterName(groupName + "_master");
                const word slaveName(groupName + "_slave");

                word groupNameMaster = groupName;
                word groupNameSlave = groupName;


                dictionary patchDictMaster(patchSource);
                patchDictMaster.set("nFaces", 0);
                patchDictMaster.set("startFace", 0);
                patchDictMaster.set("coupleGroup", groupName);

                dictionary patchDictSlave(patchDictMaster);

                // Note: This is added for the particular case where we want
                // master and slave in different groupNames
                // (ie 3D thermal baffles)

                const bool sameGroup =
                    patchSource.getOrDefault("sameGroup", true);

                if (!sameGroup)
                {
                    groupNameMaster = groupName + "Group_master";
                    groupNameSlave = groupName + "Group_slave";
                    patchDictMaster.set("coupleGroup", groupNameMaster);
                    patchDictSlave.set("coupleGroup", groupNameSlave);
                }

                addPatch(mesh, masterName, groupNameMaster, patchDictMaster);
                addPatch(mesh, slaveName, groupNameSlave, patchDictSlave);
            }
        }
    }


    // Make sure patches and zoneFaces are synchronised across couples
    mesh.boundaryMesh().checkParallelSync(true);
    mesh.faceZones().checkParallelSync(true);



    // Mesh change container
    polyTopoChange meshMod(mesh);

    const polyBoundaryMesh& pbm = mesh.boundaryMesh();


    // Do the actual changes. Note:
    // - loop in incrementing face order (not necessary if faceZone ordered).
    //   Preserves any existing ordering on patch faces.
    // - two passes, do non-flip faces first and flip faces second. This
    //   guarantees that when e.g. creating a cyclic all faces from one
    //   side come first and faces from the other side next.

    // Whether first use of face (modify) or consecutive (add)
    bitSet modifiedFace(mesh.nFaces());
    label nModified = 0;

    for (const faceSelection& selector : selectors)
    {
        const word& groupName = selector.name();
        const dictionary& dict = selector.dict();
        const dictionary* patchesDictPtr = dict.findDict("patches");

        const label zoneID = mesh.faceZones().findZoneID(groupName);
        const faceZone& fZone = mesh.faceZones()[zoneID];

        DynamicList<label> newMasterPatches;
        DynamicList<label> newSlavePatches;

        if (patchesDictPtr)
        {
            bool master = true;

            for (const entry& dEntry : *patchesDictPtr)
            {
                const word patchName(dEntry.dict().get<word>("name"));

                const label patchi = pbm.findPatchID(patchName);

                if (master)
                {
                    newMasterPatches.push_back(patchi);
                }
                else
                {
                    newSlavePatches.push_back(patchi);
                }
                master = !master;
            }
        }
        else
        {
            const word masterName(groupName + "_master");
            newMasterPatches.push_back(pbm.findPatchID(masterName));

            const word slaveName(groupName + "_slave");
            newSlavePatches.push_back(pbm.findPatchID(slaveName));
        }


        createFaces
        (
            internalFacesOnly,
            mesh,
            fZone,
            newMasterPatches,
            newSlavePatches,
            meshMod,
            modifiedFace,
            nModified
        );
    }


    Info<< "Converted " << returnReduce(nModified, sumOp<label>())
        << " faces into boundary faces in patches "
        << bafflePatches.sortedToc() << nl << endl;

    if (!overwrite)
    {
        ++runTime;
    }

    // Change the mesh. Change points directly (no inflation).
    autoPtr<mapPolyMesh> map = meshMod.changeMesh(mesh, false);

    // Update fields
    mesh.updateMesh(map());


    // Correct boundary faces mapped-out-of-nothing.
    // This is just a hack to correct the value field.
    {
        fvMeshMapper mapper(mesh, map());
        bool hasWarned = false;

        for (const word& patchName : bafflePatches)
        {
            label patchi = mesh.boundaryMesh().findPatchID(patchName);

            const fvPatchMapper& pm = mapper.boundaryMap()[patchi];

            if (pm.sizeBeforeMapping() == 0)
            {
                if (!hasWarned)
                {
                    hasWarned = true;
                    WarningInFunction
                        << "Setting field on boundary faces to zero." << endl
                        << "You might have to edit these fields." << endl;
                }

                fvMeshTools::zeroPatchFields(mesh, patchi);
            }
        }
    }


    // Pass 2: change patchFields
    // ~~~~~~~~~~~~~~~~~~~~~~~~~~

    {
        const polyBoundaryMesh& pbm = mesh.boundaryMesh();
        for (const faceSelection& selector : selectors)
        {
            const dictionary& dict = selector.dict();
            const word& groupName = selector.name();
            const dictionary* patchesDictPtr = dict.findDict("patches");

            if (patchesDictPtr)
            {
                for (const entry& dEntry : *patchesDictPtr)
                {
                    const dictionary& dict = dEntry.dict();
                    const word patchName(dict.get<word>("name"));
                    const label patchi = pbm.findPatchID(patchName);

                    const dictionary* patchFieldsDictPtr
                        = dict.findDict("patchFields");

                    if (patchFieldsDictPtr)
                    {
                        fvMeshTools::setPatchFields
                        (
                            mesh,
                            patchi,
                            *patchFieldsDictPtr
                        );
                    }
                }
            }
            else
            {
                const dictionary& patchSource = dict.subDict("patchPairs");

                const bool sameGroup =
                    patchSource.getOrDefault("sameGroup", true);

                const dictionary* patchFieldsDictPtr
                    = patchSource.findDict("patchFields");

                if (patchFieldsDictPtr)
                {
                    if (sameGroup)
                    {
                        // Make a copy
                        dictionary patchFieldsDict(*patchFieldsDictPtr);

                        // Add coupleGroup to all entries
                        for (entry& dEntry : patchFieldsDict)
                        {
                            if (dEntry.isDict())
                            {
                                dictionary& dict = dEntry.dict();
                                dict.set("coupleGroup", groupName);
                            }
                        }

                        for
                        (
                            const label patchi
                          : pbm.groupPatchIDs()[groupName]
                        )
                        {
                            fvMeshTools::setPatchFields
                            (
                                mesh,
                                patchi,
                                patchFieldsDict
                            );
                        }
                    }
                    else
                    {
                        const word masterPatchName(groupName + "_master");
                        const word slavePatchName(groupName + "_slave");

                        label patchiMaster = pbm.findPatchID(masterPatchName);
                        label patchiSlave = pbm.findPatchID(slavePatchName);

                        fvMeshTools::setPatchFields
                        (
                            mesh,
                            patchiMaster,
                            *patchFieldsDictPtr
                        );

                        fvMeshTools::setPatchFields
                        (
                            mesh,
                            patchiSlave,
                            *patchFieldsDictPtr
                        );
                    }
                }
            }
        }
    }


    // Move mesh (since morphing might not do this)
    if (map().hasMotionPoints())
    {
        mesh.movePoints(map().preMotionPoints());
    }


    // Remove any now zero-sized patches
    filterPatches(mesh, bafflePatches);


    if (overwrite)
    {
        mesh.setInstance(oldInstance);
    }

    Info<< "Writing mesh to " << runTime.timeName() << endl;

    mesh.write();
    topoSet::removeFiles(mesh);
    processorMeshes::removeFiles(mesh);

    Info<< "End\n" << endl;

    return 0;
}


// ************************************************************************* //
