/*---------------------------------------------------------------------------*\
  =========                 |
  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
   \\    /   O peration     | Website:  https://openfoam.org
    \\  /    A nd           | Copyright (C) 2011-2019 OpenFOAM Foundation
     \\/     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 "vtkPVFoam.H"
#include "vtkPVFoamReader.h"

// OpenFOAM includes
#include "fvMesh.H"
#include "cellModeller.H"
#include "vtkOpenFOAMPoints.H"
#include "Swap.H"

// VTK includes
#include "vtkCellArray.h"
#include "vtkIdTypeArray.h"
#include "vtkUnstructuredGrid.h"

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

vtkUnstructuredGrid* Foam::vtkPVFoam::volumeVTKMesh
(
    const fvMesh& mesh,
    polyDecomp& decompInfo
)
{
    const cellModel& tet = *(cellModeller::lookup("tet"));
    const cellModel& pyr = *(cellModeller::lookup("pyr"));
    const cellModel& prism = *(cellModeller::lookup("prism"));
    const cellModel& wedge = *(cellModeller::lookup("wedge"));
    const cellModel& tetWedge = *(cellModeller::lookup("tetWedge"));
    const cellModel& hex = *(cellModeller::lookup("hex"));

    vtkUnstructuredGrid* vtkmesh = vtkUnstructuredGrid::New();

    if (debug)
    {
        InfoInFunction << endl;
        printMemory();
    }

    const cellShapeList& cellShapes = mesh.cellShapes();

    // Number of additional points needed by the decomposition of polyhedra
    label nAddPoints = 0;

    // Number of additional cells generated by the decomposition of polyhedra
    label nAddCells = 0;

    // face owner is needed to determine the face orientation
    const labelList& owner = mesh.faceOwner();

    labelList& superCells = decompInfo.superCells();
    labelList& addPointCellLabels = decompInfo.addPointCellLabels();

    // Scan for cells which need to be decomposed and count additional points
    // and cells
    if (!reader_->GetUseVTKPolyhedron())
    {
        if (debug)
        {
            InfoInFunction
                << "... scanning for polyhedra" << endl;
        }

        forAll(cellShapes, celli)
        {
            const cellModel& model = cellShapes[celli].model();

            if
            (
                model != hex
             && model != wedge
             && model != prism
             && model != pyr
             && model != tet
             && model != tetWedge
            )
            {
                const cell& cFaces = mesh.cells()[celli];

                forAll(cFaces, cFacei)
                {
                    const face& f = mesh.faces()[cFaces[cFacei]];

                    label nQuads = 0;
                    label nTris = 0;
                    f.nTrianglesQuads(mesh.points(), nTris, nQuads);

                    nAddCells += nQuads + nTris;
                }

                nAddCells--;
                nAddPoints++;
            }
        }
    }

    // Set size of additional point addressing array
    // (from added point to original cell)
    addPointCellLabels.setSize(nAddPoints);

    // Set size of additional cells mapping array
    // (from added cell to original cell)

    if (debug)
    {
        Info<< "    mesh nCells     = " << mesh.nCells() << nl
            << "         nPoints    = " << mesh.nPoints() << nl
            << "         nAddCells  = " << nAddCells << nl
            << "         nAddPoints = " << nAddPoints << endl;
    }

    superCells.setSize(mesh.nCells() + nAddCells);

    if (debug)
    {
        Info<< "    ... converting points" << endl;
    }

    // Convert OpenFOAM mesh vertices to VTK
    vtkPoints* vtkpoints = vtkPoints::New();
    vtkpoints->Allocate(mesh.nPoints() + nAddPoints);

    const Foam::pointField& points = mesh.points();

    forAll(points, i)
    {
        vtkInsertNextOpenFOAMPoint(vtkpoints, points[i]);
    }


    if (debug)
    {
        Info<< "    ... converting cells" << endl;
    }

    vtkmesh->Allocate(mesh.nCells() + nAddCells);

    // Set counters for additional points and additional cells
    label addPointi = 0, addCelli = 0;

    // Create storage for points - needed for mapping from OpenFOAM to VTK
    // data types - max 'order' = hex = 8 points
    vtkIdType nodeIds[8];

    // face-stream for a polyhedral cell
    // [numFace0Pts, id1, id2, id3, numFace1Pts, id1, id2, id3, ...]
    DynamicList<vtkIdType> faceStream(256);

    forAll(cellShapes, celli)
    {
        const cellShape& cellShape = cellShapes[celli];
        const cellModel& cellModel = cellShape.model();

        superCells[addCelli++] = celli;

        if (cellModel == tet)
        {
            for (int j = 0; j < 4; j++)
            {
                nodeIds[j] = cellShape[j];
            }
            vtkmesh->InsertNextCell
            (
                VTK_TETRA,
                4,
                nodeIds
            );
        }
        else if (cellModel == pyr)
        {
            for (int j = 0; j < 5; j++)
            {
                nodeIds[j] = cellShape[j];
            }
            vtkmesh->InsertNextCell
            (
                VTK_PYRAMID,
                5,
                nodeIds
            );
        }
        else if (cellModel == prism)
        {
            // VTK has a different node order for VTK_WEDGE
            // their triangles point outwards!
            nodeIds[0] = cellShape[0];
            nodeIds[1] = cellShape[2];
            nodeIds[2] = cellShape[1];
            nodeIds[3] = cellShape[3];
            nodeIds[4] = cellShape[5];
            nodeIds[5] = cellShape[4];

            vtkmesh->InsertNextCell
            (
                VTK_WEDGE,
                6,
                nodeIds
            );
        }
        else if (cellModel == tetWedge && !reader_->GetUseVTKPolyhedron())
        {
            // Treat as squeezed prism (VTK_WEDGE)

            nodeIds[0] = cellShape[0];
            nodeIds[1] = cellShape[2];
            nodeIds[2] = cellShape[1];
            nodeIds[3] = cellShape[3];
            nodeIds[4] = cellShape[4];
            nodeIds[5] = cellShape[3];

            vtkmesh->InsertNextCell
            (
                VTK_WEDGE,
                6,
                nodeIds
            );
        }
        else if (cellModel == wedge)
        {
            // Treat as squeezed hex

            nodeIds[0] = cellShape[0];
            nodeIds[1] = cellShape[1];
            nodeIds[2] = cellShape[2];
            nodeIds[3] = cellShape[2];
            nodeIds[4] = cellShape[3];
            nodeIds[5] = cellShape[4];
            nodeIds[6] = cellShape[5];
            nodeIds[7] = cellShape[6];

            vtkmesh->InsertNextCell
            (
                VTK_HEXAHEDRON,
                8,
                nodeIds
            );
        }
        else if (cellModel == hex)
        {
            for (int j = 0; j < 8; j++)
            {
                nodeIds[j] = cellShape[j];
            }
            vtkmesh->InsertNextCell
            (
                VTK_HEXAHEDRON,
                8,
                nodeIds
            );
        }
        else if (reader_->GetUseVTKPolyhedron())
        {
            // Polyhedral cell - use VTK_POLYHEDRON
            const labelList& cFaces = mesh.cells()[celli];

#ifdef HAS_VTK_POLYHEDRON
            vtkIdType nFaces = cFaces.size();
            vtkIdType nLabels = nFaces;

            // count size for face stream
            forAll(cFaces, cFacei)
            {
                const face& f = mesh.faces()[cFaces[cFacei]];
                nLabels += f.size();
            }

            // build face-stream
            // [numFace0Pts, id1, id2, id3, numFace1Pts, id1, id2, id3, ...]
            // point Ids are global
            faceStream.clear();
            faceStream.reserve(nLabels + nFaces);

            forAll(cFaces, cFacei)
            {
                const face& f = mesh.faces()[cFaces[cFacei]];
                const bool isOwner = (owner[cFaces[cFacei]] == celli);
                const label nFacePoints = f.size();

                // number of labels for this face
                faceStream.append(nFacePoints);

                if (isOwner)
                {
                    forAll(f, fp)
                    {
                        faceStream.append(f[fp]);
                    }
                }
                else
                {
                    // Fairly immaterial if we reverse the list
                    // or use face::reverseFace()
                    forAllReverse(f, fp)
                    {
                        faceStream.append(f[fp]);
                    }
                }
            }

            vtkmesh->InsertNextCell(VTK_POLYHEDRON, nFaces, faceStream.data());
#else
            // This is a horrible substitute
            // but avoids crashes when there is no vtkPolyhedron support

            // Establish unique node ids used
            HashSet<vtkIdType, Hash<label>> hashUniqId(2*256);

            forAll(cFaces, cFacei)
            {
                const face& f = mesh.faces()[cFaces[cFacei]];

                forAll(f, fp)
                {
                    hashUniqId.insert(f[fp]);
                }
            }

            // Use face stream to store unique node ids:
            faceStream = hashUniqId.sortedToc();

            vtkmesh->InsertNextCell
            (
                VTK_CONVEX_POINT_SET,
                vtkIdType(faceStream.size()),
                faceStream.data()
            );
#endif
        }
        else
        {
            // Polyhedral cell. Decompose into tets + prisms.

            // Mapping from additional point to cell
            addPointCellLabels[addPointi] = celli;

            // The new vertex from the cell-centre
            const label newVertexLabel = mesh.nPoints() + addPointi;
            vtkInsertNextOpenFOAMPoint(vtkpoints, mesh.C()[celli]);

            // Whether to insert cell in place of original or not.
            bool substituteCell = true;

            const labelList& cFaces = mesh.cells()[celli];
            forAll(cFaces, cFacei)
            {
                const face& f = mesh.faces()[cFaces[cFacei]];
                const bool isOwner = (owner[cFaces[cFacei]] == celli);

                // Number of triangles and quads in decomposition
                label nTris = 0;
                label nQuads = 0;
                f.nTrianglesQuads(mesh.points(), nTris, nQuads);

                // Do actual decomposition into triFcs and quadFcs.
                faceList triFcs(nTris);
                faceList quadFcs(nQuads);
                label trii = 0;
                label quadi = 0;
                f.trianglesQuads(mesh.points(), trii, quadi, triFcs, quadFcs);

                forAll(quadFcs, quadI)
                {
                    if (substituteCell)
                    {
                        substituteCell = false;
                    }
                    else
                    {
                        superCells[addCelli++] = celli;
                    }

                    const face& quad = quadFcs[quadI];

                    // Ensure we have the correct orientation for the
                    // base of the primitive cell shape.
                    // If the cell is face owner, the orientation needs to be
                    // flipped.
                    // At the moment, VTK doesn't actually seem to care if
                    // negative cells are defined, but we'll do it anyhow
                    // (for safety).
                    if (isOwner)
                    {
                        nodeIds[0] = quad[3];
                        nodeIds[1] = quad[2];
                        nodeIds[2] = quad[1];
                        nodeIds[3] = quad[0];
                    }
                    else
                    {
                        nodeIds[0] = quad[0];
                        nodeIds[1] = quad[1];
                        nodeIds[2] = quad[2];
                        nodeIds[3] = quad[3];
                    }
                    nodeIds[4] = newVertexLabel;
                    vtkmesh->InsertNextCell
                    (
                        VTK_PYRAMID,
                        5,
                        nodeIds
                    );
                }

                forAll(triFcs, triI)
                {
                    if (substituteCell)
                    {
                        substituteCell = false;
                    }
                    else
                    {
                        superCells[addCelli++] = celli;
                    }

                    const face& tri = triFcs[triI];

                    // See note above about the orientation.
                    if (isOwner)
                    {
                        nodeIds[0] = tri[2];
                        nodeIds[1] = tri[1];
                        nodeIds[2] = tri[0];
                    }
                    else
                    {
                        nodeIds[0] = tri[0];
                        nodeIds[1] = tri[1];
                        nodeIds[2] = tri[2];
                    }
                    nodeIds[3] = newVertexLabel;

                    vtkmesh->InsertNextCell
                    (
                        VTK_TETRA,
                        4,
                        nodeIds
                    );
                }
            }

            addPointi++;
        }
    }

    vtkmesh->SetPoints(vtkpoints);
    vtkpoints->Delete();

    if (debug)
    {
        printMemory();
    }

    return vtkmesh;
}


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