/*---------------------------------------------------------------------------*\
  =========                 |
  \\      /  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

Description
    A multi-block mesh generator.

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

#include "Time.H"
#include "IOdictionary.H"
#include "IOPtrList.H"

#include "blockMesh.H"
#include "attachPolyTopoChanger.H"
#include "preservePatchTypes.H"
#include "emptyPolyPatch.H"
#include "cellSet.H"

#include "argList.H"
#include "OSspecific.H"
#include "OFstream.H"

#include "Pair.H"
#include "slidingInterface.H"

using namespace Foam;

// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
// Main program:

int main(int argc, char *argv[])
{
#   include "addOptions.H"
#   include "setRootCase.H"
#   include "createTime.H"
#   include "checkOptions.H"

    Info<< nl << "Reading block mesh description dictionary" << endl;

    IOobject meshDescriptionIOobject
    (
        "blockMeshDict",
        runTime.constant(),
        "polyMesh",
        runTime,
        IOobject::MUST_READ,
        IOobject::NO_WRITE,
        false
    );

    if (!meshDescriptionIOobject.headerOk())
    {
        meshDescriptionIOobject = IOobject
        (
            "meshDescription",
            runTime.constant(),
            "polyMesh",
            runTime,
            IOobject::MUST_READ,
            IOobject::NO_WRITE,
            false
        );
    }

    if (!meshDescriptionIOobject.headerOk())
    {
        meshDescriptionIOobject = IOobject
        (
            "meshDescription",
            runTime.constant(),
            "mesh",
            runTime,
            IOobject::MUST_READ,
            IOobject::NO_WRITE,
            false
        );
    }

    if (!meshDescriptionIOobject.headerOk())
    {
        FatalErrorIn(args.executable())
            << "Cannot find mesh description file " << nl
            << runTime.constant()/"polyMesh"/"blockMeshDict" << " or " << nl
            << runTime.constant()/"polyMesh"/"meshDescription" << " or " << nl
            << runTime.constant()/"mesh"/"meshDescription"
            << exit(FatalError);
    }

    IOdictionary meshDescription(meshDescriptionIOobject);

    Info<< nl << "Creating block mesh" << endl;

    blockMesh blocks(meshDescription);


    if (writeTopo)
    {
        word objMeshFile("blockTopology.obj");

        Info<< nl << "Dumping block structure as Lightwave obj format"
            << " to " << objMeshFile << endl;

        // Write mesh as edges.

        OFstream objStream(objMeshFile);

        blocks.writeTopology(objStream);

        Info<< nl << "end" << endl;

        return 0;
    }



    Info<< nl << "Creating mesh from block mesh" << endl;

    wordList patchNames = blocks.patchNames();
    wordList patchTypes = blocks.patchTypes();
    word defaultFacesType = emptyPolyPatch::typeName;
    wordList patchPhysicalTypes = blocks.patchPhysicalTypes();

    preservePatchTypes
    (
        runTime,
        runTime.constant(),
        polyMesh::meshSubDir,
        patchNames,
        patchTypes,
        defaultFacesType,
        patchPhysicalTypes
    );

    polyMesh mesh
    (
        IOobject
        (
            polyMesh::defaultRegion,
            runTime.constant(),
            runTime
        ),
        blocks.points(),
        blocks.cells(),
        blocks.patches(),
        patchNames,
        patchTypes,
        defaultFacesType,
        patchPhysicalTypes
    );


    // Read in a list of dictionaries for the merge patch pairs
    if (meshDescription.found("mergePatchPairs"))
    {
        Info<< nl << "Creating merge patch pairs" << nl << endl;

        List<Pair<word> > mergePatchPairs
        (
            meshDescription.lookup("mergePatchPairs")
        );

        if (mergePatchPairs.size() > 0)
        {
            // Create and add point and face zones and mesh modifiers
            List<pointZone*> pz(mergePatchPairs.size());
            List<faceZone*> fz(3*mergePatchPairs.size());
            List<cellZone*> cz(0);

            forAll (mergePatchPairs, pairI)
            {
                const word mergeName
                (
                    mergePatchPairs[pairI].first()
                  + mergePatchPairs[pairI].second()
                  + name(pairI)
                );

                pz[pairI] = new pointZone
                (
                    mergeName + "CutPointZone",
                    labelList(0),
                    0,
                    mesh.pointZones()
                );

                // Master patch
                const word masterPatchName(mergePatchPairs[pairI].first());
                const polyPatch& masterPatch =
                    mesh.boundaryMesh()
                    [
                        mesh.boundaryMesh().findPatchID(masterPatchName)
                    ];

                labelList isf(masterPatch.size());

                forAll (isf, i)
                {
                    isf[i] = masterPatch.start() + i;
                }

                fz[3*pairI] = new faceZone
                (
                    mergeName + "MasterZone",
                    isf,
                    boolList(masterPatch.size(), false),
                    0,
                    mesh.faceZones()
                );

                // Slave patch
                const word slavePatchName(mergePatchPairs[pairI].second());
                const polyPatch& slavePatch =
                    mesh.boundaryMesh()
                    [
                        mesh.boundaryMesh().findPatchID(slavePatchName)
                    ];

                labelList osf(slavePatch.size());

                forAll (osf, i)
                {
                    osf[i] = slavePatch.start() + i;
                }

                fz[3*pairI + 1] = new faceZone
                (
                    mergeName + "SlaveZone",
                    osf,
                    boolList(slavePatch.size(), false),
                    1,
                    mesh.faceZones()
                );

                // Add empty zone for cut faces
                fz[3*pairI + 2] = new faceZone
                (
                    mergeName + "CutFaceZone",
                    labelList(0),
                    boolList(0, false),
                    2,
                    mesh.faceZones()
                );
            }  // end of all merge pairs

            Info << "Adding point and face zones" << endl;
            mesh.addZones(pz, fz, cz);


            Info << "Creating attachPolyTopoChanger" << endl;
            attachPolyTopoChanger polyMeshAttacher(mesh);
            polyMeshAttacher.setSize(mergePatchPairs.size());

            forAll (mergePatchPairs, pairI)
            {
                const word mergeName
                (
                    mergePatchPairs[pairI].first()
                  + mergePatchPairs[pairI].second()
                  + name(pairI)
                );

                // Add the sliding interface mesh modifier
                polyMeshAttacher.hook
                (
                    new slidingInterface
                    (
                        "couple" + name(pairI),
                        pairI,
                        polyMeshAttacher,
                        mergeName + "MasterZone",
                        mergeName + "SlaveZone",
                        mergeName + "CutPointZone",
                        mergeName + "CutFaceZone",
                        mergePatchPairs[pairI].first(),
                        mergePatchPairs[pairI].second(),
                        slidingInterface::INTEGRAL, // always integral
                        intersection::VISIBLE
                    )
                );
            }

            polyMeshAttacher.attach(true);
        }
    }
    else
    {
        Info<< nl << "There are no merge patch pairs edges" << endl;
    }


    // Set any cellZones (note: cell labelling unaffected by above
    // mergePatchPairs)

    label nZones = blocks.numZonedBlocks();

    if (nZones > 0)
    {
        Info<< nl << "Adding cell zones" << endl;

        // Map from zoneName to cellZone index
        HashTable<label> zoneMap(nZones);

        // Cells per zone.
        List<DynamicList<label> > zoneCells(nZones);

        // Running cell counter
        label cellI = 0;

        // Largest zone so far
        label freeZoneI = 0;

        forAll(blocks, blockI)
        {
            const block& b = blocks[blockI];
            const labelListList& blockCells = b.cells();
            const word& zoneName = b.blockDef().zoneName();

            if (zoneName.size() > 0)
            {
                HashTable<label>::const_iterator iter = zoneMap.find(zoneName);

                label zoneI;

                if (iter == zoneMap.end())
                {
                    zoneI = freeZoneI++;

                    Info<< "    " << zoneI << '\t' << zoneName << endl;

                    zoneMap.insert(zoneName, zoneI);
                }
                else
                {
                    zoneI = iter();
                }

                forAll(blockCells, i)
                {
                    zoneCells[zoneI].append(cellI++);
                }
            }
            else
            {
                cellI += b.cells().size();
            }
        }


        List<cellZone*> cz(zoneMap.size());

        Info<< nl << "Writing cell zones as cellSets" << endl;

        forAllConstIter(HashTable<label>, zoneMap, iter)
        {
            label zoneI = iter();

            cz[zoneI]= new cellZone
            (
                iter.key(),
                zoneCells[zoneI].shrink(),
                zoneI,
                mesh.cellZones()
            );

            // Write as cellSet for ease of processing
            cellSet cset(mesh, iter.key(), zoneCells[zoneI].shrink());
            cset.write();
        }

        mesh.pointZones().setSize(0);
        mesh.faceZones().setSize(0);
        mesh.cellZones().setSize(0);
        mesh.addZones(List<pointZone*>(0), List<faceZone*>(0), cz);
    }

    // Set the precision of the points data to 10
    IOstream::defaultPrecision(10);

    Info << nl << "Writing polyMesh" << endl;
    mesh.removeFiles(mesh.instance());
    if (!mesh.write())
    {
        FatalErrorIn(args.executable())
            << "Failed writing polyMesh."
            << exit(FatalError);
    }

    Info<< nl << "end" << endl;
    return 0;
}


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