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

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

#include "FaceCellWave.H"
#include "polyMesh.H"
#include "processorPolyPatch.H"
#include "cyclicPolyPatch.H"
#include "cyclicAMIPolyPatch.H"
#include "OPstream.H"
#include "IPstream.H"
#include "PstreamReduceOps.H"
#include "debug.H"
#include "typeInfo.H"
#include "SubField.H"
#include "globalMeshData.H"

// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //

template<class Type, class TrackingData>
const Foam::scalar Foam::FaceCellWave<Type, TrackingData>::geomTol_ = 1e-6;

template<class Type, class TrackingData>
Foam::scalar Foam::FaceCellWave<Type, TrackingData>::propagationTol_ = 0.01;

template<class Type, class TrackingData>
int Foam::FaceCellWave<Type, TrackingData>::dummyTrackData_ = 12345;


namespace Foam
{
    template<class Type, class TrackingData>
    class combine
    {
        //- Combine operator for AMIInterpolation

        FaceCellWave<Type, TrackingData>& solver_;

        const cyclicAMIPolyPatch& patch_;

    public:

            combine
            (
                FaceCellWave<Type, TrackingData>& solver,
                const cyclicAMIPolyPatch& patch
            )
            :
                solver_(solver),
                patch_(patch)
            {}


            void operator()
            (
                Type& x,
                const label facei,
                const Type& y,
                const scalar weight
            ) const
            {
                if (y.valid(solver_.data()))
                {
                    label meshFacei = -1;
                    if (patch_.owner())
                    {
                        meshFacei = patch_.start() + facei;
                    }
                    else
                    {
                        meshFacei = patch_.neighbPatch().start() + facei;
                    }
                    x.updateFace
                    (
                        solver_.mesh(),
                        meshFacei,
                        y,
                        solver_.propagationTol(),
                        solver_.data()
                    );
                }
            }
    };
}


// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //

template<class Type, class TrackingData>
bool Foam::FaceCellWave<Type, TrackingData>::updateCell
(
    const label celli,
    const label neighbourFacei,
    const Type& neighbourInfo,
    const scalar tol,
    Type& cellInfo
)
{
    // Update info for celli, at position pt, with information from
    // neighbouring face/cell.
    // Updates:
    //      - changedCell_, changedCells_
    //      - statistics: nEvals_, nUnvisitedCells_

    ++nEvals_;

    const bool wasValid = cellInfo.valid(td_);

    const bool propagate =
        cellInfo.updateCell
        (
            mesh_,
            celli,
            neighbourFacei,
            neighbourInfo,
            tol,
            td_
        );

    if (propagate)
    {
        if (changedCell_.set(celli))
        {
            changedCells_.append(celli);
        }
    }

    if (!wasValid && cellInfo.valid(td_))
    {
        --nUnvisitedCells_;
    }

    return propagate;
}


template<class Type, class TrackingData>
bool Foam::FaceCellWave<Type, TrackingData>::updateFace
(
    const label facei,
    const label neighbourCelli,
    const Type& neighbourInfo,
    const scalar tol,
    Type& faceInfo
)
{
    // Update info for facei, at position pt, with information from
    // neighbouring face/cell.
    // Updates:
    //      - changedFace_, changedFaces_,
    //      - statistics: nEvals_, nUnvisitedFaces_

    ++nEvals_;

    const bool wasValid = faceInfo.valid(td_);

    const bool propagate =
        faceInfo.updateFace
        (
            mesh_,
            facei,
            neighbourCelli,
            neighbourInfo,
            tol,
            td_
        );

    if (propagate)
    {
        if (changedFace_.set(facei))
        {
            changedFaces_.append(facei);
        }
    }

    if (!wasValid && faceInfo.valid(td_))
    {
        --nUnvisitedFaces_;
    }

    return propagate;
}


template<class Type, class TrackingData>
bool Foam::FaceCellWave<Type, TrackingData>::updateFace
(
    const label facei,
    const Type& neighbourInfo,
    const scalar tol,
    Type& faceInfo
)
{
    // Update info for facei, at position pt, with information from
    // same face.
    // Updates:
    //      - changedFace_, changedFaces_,
    //      - statistics: nEvals_, nUnvisitedFaces_

    ++nEvals_;

    const bool wasValid = faceInfo.valid(td_);

    const bool propagate =
        faceInfo.updateFace
        (
            mesh_,
            facei,
            neighbourInfo,
            tol,
            td_
        );

    if (propagate)
    {
        if (changedFace_.set(facei))
        {
            changedFaces_.append(facei);
        }
    }

    if (!wasValid && faceInfo.valid(td_))
    {
        --nUnvisitedFaces_;
    }

    return propagate;
}


template<class Type, class TrackingData>
void Foam::FaceCellWave<Type, TrackingData>::checkCyclic
(
    const polyPatch& patch
) const
{
    // For debugging: check status on both sides of cyclic

    const cyclicPolyPatch& nbrPatch =
        refCast<const cyclicPolyPatch>(patch).neighbPatch();

    forAll(patch, patchFacei)
    {
        const label i1 = patch.start() + patchFacei;
        const label i2 = nbrPatch.start() + patchFacei;

        if
        (
           !allFaceInfo_[i1].sameGeometry
            (
                mesh_,
                allFaceInfo_[i2],
                geomTol_,
                td_
            )
        )
        {
            FatalErrorInFunction
                << "   faceInfo:" << allFaceInfo_[i1]
                << "   otherfaceInfo:" << allFaceInfo_[i2]
                << abort(FatalError);
        }

        if (changedFace_.test(i1) != changedFace_.test(i2))
        {
            FatalErrorInFunction
                << "   faceInfo:" << allFaceInfo_[i1]
                << "   otherfaceInfo:" << allFaceInfo_[i2]
                << "   changedFace:" << changedFace_.test(i1)
                << "   otherchangedFace:" << changedFace_.test(i2)
                << abort(FatalError);
        }
    }
}


template<class Type, class TrackingData>
template<class PatchType>
bool Foam::FaceCellWave<Type, TrackingData>::hasPatch() const
{
    for (const polyPatch& p : mesh_.boundaryMesh())
    {
        if (isA<PatchType>(p))
        {
            return true;
        }
    }
    return false;
}


template<class Type, class TrackingData>
void Foam::FaceCellWave<Type, TrackingData>::setFaceInfo
(
    const label facei,
    const Type& faceInfo
)
{
    const bool wasValid = allFaceInfo_[facei].valid(td_);

    // Copy info for facei
    allFaceInfo_[facei] = faceInfo;

    // Maintain count of unset faces
    if (!wasValid && allFaceInfo_[facei].valid(td_))
    {
        --nUnvisitedFaces_;
    }

    // Mark facei as visited and changed (both on list and on face itself)
    changedFace_.set(facei);
    changedFaces_.append(facei);
}


template<class Type, class TrackingData>
void Foam::FaceCellWave<Type, TrackingData>::setFaceInfo
(
    const labelUList& changedFaces,
    const List<Type>& changedFacesInfo
)
{
    forAll(changedFaces, changedFacei)
    {
        const label facei = changedFaces[changedFacei];

        const bool wasValid = allFaceInfo_[facei].valid(td_);

        // Copy info for facei
        allFaceInfo_[facei] = changedFacesInfo[changedFacei];

        // Maintain count of unset faces
        if (!wasValid && allFaceInfo_[facei].valid(td_))
        {
            --nUnvisitedFaces_;
        }

        // Mark facei as changed, both on list and on face itself.
        changedFace_.set(facei);
        changedFaces_.append(facei);
    }
}


template<class Type, class TrackingData>
void Foam::FaceCellWave<Type, TrackingData>::mergeFaceInfo
(
    const polyPatch& patch,
    const label nFaces,
    const labelUList& changedFaces,
    const List<Type>& changedFacesInfo
)
{
    // Merge face information into member data

    for (label changedFacei = 0; changedFacei < nFaces; ++changedFacei)
    {
        const Type& newInfo = changedFacesInfo[changedFacei];
        const label patchFacei = changedFaces[changedFacei];

        const label meshFacei = patch.start() + patchFacei;

        Type& currInfo = allFaceInfo_[meshFacei];

        if (!currInfo.equal(newInfo, td_))
        {
            updateFace
            (
                meshFacei,
                newInfo,
                propagationTol_,
                currInfo
            );
        }
    }
}


template<class Type, class TrackingData>
Foam::label Foam::FaceCellWave<Type, TrackingData>::getChangedPatchFaces
(
    const polyPatch& patch,
    const label startFacei,
    const label nFaces,
    labelList& changedPatchFaces,
    List<Type>& changedPatchFacesInfo
) const
{
    // Construct compact patchFace change arrays for a (slice of a) single
    // patch. changedPatchFaces in local patch numbering.
    // Return length of arrays.
    label nChanged = 0;

    for (label i = 0; i < nFaces; ++i)
    {
        const label patchFacei = i + startFacei;
        const label meshFacei = patch.start() + patchFacei;

        if (changedFace_.test(meshFacei))
        {
            changedPatchFaces[nChanged] = patchFacei;
            changedPatchFacesInfo[nChanged] = allFaceInfo_[meshFacei];
            ++nChanged;
        }
    }

    return nChanged;
}


template<class Type, class TrackingData>
void Foam::FaceCellWave<Type, TrackingData>::leaveDomain
(
    const polyPatch& patch,
    const label nFaces,
    const labelUList& faceLabels,
    List<Type>& faceInfo
) const
{
    // Handle leaving domain. Implementation referred to Type

    const vectorField& fc = mesh_.faceCentres();

    for (label i = 0; i < nFaces; ++i)
    {
        const label patchFacei = faceLabels[i];
        const label meshFacei = patch.start() + patchFacei;

        faceInfo[i].leaveDomain(mesh_, patch, patchFacei, fc[meshFacei], td_);
    }
}


template<class Type, class TrackingData>
void Foam::FaceCellWave<Type, TrackingData>::enterDomain
(
    const polyPatch& patch,
    const label nFaces,
    const labelUList& faceLabels,
    List<Type>& faceInfo
) const
{
    // Handle entering domain. Implementation referred to Type

    const vectorField& fc = mesh_.faceCentres();

    for (label i = 0; i < nFaces; ++i)
    {
        const label patchFacei = faceLabels[i];
        const label meshFacei = patch.start() + patchFacei;

        faceInfo[i].enterDomain(mesh_, patch, patchFacei, fc[meshFacei], td_);
    }
}


template<class Type, class TrackingData>
void Foam::FaceCellWave<Type, TrackingData>::transform
(
    const tensorField& rotTensor,
    const label nFaces,
    List<Type>& faceInfo
)
{
    // Transform. Implementation referred to Type

    if (rotTensor.size() == 1)
    {
        const tensor& T = rotTensor[0];

        for (label facei = 0; facei < nFaces; ++facei)
        {
            faceInfo[facei].transform(mesh_, T, td_);
        }
    }
    else
    {
        for (label facei = 0; facei < nFaces; ++facei)
        {
            faceInfo[facei].transform(mesh_, rotTensor[facei], td_);
        }
    }
}


template<class Type, class TrackingData>
void Foam::FaceCellWave<Type, TrackingData>::offset
(
    const polyPatch&,
    const label cycOffset,
    const label nFaces,
    labelList& faces
)
{
    // Offset mesh face.
    // Used for transferring from one cyclic half to the other.

    for (label facei = 0; facei < nFaces; ++facei)
    {
        faces[facei] += cycOffset;
    }
}


template<class Type, class TrackingData>
void Foam::FaceCellWave<Type, TrackingData>::handleProcPatches()
{
    // Transfer all the information to/from neighbouring processors

    const globalMeshData& pData = mesh_.globalData();

    // Which patches are processor patches
    const labelList& procPatches = pData.processorPatches();

    // Send all

    PstreamBuffers pBufs(Pstream::commsTypes::nonBlocking);

    for (const label patchi : procPatches)
    {
        const processorPolyPatch& procPatch =
            refCast<const processorPolyPatch>(mesh_.boundaryMesh()[patchi]);

        // Allocate buffers
        label nSendFaces;
        labelList sendFaces(procPatch.size());
        List<Type> sendFacesInfo(procPatch.size());

        // Determine which faces changed on current patch
        nSendFaces = getChangedPatchFaces
        (
            procPatch,
            0,
            procPatch.size(),
            sendFaces,
            sendFacesInfo
        );

        // Adapt wallInfo for leaving domain
        leaveDomain
        (
            procPatch,
            nSendFaces,
            sendFaces,
            sendFacesInfo
        );

        if (debug & 2)
        {
            Pout<< " Processor patch " << patchi << ' ' << procPatch.name()
                << " communicating with " << procPatch.neighbProcNo()
                << "  Sending:" << nSendFaces
                << endl;
        }

        UOPstream toNeighbour(procPatch.neighbProcNo(), pBufs);
        //writeFaces(nSendFaces, sendFaces, sendFacesInfo, toNeighbour);
        toNeighbour
            << SubList<label>(sendFaces, nSendFaces)
            << SubList<Type>(sendFacesInfo, nSendFaces);
    }

    pBufs.finishedSends();

    // Receive all

    for (const label patchi : procPatches)
    {
        const processorPolyPatch& procPatch =
            refCast<const processorPolyPatch>(mesh_.boundaryMesh()[patchi]);

        // Allocate buffers
        labelList receiveFaces;
        List<Type> receiveFacesInfo;

        {
            UIPstream fromNeighbour(procPatch.neighbProcNo(), pBufs);
            fromNeighbour >> receiveFaces >> receiveFacesInfo;
        }

        if (debug & 2)
        {
            Pout<< " Processor patch " << patchi << ' ' << procPatch.name()
                << " communicating with " << procPatch.neighbProcNo()
                << "  Receiving:" << receiveFaces.size()
                << endl;
        }

        // Apply transform to received data for non-parallel planes
        if (!procPatch.parallel())
        {
            transform
            (
                procPatch.forwardT(),
                receiveFaces.size(),
                receiveFacesInfo
            );
        }

        // Adapt wallInfo for entering domain
        enterDomain
        (
            procPatch,
            receiveFaces.size(),
            receiveFaces,
            receiveFacesInfo
        );

        // Merge received info
        mergeFaceInfo
        (
            procPatch,
            receiveFaces.size(),
            receiveFaces,
            receiveFacesInfo
        );
    }
}


template<class Type, class TrackingData>
void Foam::FaceCellWave<Type, TrackingData>::handleCyclicPatches()
{
    // Transfer information across cyclic halves.

    for (const polyPatch& patch : mesh_.boundaryMesh())
    {
        const cyclicPolyPatch* cpp = isA<cyclicPolyPatch>(patch);

        if (cpp)
        {
            const auto& cycPatch = *cpp;
            const auto& nbrPatch = cycPatch.neighbPatch();

            // Allocate buffers
            label nReceiveFaces;
            labelList receiveFaces(patch.size());
            List<Type> receiveFacesInfo(patch.size());

            // Determine which faces changed
            nReceiveFaces = getChangedPatchFaces
            (
                nbrPatch,
                0,
                nbrPatch.size(),
                receiveFaces,
                receiveFacesInfo
            );

            // Adapt wallInfo for leaving domain
            leaveDomain
            (
                nbrPatch,
                nReceiveFaces,
                receiveFaces,
                receiveFacesInfo
            );

            if (!cycPatch.parallel())
            {
                // Received data from other half
                transform
                (
                    cycPatch.forwardT(),
                    nReceiveFaces,
                    receiveFacesInfo
                );
            }

            if (debug & 2)
            {
                Pout<< " Cyclic patch "
                    << cycPatch.index() << ' ' << cycPatch.name()
                    << "  Changed : " << nReceiveFaces
                    << endl;
            }

            // Half2: Adapt wallInfo for entering domain
            enterDomain
            (
                cycPatch,
                nReceiveFaces,
                receiveFaces,
                receiveFacesInfo
            );

            // Merge into global storage
            mergeFaceInfo
            (
                cycPatch,
                nReceiveFaces,
                receiveFaces,
                receiveFacesInfo
            );

            if (debug)
            {
                checkCyclic(cycPatch);
            }
        }
    }
}


template<class Type, class TrackingData>
void Foam::FaceCellWave<Type, TrackingData>::handleAMICyclicPatches()
{
    // Transfer information across cyclicAMI halves.

    for (const polyPatch& patch : mesh_.boundaryMesh())
    {
        const cyclicAMIPolyPatch* cpp = isA<cyclicAMIPolyPatch>(patch);

        if (cpp)
        {
            const auto& cycPatch = *cpp;
            const auto& nbrPatch = cycPatch.neighbPatch();

            List<Type> receiveInfo;

            {
                // Get nbrPatch data (so not just changed faces)
                typename List<Type>::subList sendInfo
                (
                    nbrPatch.patchSlice
                    (
                        allFaceInfo_
                    )
                );

                if (!nbrPatch.parallel() || nbrPatch.separated())
                {
                    // Adapt sendInfo for leaving domain
                    const vectorField::subField fc = nbrPatch.faceCentres();
                    forAll(sendInfo, i)
                    {
                        sendInfo[i].leaveDomain(mesh_, nbrPatch, i, fc[i], td_);
                    }
                }

                // Transfer sendInfo to cycPatch
                combine<Type, TrackingData> cmb(*this, cycPatch);

                if (cycPatch.applyLowWeightCorrection())
                {
                    List<Type> defVals
                    (
                        cycPatch.patchInternalList(allCellInfo_)
                    );

                    cycPatch.interpolate(sendInfo, cmb, receiveInfo, defVals);
                }
                else
                {
                    cycPatch.interpolate(sendInfo, cmb, receiveInfo);
                }
            }

            // Apply transform to received data for non-parallel planes
            if (!cycPatch.parallel())
            {
                transform
                (
                    cycPatch.forwardT(),
                    receiveInfo.size(),
                    receiveInfo
                );
            }

            if (!cycPatch.parallel() || cycPatch.separated())
            {
                // Adapt receiveInfo for entering domain
                const vectorField::subField fc = cycPatch.faceCentres();
                forAll(receiveInfo, i)
                {
                    receiveInfo[i].enterDomain(mesh_, cycPatch, i, fc[i], td_);
                }
            }

            // Merge into global storage
            forAll(receiveInfo, i)
            {
                const label meshFacei = cycPatch.start()+i;

                const Type& newInfo = receiveInfo[i];

                Type& currInfo = allFaceInfo_[meshFacei];

                if (newInfo.valid(td_) && !currInfo.equal(newInfo, td_))
                {
                    updateFace
                    (
                        meshFacei,
                        newInfo,
                        propagationTol_,
                        currInfo
                    );
                }
            }
        }
    }
}


template<class Type, class TrackingData>
void Foam::FaceCellWave<Type, TrackingData>::handleExplicitConnections()
{
    changedBaffles_.clear();

    // Collect all/any changed information touching a baffle
    for (const labelPair& baffle : explicitConnections_)
    {
        const label f0 = baffle.first();
        const label f1 = baffle.second();

        if (changedFace_.test(f0))
        {
            // f0 changed. Update information on f1.
            changedBaffles_.append(taggedInfoType(f1, allFaceInfo_[f0]));
        }

        if (changedFace_.test(f1))
        {
            // f1 changed. Update information on f0.
            changedBaffles_.append(taggedInfoType(f0, allFaceInfo_[f1]));
        }
    }


    // Update other side with changed information

    for (const taggedInfoType& updated : changedBaffles_)
    {
        const label tgtFace = updated.first;
        const Type& newInfo = updated.second;

        Type& currInfo = allFaceInfo_[tgtFace];

        if (!currInfo.equal(newInfo, td_))
        {
            updateFace
            (
                tgtFace,
                newInfo,
                propagationTol_,
                currInfo
            );
        }
    }

    changedBaffles_.clear();
}


// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //

template<class Type, class TrackingData>
Foam::FaceCellWave<Type, TrackingData>::FaceCellWave
(
    const polyMesh& mesh,
    UList<Type>& allFaceInfo,
    UList<Type>& allCellInfo,
    TrackingData& td
)
:
    mesh_(mesh),
    explicitConnections_(),
    allFaceInfo_(allFaceInfo),
    allCellInfo_(allCellInfo),
    td_(td),
    changedFace_(mesh_.nFaces(), false),
    changedCell_(mesh_.nCells(), false),
    changedFaces_(mesh_.nFaces()),
    changedCells_(mesh_.nCells()),
    changedBaffles_(2*explicitConnections_.size()),
    hasCyclicPatches_(hasPatch<cyclicPolyPatch>()),
    hasCyclicAMIPatches_
    (
        returnReduce(hasPatch<cyclicAMIPolyPatch>(), orOp<bool>())
    ),
    nEvals_(0),
    nUnvisitedCells_(mesh_.nCells()),
    nUnvisitedFaces_(mesh_.nFaces())
{
    if
    (
        allFaceInfo.size() != mesh_.nFaces()
     || allCellInfo.size() != mesh_.nCells()
    )
    {
        FatalErrorInFunction
            << "face and cell storage not the size of mesh faces, cells:" << nl
            << "    allFaceInfo   :" << allFaceInfo.size() << nl
            << "    mesh_.nFaces():" << mesh_.nFaces() << nl
            << "    allCellInfo   :" << allCellInfo.size() << nl
            << "    mesh_.nCells():" << mesh_.nCells() << endl
            << exit(FatalError);
    }
}


template<class Type, class TrackingData>
Foam::FaceCellWave<Type, TrackingData>::FaceCellWave
(
    const polyMesh& mesh,
    const labelUList& changedFaces,
    const List<Type>& changedFacesInfo,
    UList<Type>& allFaceInfo,
    UList<Type>& allCellInfo,
    const label maxIter,
    TrackingData& td
)
:
    mesh_(mesh),
    explicitConnections_(),
    allFaceInfo_(allFaceInfo),
    allCellInfo_(allCellInfo),
    td_(td),
    changedFace_(mesh_.nFaces(), false),
    changedCell_(mesh_.nCells(), false),
    changedFaces_(mesh_.nFaces()),
    changedCells_(mesh_.nCells()),
    changedBaffles_(2*explicitConnections_.size()),
    hasCyclicPatches_(hasPatch<cyclicPolyPatch>()),
    hasCyclicAMIPatches_
    (
        returnReduce(hasPatch<cyclicAMIPolyPatch>(), orOp<bool>())
    ),
    nEvals_(0),
    nUnvisitedCells_(mesh_.nCells()),
    nUnvisitedFaces_(mesh_.nFaces())
{
    if
    (
        allFaceInfo.size() != mesh_.nFaces()
     || allCellInfo.size() != mesh_.nCells()
    )
    {
        FatalErrorInFunction
            << "face and cell storage not the size of mesh faces, cells:" << nl
            << "    allFaceInfo   :" << allFaceInfo.size() << nl
            << "    mesh_.nFaces():" << mesh_.nFaces() << nl
            << "    allCellInfo   :" << allCellInfo.size() << nl
            << "    mesh_.nCells():" << mesh_.nCells() << endl
            << exit(FatalError);
    }

    // Copy initial changed faces data
    setFaceInfo(changedFaces, changedFacesInfo);

    // Iterate until nothing changes
    const label iter = iterate(maxIter);

    if ((maxIter > 0) && (iter >= maxIter))
    {
        FatalErrorInFunction
            << "Maximum number of iterations reached. Increase maxIter." << nl
            << "    maxIter:" << maxIter << nl
            << "    nChangedCells:" << changedCells_.size() << nl
            << "    nChangedFaces:" << changedFaces_.size() << endl
            << exit(FatalError);
    }
}


template<class Type, class TrackingData>
Foam::FaceCellWave<Type, TrackingData>::FaceCellWave
(
    const polyMesh& mesh,
    const labelPairList& explicitConnections,
    const bool handleCyclicAMI,
    const labelUList& changedFaces,
    const List<Type>& changedFacesInfo,
    UList<Type>& allFaceInfo,
    UList<Type>& allCellInfo,
    const label maxIter,
    TrackingData& td
)
:
    mesh_(mesh),
    explicitConnections_(explicitConnections),
    allFaceInfo_(allFaceInfo),
    allCellInfo_(allCellInfo),
    td_(td),
    changedFace_(mesh_.nFaces(), false),
    changedCell_(mesh_.nCells(), false),
    changedFaces_(mesh_.nFaces()),
    changedCells_(mesh_.nCells()),
    changedBaffles_(2*explicitConnections_.size()),
    hasCyclicPatches_(hasPatch<cyclicPolyPatch>()),
    hasCyclicAMIPatches_
    (
        handleCyclicAMI
     && returnReduce(hasPatch<cyclicAMIPolyPatch>(), orOp<bool>())
    ),
    nEvals_(0),
    nUnvisitedCells_(mesh_.nCells()),
    nUnvisitedFaces_(mesh_.nFaces())
{
    if
    (
        allFaceInfo.size() != mesh_.nFaces()
     || allCellInfo.size() != mesh_.nCells()
    )
    {
        FatalErrorInFunction
            << "face and cell storage not the size of mesh faces, cells:" << nl
            << "    allFaceInfo   :" << allFaceInfo.size() << nl
            << "    mesh_.nFaces():" << mesh_.nFaces() << nl
            << "    allCellInfo   :" << allCellInfo.size() << nl
            << "    mesh_.nCells():" << mesh_.nCells() << endl
            << exit(FatalError);
    }

    // Copy initial changed faces data
    setFaceInfo(changedFaces, changedFacesInfo);

    // Iterate until nothing changes
    const label iter = iterate(maxIter);

    if ((maxIter > 0) && (iter >= maxIter))
    {
        FatalErrorInFunction
            << "Maximum number of iterations reached. Increase maxIter." << nl
            << "    maxIter:" << maxIter << nl
            << "    nChangedCells:" << changedCells_.size() << nl
            << "    nChangedFaces:" << changedFaces_.size() << endl
            << exit(FatalError);
    }
}


// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //

template<class Type, class TrackingData>
Foam::label Foam::FaceCellWave<Type, TrackingData>::nUnvisitedCells() const
{
    return nUnvisitedCells_;
}


template<class Type, class TrackingData>
Foam::label Foam::FaceCellWave<Type, TrackingData>::nUnvisitedFaces() const
{
    return nUnvisitedFaces_;
}


template<class Type, class TrackingData>
Foam::label Foam::FaceCellWave<Type, TrackingData>::faceToCell()
{
    // Propagate face to cell

    const labelList& owner = mesh_.faceOwner();
    const labelList& neighbour = mesh_.faceNeighbour();
    const label nInternalFaces = mesh_.nInternalFaces();

    for (const label facei : changedFaces_)
    {
        if (!changedFace_.test(facei))
        {
            FatalErrorInFunction
                << "Face " << facei
                << " not marked as having been changed"
                << abort(FatalError);
        }

        const Type& newInfo = allFaceInfo_[facei];

        // Evaluate all connected cells

        // Owner
        {
            const label celli = owner[facei];
            Type& currInfo = allCellInfo_[celli];

            if (!currInfo.equal(newInfo, td_))
            {
                updateCell
                (
                    celli,
                    facei,
                    newInfo,
                    propagationTol_,
                    currInfo
                );
            }
        }

        // Neighbour.
        if (facei < nInternalFaces)
        {
            const label celli = neighbour[facei];
            Type& currInfo = allCellInfo_[celli];

            if (!currInfo.equal(newInfo, td_))
            {
                updateCell
                (
                    celli,
                    facei,
                    newInfo,
                    propagationTol_,
                    currInfo
                );
            }
        }

        // Reset status of face
        changedFace_.unset(facei);
    }

    // Handled all changed faces by now
    changedFaces_.clear();

    if (debug & 2)
    {
        Pout<< " Changed cells            : " << changedCells_.size() << endl;
    }

    // Number of changedCells over all procs
    return returnReduce(changedCells_.size(), sumOp<label>());
}


template<class Type, class TrackingData>
Foam::label Foam::FaceCellWave<Type, TrackingData>::cellToFace()
{
    // Propagate cell to face

    const cellList& cells = mesh_.cells();

    for (const label celli : changedCells_)
    {
        if (!changedCell_.test(celli))
        {
            FatalErrorInFunction
                << "Cell " << celli << " not marked as having been changed"
                << abort(FatalError);
        }

        const Type& newInfo = allCellInfo_[celli];

        // Evaluate all connected faces

        const labelList& faceLabels = cells[celli];
        for (const label facei : faceLabels)
        {
            Type& currInfo = allFaceInfo_[facei];

            if (!currInfo.equal(newInfo, td_))
            {
                updateFace
                (
                    facei,
                    celli,
                    newInfo,
                    propagationTol_,
                    currInfo
                );
            }
        }

        // Reset status of cell
        changedCell_.unset(celli);
    }

    // Handled all changed cells by now
    changedCells_.clear();


    // Transfer across any explicitly provided internal connections
    handleExplicitConnections();

    if (hasCyclicPatches_)
    {
        handleCyclicPatches();
    }

    if (hasCyclicAMIPatches_)
    {
        handleAMICyclicPatches();
    }

    if (Pstream::parRun())
    {
        handleProcPatches();
    }

    if (debug & 2)
    {
        Pout<< " Changed faces            : " << changedFaces_.size() << endl;
    }


    // Number of changedFaces over all procs
    return returnReduce(changedFaces_.size(), sumOp<label>());
}


// Iterate
template<class Type, class TrackingData>
Foam::label Foam::FaceCellWave<Type, TrackingData>::iterate(const label maxIter)
{
    if (maxIter < 0)
    {
        return 0;
    }

    if (hasCyclicPatches_)
    {
        handleCyclicPatches();
    }

    if (hasCyclicAMIPatches_)
    {
        handleAMICyclicPatches();
    }

    if (Pstream::parRun())
    {
        handleProcPatches();
    }

    label iter = 0;

    for (/*nil*/; iter < maxIter; ++iter)
    {
        if (debug)
        {
            Info<< " Iteration " << iter << endl;
        }

        nEvals_ = 0;
        const label nCells = faceToCell();
        const label nFaces = nCells ? cellToFace() : 0;

        if (debug)
        {
            Info<< " Total evaluations     : "
                << nEvals_ << nl
                << " Changed cells / faces : "
                << nCells << " / " << nFaces << nl
                << " Pending cells / faces : "
                << nUnvisitedCells_ << " / " << nUnvisitedFaces_ << nl;
        }

        if (!nCells || !nFaces)
        {
            break;
        }
    }

    return iter;
}


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