/*---------------------------------------------------------------------------*\
  =========                 |
  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
   \\    /   O peration     |
    \\  /    A nd           | Copyright (C) 1991-2010 OpenCFD Ltd.
     \\/     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/>.

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

#include "domainDecomposition.H"
#include <decompositionMethods/decompositionMethod.H>
#include <OSspecific/cpuTime.H>
#include <OpenFOAM/cyclicPolyPatch.H>
#include <meshTools/cellSet.H>
#include <meshTools/regionSplit.H>

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

void domainDecomposition::distributeCells()
{
    Info<< "\nCalculating distribution of cells" << endl;

    cpuTime decompositionTime;


    // See if any faces need to have owner and neighbour on same processor
    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    labelHashSet sameProcFaces;

    if (decompositionDict_.found("preservePatches"))
    {
        wordList pNames(decompositionDict_.lookup("preservePatches"));

        Info<< "Keeping owner of faces in patches " << pNames
            << " on same processor. This only makes sense for cyclics." << endl;

        const polyBoundaryMesh& patches = boundaryMesh();

        forAll(pNames, i)
        {
            label patchI = patches.findPatchID(pNames[i]);

            if (patchI == -1)
            {
                FatalErrorIn("domainDecomposition::distributeCells()")
                    << "Unknown preservePatch " << pNames[i]
                    << endl << "Valid patches are " << patches.names()
                    << exit(FatalError);
            }

            const polyPatch& pp = patches[patchI];

            forAll(pp, i)
            {
                sameProcFaces.insert(pp.start() + i);
            }
        }
    }
    if (decompositionDict_.found("preserveFaceZones"))
    {
        wordList zNames(decompositionDict_.lookup("preserveFaceZones"));

        Info<< "Keeping owner and neighbour of faces in zones " << zNames
            << " on same processor" << endl;

        const faceZoneMesh& fZones = faceZones();

        forAll(zNames, i)
        {
            label zoneI = fZones.findZoneID(zNames[i]);

            if (zoneI == -1)
            {
                FatalErrorIn("domainDecomposition::distributeCells()")
                    << "Unknown preserveFaceZone " << zNames[i]
                    << endl << "Valid faceZones are " << fZones.names()
                    << exit(FatalError);
            }

            const faceZone& fz = fZones[zoneI];

            forAll(fz, i)
            {
                sameProcFaces.insert(fz[i]);
            }
        }
    }


    // Construct decomposition method and either do decomposition on
    // cell centres or on agglomeration


    autoPtr<decompositionMethod> decomposePtr = decompositionMethod::New
    (
        decompositionDict_,
        *this
    );

    if (sameProcFaces.empty())
    {
        if (decompositionDict_.found("weightField"))
        {
            word weightName = decompositionDict_.lookup("weightField");

            volScalarField weights
            (
                IOobject
                (
                    weightName,
                    time().timeName(),
                    *this,
                    IOobject::MUST_READ,
                    IOobject::NO_WRITE
                ),
                *this
            );

            cellToProc_ = decomposePtr().decompose
            (
                cellCentres(),
                weights.internalField()
            );
        }
        else
        {
            cellToProc_ = decomposePtr().decompose(cellCentres());
        }
    }
    else
    {
        Info<< "Selected " << sameProcFaces.size()
            << " faces whose owner and neighbour cell should be kept on the"
            << " same processor" << endl;

        if (decompositionDict_.found("weightField"))
        {
            WarningIn("void domainDecomposition::distributeCells()")
                << "weightField not supported when agglomerated "
                << "decomposition required" << endl;
        }

        // Faces where owner and neighbour are not 'connected' (= all except
        // sameProcFaces)
        boolList blockedFace(nFaces(), true);

        forAllConstIter(labelHashSet, sameProcFaces, iter)
        {
            blockedFace[iter.key()] = false;
        }

        // Connect coupled boundary faces
        const polyBoundaryMesh& patches =  boundaryMesh();

        forAll(patches, patchI)
        {
            const polyPatch& pp = patches[patchI];

            if (pp.coupled())
            {
                forAll(pp, i)
                {
                    blockedFace[pp.start()+i] = false;
                }
            }
        }

        // Determine global regions, separated by blockedFaces
        regionSplit globalRegion(*this, blockedFace);


        // Determine region cell centres
        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        // This just takes the first cell in the region. Otherwise the problem
        // is with cyclics - if we'd average the region centre might be
        // somewhere in the middle of the domain which might not be anywhere
        // near any of the cells.

        const point greatPoint(GREAT, GREAT, GREAT);

        pointField regionCentres(globalRegion.nRegions(), greatPoint);

        forAll(globalRegion, cellI)
        {
            label regionI = globalRegion[cellI];

            if (regionCentres[regionI] == greatPoint)
            {
                regionCentres[regionI] = cellCentres()[cellI];
            }
        }

        // Do decomposition on agglomeration
        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        cellToProc_ = decomposePtr().decompose(globalRegion, regionCentres);
    }

    Info<< "\nFinished decomposition in "
        << decompositionTime.elapsedCpuTime()
        << " s" << endl;
}


// ************************ vim: set sw=4 sts=4 et: ************************ //
