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

Description

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

#include "argList.H"
#include "label.H"
#include "labelList.H"
#include "Fstream.H"
#include "point.H"
#include "Time.H"
#include "fvMesh.H"
#include "router.H"
#include "processorPolyPatch.H"
#include "typeInfo.H"
#include "Gather.H"


using namespace Foam;

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

// Get list of my processor neighbours
labelList procNeighbours(const polyMesh& mesh)
{
    word procLabel = '[' + word(name(Pstream::myProcNo())) + "]-";

    label nNeighbours = 0;

    forAll(mesh.boundaryMesh(), patchi)
    {
        if (isA<processorPolyPatch>(mesh.boundaryMesh()[patchi]))
        {
            ++nNeighbours;
        }
    }

    labelList neighbours(nNeighbours);

    nNeighbours = 0;

    forAll(mesh.boundaryMesh(), patchi)
    {
        const auto* procPatch =
            isA<processorPolyPatch>(mesh.boundaryMesh()[patchi]);

        if (procPatch)
        {
            neighbours[nNeighbours++] = procPatch->neighbProcNo();
        }
    }

    return neighbours;
}


// Calculate some average position for mesh.
point meshCentre(const polyMesh& mesh)
{
    return average(mesh.points());
}


// Main program:


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

    word procLabel = '[' + word(name(Pstream::myProcNo())) + "]-";

    if (!Pstream::parRun())
    {
        FatalErrorInFunction
            << "Please run in parallel" << exit(FatalError);
    }

    // 'Gather' processor-processor topology
    Gather<labelList> connections
    (
        static_cast<const labelList&>(procNeighbours(mesh))
    );

    // Collect centres of individual meshes (for visualization only)
    Gather<point> meshCentres(meshCentre(mesh));

    if (!Pstream::master())
    {
        return 0;
    }


    //
    // At this point we have the connections between processors and the
    // processor-mesh centres.
    //

    Info<< "connections:" << connections << endl;
    Info<< "meshCentres:" << meshCentres << endl;


    //
    // Dump connections and meshCentres to OBJ file
    //

    fileName fName("decomposition.obj");

    Info<< "Writing decomposition to " << fName << endl;

    OFstream objFile(fName);

    // Write processors as single vertex in centre of mesh
    forAll(meshCentres, proci)
    {
        const point& pt = meshCentres[proci];

        objFile << "v " << pt.x() << ' ' << pt.y() << ' ' << pt.z() << endl;
    }
    // Write connections as lines between processors (duplicated)
    forAll(connections, proci)
    {
        const labelList& nbs = connections[proci];

        forAll(nbs, nbI)
        {
            objFile << "l " << proci + 1 << ' ' << nbs[nbI] + 1 << endl;
        }
    }


    //
    // Read paths to route from dictionary
    //

    IFstream dictFile("routerDict");

    dictionary routeDict(dictFile);

    labelListList paths(routeDict.lookup("paths"));



    //
    // Iterate over routing. Route as much as possible during each iteration
    // and stop if all paths have been routed. No special ordering to maximize
    // routing during one iteration.
    //

    boolList routeOk(paths.size(), false);
    label nOk = 0;

    label iter = 0;

    while (nOk < paths.size())
    {
        Info<< "Iteration:" << iter << endl;
        Info<< "---------------" << endl;


        // Dump to OBJ file

        fileName fName("route_" + name(iter) + ".obj");
        Info<< "Writing route to " << fName << endl;

        OFstream objFile(fName);

        forAll(meshCentres, proci)
        {
            const point& pt = meshCentres[proci];

            objFile << "v " << pt.x() << ' ' << pt.y() << ' ' << pt.z()
                    << endl;
        }


        // Router
        router cellRouter(connections, meshCentres);

        // Try to route as many paths possible during this iteration.
        forAll(paths, pathI)
        {
            if (!routeOk[pathI])
            {
                const labelList& path = paths[pathI];

                Info<< "Trying to route path " << pathI
                    << " nodes " << path << endl;

                routeOk[pathI] = cellRouter.route(path, -(pathI + 1));

                Info<< "Result of routing:" << routeOk[pathI] << endl;

                if (routeOk[pathI])
                {
                    nOk++;

                    // Dump route as lines.
                    labelList route(cellRouter.getRoute(-(pathI + 1)));

                    for (label elemI = 1; elemI < route.size(); elemI++)
                    {
                        objFile
                            << "l " << route[elemI-1]+1 << ' '
                            << route[elemI]+1 << endl;
                    }
                    Info<< "route:" << route << endl;
                }
            }
        }
        Info<< endl;

        iter++;
    }

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

    return 0;
}


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