/*---------------------------------------------------------------------------*\
  =========                 |
  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
   \\    /   O peration     | Website:  https://openfoam.org
    \\  /    A nd           | Copyright (C) 2023-2025 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 "blockMeshCylindricalConfiguration.H"
#include "dictionary.H"
#include "polyPatch.H"
#include "wallPolyPatch.H"
#include "blockMeshFunctions.H"

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

const Foam::List<Foam::word> Foam::blockMeshCylindricalConfiguration::patches =
    {"zMin", "zMax"};


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

void Foam::blockMeshCylindricalConfiguration::bbInflate
(
    boundBox& bb,
    const vector& s
)
{
    bb.min() = cmptMultiply(s, bb.min());
    bb.max() = cmptMultiply(s, bb.max());
}

bool Foam::blockMeshCylindricalConfiguration::isBoundBoxOnZaxis()
{
    const vector bbMidNorm = bb_.midpoint()/bb_.mag();
    return mag(bbMidNorm.x()) < rootSmall && mag(bbMidNorm.y()) < rootSmall;
}

void Foam::blockMeshCylindricalConfiguration::calcBlockMeshDict
(
    const bool& boundsOpt,
    const bool& rotatingZonesOpt
)
{
    // Set nCells as a vector: (boxCells radialCells zCells)
    label boxCells = nCells_.x();
    label radialCells = nCells_.y();

    if (nCells_ == Vector<label>::zero)
    {
        boxCells = 5;
        radialCells = 15;

        nCells_ = Vector<label>
        (
            boxCells,
            radialCells,
            2.0*radialCells*bb_.span().z()/bb_.span().x()
        );
    }

    // Size the bounding box
    const scalar roundFactor = roundingScale(0.01*bb_.minDim());

    // If '-bounds' is not specified, or '-rotatingZones' is specified
    // inflate the respective bounding box(es) in radial direction,
    // followed by rounding
    if(!boundsOpt || rotatingZonesOpt)
    {
        const scalar expansion = 1.0/(Foam::cos(degToRad(45.0/nCells_.x())));
        const vector scaling(expansion, expansion, 1);

        if(!boundsOpt)
        {
            bbInflate(bb_, scaling);
            roundBoundingBox(bb_, roundFactor);
        }
        if(rotatingZonesOpt)
        {
            bbInflate(rzbb_, scaling);
            roundBoundingBox(rzbb_, roundFactor);
        }
    }

    radBox_ = ceil(0.3*rzbb_.max().x()/roundFactor)*roundFactor;
    nCells_ *= refineFactor_;
}


void Foam::blockMeshCylindricalConfiguration::writeBackgroundMesh()
{
    const scalar radOut = bb_.max().x();
    const scalar radIn = rzbb_.max().x();
    const scalar boxToRadOut = radOut - radBox_;
    const scalar boxToRadIn = radIn - radBox_;

    const label inCells = ceil(boxToRadIn*nCells_.y()/boxToRadOut);
    const label outCells = nCells_.y() - inCells;

    beginDict(os_, "backgroundMesh");

    os_ << indent << "radOut     " << radOut << ";" << endl;
    os_ << indent << "radIn      " << radIn << ";" << endl;
    os_ << indent << "radBox     " << radBox_ << ";" << nl << endl;
    os_ << indent << "zMin       " << bb_.min().z() << ";" << endl;
    os_ << indent << "zMax       " << bb_.max().z() << ";" << nl << endl;
    os_ << indent << "boxCells   " << nCells_.x() << ";" << endl;
    os_ << indent << "inCells    " << inCells << ";" << endl;
    os_ << indent << "outCells   " << outCells << ";" << endl;
    os_ << indent << "zCells     " << nCells_.z() << ";" << nl << endl;
    os_ << indent << "outGrading 2.0;" << nl << endl;
    os_ << indent << "radOutN    #neg $radOut;" << endl;
    os_ << indent << "radInN     #neg $radIn;" << endl;
    os_ << indent << "radBoxN    #neg $radBox;" << endl;

    endDict(os_);

    os_ << "convertToMeters 1;" << nl << endl;
}


void Foam::blockMeshCylindricalConfiguration::writeDefaultPatch()
{
    Pair<word> defaultPatch;

    word opt = "defaultPatch";
    if (patchOpts_.found(opt))
    {
        defaultPatch = readPatchOption(opt);
    }
    else
    {
        defaultPatch = {"background", "internal"};
    }

    beginDict(os_, "defaultPatch");

    os_ << indent << "name " << defaultPatch.first() << ";" << nl
        << indent << "type " << defaultPatch.second() << ";" << endl;

    endDict(os_);

    Info<< "\nAdding defaultPatch '" << defaultPatch.first()
        << "' of type '" << defaultPatch.second() << "'" << endl;
}


void Foam::blockMeshCylindricalConfiguration::writePatch
(
    const word& name,
    const word& type,
    const string& face
)
{
    os_ << indent << name
        << " { type " << type
        << "; faces ( " << face.c_str()
        << " ); }" << endl;

    Info<< "Adding patch '" << name
        << "' of type '" << type << "'" << endl;
}


void Foam::blockMeshCylindricalConfiguration::writeBoundary()
{
    // Enable boundary section if a patch option or clearBoundary is selected
    bool enableBoundary = clearBoundary_;
    forAll(patches, i)
    {
        if (enableBoundary)
        {
            break;
        }

        enableBoundary = patchOpts_.found(patches[i] + "Patch");
    }

    if (!enableBoundary)
    {
        os_ << "// delete \"-disabled\" to enable boundary settings" << endl;

        Info<< "\nNote: The boundary list in blockMeshDict is disabled" << nl
            << "To enable, open the file and edit line number "
            << os_.lineNumber() << nl << endl;
    }

    beginList
    (
        os_,
        enableBoundary ? "boundary" : "boundary-disabled"
    );

    forAll(patches, i)
    {
        const bool optFound(patchOpts_.found(patches[i] + "Patch"));

        // Do not write patch entry if clearBoundary option is selected
        // and the respective patch option is not selected
        if (clearBoundary_ && !optFound)
        {
            continue;
        }

        Pair<word> patch(patches[i], "patch");

        if (optFound)
        {
            patch = readPatchOption(patch.first() + "Patch");
        }

        beginDict(os_, patch.first());
        os_ << indent << "type " << patch.second() << ";" << endl;
        beginList(os_, "faces");

        switch (i)
        {
            case 0:
            {
                os_ << indent << "(0 1 2 3)" << nl
                    << indent << "(0 4 5 1)" << nl
                    << indent << "(1 5 6 2)" << nl
                    << indent << "(2 6 7 3)" << nl
                    << indent << "(3 7 4 0)" << nl
                    << indent << "(4 8 9 5)" << nl
                    << indent << "(5 9 10 6)" << nl
                    << indent << "(6 10 11 7)" << nl
                    << indent << "(7 11 8 4)" << endl;
                break;
            }

            case 1:
            {
                os_ << indent << "(12 13 14 15)" << nl
                    << indent << "(12 16 17 13)" << nl
                    << indent << "(13 17 18 14)" << nl
                    << indent << "(14 18 19 15)" << nl
                    << indent << "(15 19 16 12)" << nl
                    << indent << "(16 20 21 17)" << nl
                    << indent << "(17 21 22 18)" << nl
                    << indent << "(18 22 23 19)" << nl
                    << indent << "(19 23 20 16)" << endl;
                break;
            }
        }

        endList(os_, false);
        endDict(os_, i != 1);
    }

    endList(os_);
}


void Foam::blockMeshCylindricalConfiguration::writeGeometry()
{
    beginDict(os_, "geometry");

    List<word> geometries {"rotatingZone", "outer"};
    List<word> dims {"radIn", "radOut"};

    scalar zMin = roundDown(bb_.min().z(), 10);
    scalar zMax = roundUp(bb_.max().z(), 10);

    // Extend the bounds to avoid bad projections
    const scalar span = zMax - zMin;
    zMax += span;
    zMin -= span;

    forAll(geometries, i)
    {
        beginDict(os_, geometries[i]);

        os_ << indent << "type cylinder;" << nl
            << indent << "point1 (0 0 " << zMin << ");" << nl
            << indent << "point2 (0 0 " << zMax << ");" << nl
            << indent << "radius $!backgroundMesh/" << dims[i] << ";" << endl;

        endDict(os_);
    }

    endDict(os_);
}


void Foam::blockMeshCylindricalConfiguration::writeProjectedVertex
(
    const word& x,
    const word& y,
    const word& z,
    const word& surface
)
{
    os_ << indent << "project" << endl;
    writeVertex(x, y, z);
    os_ << indent << "(" << surface << ")" << nl << endl;
}


void Foam::blockMeshCylindricalConfiguration::writeVertices()
{
    beginList(os_, "vertices");

    forAll(patches, i)
    {
        const word& dir = patches[i];

        writeVertex("radBoxN", "radBoxN", dir);
        writeVertex("radBox", "radBoxN", dir);
        writeVertex("radBox", "radBox", dir);
        writeVertex("radBoxN", "radBox", dir);

        os_ << endl;

        writeProjectedVertex("radInN", "radInN", dir, "rotatingZone");
        writeProjectedVertex("radIn", "radInN", dir, "rotatingZone");
        writeProjectedVertex("radIn", "radIn", dir, "rotatingZone");
        writeProjectedVertex("radInN", "radIn", dir, "rotatingZone");

        writeProjectedVertex("radOutN", "radOutN", dir, "outer");
        writeProjectedVertex("radOut", "radOutN", dir, "outer");
        writeProjectedVertex("radOut", "radOut", dir, "outer");
        writeProjectedVertex("radOutN", "radOut", dir, "outer");
    }

    endList(os_);
}


void Foam::blockMeshCylindricalConfiguration::writeBlocks()
{
    os_ << "boxMesh" << endl;
    writeVertex("boxCells", "boxCells", "zCells");
    os_ << "simpleGrading (1 1 1);" << nl << endl;

    os_ << "inMesh" << endl;
    writeVertex("boxCells", "inCells", "zCells");
    os_ << "simpleGrading (1 1 1);" << nl << endl;

    os_ << "outMesh" << endl;
    writeVertex("boxCells", "outCells", "zCells");
    os_ << "simpleGrading (1 $!backgroundMesh/outGrading 1);" << nl << endl;

    beginList(os_, "blocks");

    os_ << indent << "hex (0 1 2 3 12 13 14 15) $boxMesh" << nl << nl
        << indent << "hex (1 0 4 5 13 12 16 17) $inMesh" << nl
        << indent << "hex (0 3 7 4 12 15 19 16) $inMesh" << nl
        << indent << "hex (2 1 5 6 14 13 17 18) $inMesh" << nl
        << indent << "hex (3 2 6 7 15 14 18 19) $inMesh" << nl << nl
        << indent << "hex (5 4  8  9 17 16 20 21) $outMesh" << nl
        << indent << "hex (4 7 11  8 16 19 23 20) $outMesh" << nl
        << indent << "hex (6 5  9 10 18 17 21 22) $outMesh" << nl
        << indent << "hex (7 6 10 11 19 18 22 23) $outMesh" << endl;

    endList(os_);
}


void Foam::blockMeshCylindricalConfiguration::writeEdges()
{
    beginList(os_, "edges");

    os_ << indent << "project  4  5  (rotatingZone)" << nl
        << indent << "project  5  6  (rotatingZone)" << nl
        << indent << "project  6  7  (rotatingZone)" << nl
        << indent << "project  7  4  (rotatingZone)" << nl
        << indent << "project 16 17  (rotatingZone)" << nl
        << indent << "project 17 18  (rotatingZone)" << nl
        << indent << "project 18 19  (rotatingZone)" << nl
        << indent << "project 19 16  (rotatingZone)" << nl << nl
        << indent << "project  8  9  (outer)" << nl
        << indent << "project  9 10  (outer)" << nl
        << indent << "project 10 11  (outer)" << nl
        << indent << "project 11  8  (outer)" << nl
        << indent << "project 20 21  (outer)" << nl
        << indent << "project 21 22  (outer)" << nl
        << indent << "project 22 23  (outer)" << nl
        << indent << "project 23 20  (outer)" << endl;

    endList(os_);
}


void Foam::blockMeshCylindricalConfiguration::writeMergePatchPairs()
{
    os_ << "mergePatchPairs" << nl
        << "(" << nl
        << ");" << endl;
}


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

Foam::blockMeshCylindricalConfiguration::blockMeshCylindricalConfiguration
(
    const fileName& name,
    const fileName& dir,
    const Time& time,
    const meshingSurfaceList& surfaces,
    const bool& boundsOpt,
    const Vector<label>& nCells,
    const label refineFactor,
    const HashTable<Pair<word>>& patchOpts,
    const bool clearBoundary
)
:
    blockMeshConfigurationBase(name, dir, time, surfaces, patchOpts),
    rzbb_(surfaces.rzbb()),
    nCells_(nCells),
    refineFactor_(refineFactor),
    clearBoundary_(clearBoundary)
{
    if (!isBoundBoxOnZaxis())
    {
        FatalErrorInFunction
            << "Attempting to create a cylindrical background mesh"
            << nl << "but the geometry bounds are not aligned with the z-axis."
            << exit(FatalError);
    }

    bool rotatingZonesOpt(true);

    if (rzbb_.volume() == 0)
    {
        rotatingZonesOpt = false;

        WarningInFunction
            << "Creating a cylindrical background mesh without a "
            << "rotatingZone specified by the '-rotatingZones' option."
            << nl <<endl;

        // Create the intermediate interface at 40% of domain size if no
        // rotating zone is specified
        scalar factor = 0.4;

        rzbb_.min() = factor*bb_.min();
        rzbb_.max() = factor*bb_.max();
    }

    calcBlockMeshDict(boundsOpt, rotatingZonesOpt);
}


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

Foam::blockMeshCylindricalConfiguration::~blockMeshCylindricalConfiguration()
{}


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

void Foam::blockMeshCylindricalConfiguration::write()
{
    dict_.writeHeader(os_, word("dictionary"));

    writeBackgroundMesh();
    writeDefaultPatch();
    writeBoundary();
    writeGeometry();
    writeVertices();
    writeBlocks();
    writeEdges();
    writeMergePatchPairs();

    dict_.writeEndDivider(os_);
}


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