/*---------------------------------------------------------------------------*\
  =========                 |
  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
   \\    /   O peration     |
    \\  /    A nd           | Copyright (C) 1991-2005 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 2 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, write to the Free Software Foundation,
    Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

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

#include "mergePolyMesh.H"
#include "Time.H"
#include "processorPolyPatch.H"
#include "polyTopoChanger.H"
#include "mapPolyMesh.H"

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

namespace Foam
{
    defineTypeNameAndDebug(mergePolyMesh, 1);
}


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

Foam::label Foam::mergePolyMesh::patchIndex(const polyPatch& p)
{
    // Find the patch name on the list.  If the patch is already there
    // and patch types match, return index
    word pType = p.type();
    if (pType == processorPolyPatch::typeName)
    {
        pType = polyPatch::typeName;
    }

    const word& pName = p.name();

    bool nameFound = false;

    forAll (patchNames_, patchI)
    {
        if (patchNames_[patchI] == pName)
        {
            if (patchTypes_[patchI] == pType)
            {
                // Found name and types match
                return patchI;
            }
            else
            {
                // Found the name, but type is different
                nameFound = true;
            }
        }
    }

    // Patch not found.  Append to the list
    patchTypes_.append(pType);

    if (nameFound)
    {
        // Duplicate name is not allowed.  Create a composite name from the
        // patch name and case name
        const word& caseName = p.boundaryMesh().mesh().time().caseName();

        patchNames_.append(pName + "_" + caseName);

        Info<< "label patchIndex(const polyPatch& p) : "
            << "Patch " << p.index() << " named "
            << pName << " in mesh " << caseName
            << " already exists, but patch types "
            << " do not match.\nCreating a composite name as "
            << patchNames_[patchNames_.size() - 1] << endl;
    }
    else
    {
        patchNames_.append(pName);
    }

    return patchNames_.size() - 1;
}


Foam::label Foam::mergePolyMesh::zoneIndex
(
    DynamicList<word>& names,
    const word& curName
)
{
    forAll (names, zoneI)
    {
        if (names[zoneI] == curName)
        {
            return zoneI;
        }
    }

    // Not found.  Add new name to the list
    names.append(curName);

    return names.size() - 1;
}


void Foam::mergePolyMesh::insertPatchAndZones()
{
    // Insert the original patches into the list
    wordList curPatchTypes = boundaryMesh().types();
    wordList curPatchNames = boundaryMesh().names();

    forAll (curPatchTypes, patchI)
    {
        const word& patchType = curPatchTypes[patchI];

        if (patchType == processorPolyPatch::typeName)
        {
            patchTypes_.append(polyPatch::typeName);
        }
        else
        {
            patchTypes_.append(patchType);
        }
        patchNames_.append(curPatchNames[patchI]);
    }

    // Insert point, face and cell zones into the list

    // Point zones
    wordList curPointZoneNames = pointZones().names();
    if (curPointZoneNames.size() > 0)
    {
        pointZoneNames_.setSize(2*curPointZoneNames.size());
    }

    forAll (curPointZoneNames, zoneI)
    {
        pointZoneNames_.append(curPointZoneNames[zoneI]);
    }

    // Face zones
    wordList curFaceZoneNames = faceZones().names();

    if (curFaceZoneNames.size() > 0)
    {
        faceZoneNames_.setSize(2*curFaceZoneNames.size());
    }
    forAll (curFaceZoneNames, zoneI)
    {
        faceZoneNames_.append(curFaceZoneNames[zoneI]);
    }

    // Cell zones
    wordList curCellZoneNames = cellZones().names();

    if (curCellZoneNames.size() > 0)
    {
        cellZoneNames_.setSize(2*curCellZoneNames.size());
    }
    forAll (curCellZoneNames, zoneI)
    {
        cellZoneNames_.append(curCellZoneNames[zoneI]);
    }
}


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

Foam::mergePolyMesh::mergePolyMesh(const IOobject& io)
:
    polyMesh(io),
    meshMod_(*this),
    patchTypes_(2*boundaryMesh().size()),
    patchNames_(2*boundaryMesh().size()),
    pointZoneNames_(),
    faceZoneNames_(),
    cellZoneNames_()
{
    // Insert the original patches into the list
    insertPatchAndZones();
}


Foam::mergePolyMesh::mergePolyMesh
(
    const Time& db,
    const polyMesh& mesh,
    IOobject::writeOption w
)
:
    polyMesh
    (
        IOobject
        (
            polyMesh::defaultRegion,
            db.timeName(),
            db,
            IOobject::NO_READ,
            w
        ),
        mesh.points(),
        mesh.faces(),
        mesh.cells()
    ),
    meshMod_(*this),
    patchTypes_(2*mesh.boundaryMesh().size()),
    patchNames_(2*mesh.boundaryMesh().size()),
    pointZoneNames_(),
    faceZoneNames_(),
    cellZoneNames_()
{
    // Add patches.
    Info<< "Adding patches from input mesh\n" << endl;

    const polyBoundaryMesh& patches = mesh.boundaryMesh();

    List<polyPatch*> masterPatches(patches.size());

    forAll(patches, patchI)
    {
        masterPatches[patchI] = patches[patchI].clone(boundaryMesh()).ptr();
    }

    addPatches(masterPatches);

    // Insert all info
    insertPatchAndZones();
}

// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //


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

void Foam::mergePolyMesh::addMesh(const polyMesh& m)
{
    // Add all the points, faces and cells of the new mesh

    // Add points

    label zoneID = -1;

    const pointField& p = m.allPoints();
    labelList renumberPoints(p.size());

    const pointZoneMesh& pz = m.pointZones();
    labelList pointZoneIndices(pz.size());

    forAll (pz, zoneI)
    {
        pointZoneIndices[zoneI] = zoneIndex(pointZoneNames_, pz[zoneI].name());
    }

    forAll (p, pointI)
    {
        // Grab zone ID.  If a point is not in a zone, it will return -1
        zoneID = pz.whichZone(pointI);

        if (zoneID > 0)
        {
            // Translate zone ID into the new index
            zoneID = pointZoneIndices[zoneID];
        }

        renumberPoints[pointI] =
            meshMod_.setAction
            (
                polyAddPoint
                (
                    p[pointI],            // Point to add
                    -1,                   // Master point (straight addition)
                    zoneID,               // Zone for point
                    pointI < m.nPoints()  // Is in cell?
                )
            );
    }

    // Add cells

    const cellList& c = m.cells();
    labelList renumberCells(c.size());

    const cellZoneMesh& cz = m.cellZones();
    labelList cellZoneIndices(cz.size());

    forAll (cz, zoneI)
    {
        cellZoneIndices[zoneI] = zoneIndex(cellZoneNames_, cz[zoneI].name());
    }

    forAll (c, cellI)
    {
        // Grab zone ID.  If a cell is not in a zone, it will return -1
        zoneID = cz.whichZone(cellI);

        if (zoneID > 0)
        {
            // Translate zone ID into the new index
            zoneID = cellZoneIndices[zoneID];
        }

        renumberCells[cellI] =
            meshMod_.setAction
            (
                polyAddCell
                (
                    -1,                   // Master point
                    -1,                   // Master edge
                    -1,                   // Master face
                    -1,                   // Master cell
                    zoneID                // Zone for cell
                )
            );
    }

    // Add faces
    const polyBoundaryMesh& bm = m.boundaryMesh();

    // Gather the patch indices
    labelList patchIndices(bm.size());

    forAll (patchIndices, patchI)
    {
        patchIndices[patchI] = patchIndex(bm[patchI]);
    }

    const faceZoneMesh& fz = m.faceZones();
    labelList faceZoneIndices(fz.size());

    forAll (fz, zoneI)
    {
        faceZoneIndices[zoneI] = zoneIndex(faceZoneNames_, fz[zoneI].name());
    }

    const faceList& f = m.allFaces();
    labelList renumberFaces(f.size());

    const labelList& own = m.allOwner();
    const labelList& nei = m.allNeighbour();

    label newOwn, newNei, newPatch, newZone;
    bool newZoneFlip;

    forAll (f, faceI)
    {
        const face& curFace = f[faceI];

        face newFace(curFace.size());

        forAll (curFace, pointI)
        {
            newFace[pointI] = renumberPoints[curFace[pointI]];
        }

        if (debug)
        {
            // Check that the face is valid
            if (min(newFace) < 0)
            {
                FatalErrorIn("void mergePolyMesh::addMesh(const polyMesh&)")
                    << "Error in point mapping for face " << faceI
                    << ".  Old face: " << curFace << " New face: " << newFace
                    << abort(FatalError);
            }
        }

        newOwn = own[faceI];
        if (newOwn > -1) newOwn = renumberCells[newOwn];

        newNei = nei[faceI];
        if (newNei > -1) newNei = renumberCells[newNei];

        if (faceI < m.nInternalFaces() || faceI >= m.nFaces())
        {
            newPatch = -1;
        }
        else
        {
            newPatch = patchIndices[bm.whichPatch(faceI)];
        }

        newZone = fz.whichZone(faceI);
        newZoneFlip = false;

        if (newZone > -1)
        {
            newZoneFlip = fz[newZone].flipMap()[fz[newZone].whichFace(faceI)];

            // Grab the new zone
            newZone = faceZoneIndices[newZone];
        }

        renumberFaces[faceI] =
            meshMod_.setAction
            (
                polyAddFace
                (
                    newFace,
                    newOwn,
                    newNei,
                    -1,
                    -1,
                    -1,
                    false,
                    newPatch,
                    newZone,
                    newZoneFlip
                )
            );
    }
        
}


void Foam::mergePolyMesh::merge()
{
//    Info<< "patch names: " << patchNames_ << nl
//        << "patch types: " << patchTypes_ << nl
//        << "point zone names: " << pointZoneNames_ << nl
//        << "face zone names: " << faceZoneNames_ << nl
//        << "cell zone names: " << cellZoneNames_ << endl;

    // Add the patches if necessary
    if (patchNames_.size() != boundaryMesh().size())
    {
        List<polyPatch*> newPatches(patchNames_.size());

        const polyBoundaryMesh& oldPatches = boundaryMesh();

        // Note.  Re-using counter in two for loops
        label patchI = 0;

        for (patchI = 0; patchI < oldPatches.size(); patchI++)
        {
            newPatches[patchI] = oldPatches[patchI].clone(boundaryMesh()).ptr();
        }


        label endOfLastPatch =
            oldPatches[patchI - 1].start() + oldPatches[patchI - 1].size();

        for (; patchI < patchNames_.size(); patchI++)
        {
            // Add a patch
            newPatches[patchI] =
            (
                polyPatch::New
                (
                    patchTypes_[patchI],
                    patchNames_[patchI],
                    0,
                    endOfLastPatch,
                    patchI,
                    oldPatches
                ).ptr()
            );
        }

        removeBoundary();
        addPatches(newPatches);
    }

    // Add the zones if necessary
    if
    (
        pointZoneNames_.size() != pointZones().size()
     || faceZoneNames_.size() != faceZones().size()
     || cellZoneNames_.size() != cellZones().size()
    )
    {

    }

    polyTopoChanger::changeMesh(*this, meshMod_);

    // Clear topo change for the next operation
    meshMod_.clear();
}


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