/*---------------------------------------------------------------------------*\
  =========                 |
  \\      /  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 "meshingSurfaceList.H"

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

void Foam::meshingSurfaceList::mergeBoundingBoxes
(
    boundBox& bb1,
    const boundBox& bb2
)
{
    if (bb1.volume() == 0)
    {
        bb1 = bb2;
        return;
    }

    point& min1 = bb1.min();
    point& max1 = bb1.max();
    const point& min2 = bb2.min();
    const point& max2 = bb2.max();

    forAll(min1, i)
    {
        min1[i] = Foam::min(min1[i], min2[i]);
        max1[i] = Foam::max(max1[i], max2[i]);
    }
}


void Foam::meshingSurfaceList::swapExternalIndexZero(const label index)
{
    if (index == 0)
    {
        return;
    }

    autoPtr<meshingSurface> s0Ptr(set(0, nullptr));
    autoPtr<meshingSurface> sIndexPtr(set(index, nullptr));

    set(index, s0Ptr.ptr());
    set(0, sIndexPtr.ptr());
}


bool Foam::meshingSurfaceList::regionsValid
(
    const wordList& specifiedRegions,
    const wordList& regions,
    const word& opt
)
{
    if (specifiedRegions.empty())
    {
        return false;
    }

    forAll(specifiedRegions, s)
    {
        bool match(false);

        forAll(regions, r)
        {
            match = (specifiedRegions[s] == regions[r]) || match;
            if (match)
            {
                break;
            }
        }

        if (!match)
        {
            FatalErrorInFunction
                << "Region '"<< specifiedRegions[s]
                << "' specified with the '" << opt
                << "' option" << nl
                << "does not match any regions in the external surface"
                << exit(FatalError);
        }
    }

    return true;
}


void Foam::meshingSurfaceList::setSurfaceTypes
(
    const List<word>& surfaces,
    const surfaceType& type
)
{
    forAll(surfaces, i)
    {
        const word surface = fileName(surfaces[i]).lessExt();
        const word surfaceType = meshingSurface::surfaceTypeNames[type];
        bool match = false;

        // Check if the surface name matches a surface name
        forAll(*this, surfi)
        {
            if (surface == operator[](surfi).name())
            {
                // For cellZone and rotatingZone, ensure surface is closed
                if
                (
                    !operator[](surfi).closed()
                 && (
                        type == meshingSurface::surfaceType::rotatingZone
                     || type == meshingSurface::surfaceType::cellZone
                    )
                )
                {
                    FatalErrorInFunction
                        << "Argument to '-" << surfaceType
                        << "' contains the " << surfaceType << " '"
                        << surface << "'" << nl
                        << "which is not a closed surface."
                        << exit(FatalError);
                }

                operator[](surfi).type() = type;
                match = true;
                break;
            }
        }

        if (!match)
        {
            FatalErrorInFunction
                << "Argument to '-" << surfaceType
                << "' contains the " << surfaceType << " '"
                << surface << "'" << nl
                << "which does not correspond to any surfaces."
                << exit(FatalError);
        }
    }
}


void Foam::meshingSurfaceList::setRotatingZoneBounds()
{
    forAll(*this, surfi)
    {
        if (operator[](surfi).type() == surfaceType::rotatingZone)
        {
            mergeBoundingBoxes(rzbb_, operator[](surfi).bb());
        }
    }
}


void Foam::meshingSurfaceList::identifyCellZones()
{
    forAll(*this, i)
    {
        if
        (
            operator[](i).type() == surfaceType::external
         || operator[](i).type() == surfaceType::rotatingZone
         || operator[](i).nParts() != 1
         || !operator[](i).closed()
        )
        {
            continue;
        }

        forAll(*this, j)
        {
            if (i == j)
            {
                continue;
            }

            if (operator[](i).bb().contains(operator[](j).bb()))
            {
                operator[](i).type() = surfaceType::cellZone;
                continue;
            }
        }
    }
}


void Foam::meshingSurfaceList::reportWordList(const wordList& wl)
{
    forAll(wl, i)
    {
        Info<< wl[i];
        if (i != wl.size() - 1)
        {
            Info<< ", ";
        }
    }

    Info<< endl;
}


void Foam::meshingSurfaceList::reportSurfaces()
{
    Info<< "Case contains the following surface geometry files:"
        << endl;

    forAll(*this, i)
    {
        Info<< "+ " << operator[](i).name() << nl
            << "  + File: " << operator[](i).file() << nl
            << "  + Bounding box: " << operator[](i).bb() << nl
            << "  + " << (operator[](i).closed() ? "Closed" : "Open")
            << " surface" << endl;

        switch (operator[](i).type())
        {
            case surfaceType::external:
            {
                Info << "  + External boundary surface" << endl;

                if (!operator[](i).inletRegions().empty())
                {
                    Info<< "  + Inlet regions: ";
                    reportWordList(operator[](i).inletRegions());
                }

                if (!operator[](i).outletRegions().empty())
                {
                    Info << "  + Outlet regions: ";
                    reportWordList(operator[](i).outletRegions());
                }

                break;
            }

            case surfaceType::wall:
            {
                Info << "  + Wall boundary surface" << endl;
                break;
            }

            case surfaceType::cellZone:
            {
                Info << "  + Cell zone surface" << endl;
                break;
            }

            case surfaceType::rotatingZone:
            {
                Info << "  + Rotating zone surface" << endl;
                break;
            }

            case surfaceType::baffle:
            {
                Info << "  + Baffle wall surface" << endl;
                break;
            }
        }
    }

    Info<< endl;
}


void Foam::meshingSurfaceList::setBounds(const boundBox& bb)
{
    if (bb.contains(bb_))
    {
        Info<< "Specified bounding box contains the overall bounding box"
            << endl;
    }
    else
    {
        WarningInFunction
            << "Specified bounding box does not contain the overall "
            << "bounding box"
            << endl;
    }

    if (bb.mag() > 1.5*bb_.mag())
    {
        Info<< "Specified bounding box is > (1.5 * overall "
            << "bounding box)" << nl
            << "**Assuming this is an external flow**"
            << endl;

        operator[](0).type() = surfaceType::wall;
    }

    Info<< endl;

    bb_ = bb;
}


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

Foam::meshingSurfaceList::meshingSurfaceList
(
    const Time& time,
    const fileNameList& surfaces,
    const wordList& cellZones,
    const wordList& rotatingZones,
    const wordList& baffles,
    const boundBox& bb,
    const wordList& specifiedInletRegions,
    const wordList& specifiedOutletRegions,
    const bool closedDomain
)
:
    PtrList<meshingSurface>(),
    bb_(),
    rzbb_()
{
    // Load all the surfaces and construct the bounding box
    forAll(surfaces, i)
    {
        append(new meshingSurface(surfaces[i], time));

        mergeBoundingBoxes(bb_, last().bb());
    }

    label externalID = 0;
    forAll(surfaces, i)
    {
        // Test for external surface inflates the bounding box by a small factor
        // to account for imprecise surface geometry files
        boundBox bbInflate = operator[](i).bb();
        bbInflate.inflate(1e-4);

        if
        (
            operator[](i).closed()
         && operator[](i).nParts() == 1
         && (closedDomain || operator[](i).regions().size() != 1)
         && bbInflate.contains(bb_)
        )
        {
            externalID = i;
            operator[](i).type() = surfaceType::external;

            // Override any inlet and outlet regions on external boundary
            // specified by the '-inletRegions' and '-outletRegions' options
            const wordList& regions = operator[](i).regions();

            wordList& inletRegions = operator[](i).inletRegions();
            if (regionsValid(specifiedInletRegions, regions, "-inletRegions"))
            {
                inletRegions = specifiedInletRegions;
            }

            wordList& outletRegions = operator[](i).outletRegions();
            if (regionsValid(specifiedOutletRegions, regions, "-outletRegions"))
            {
                outletRegions = specifiedOutletRegions;
            }

            // If inletRegions and outletRegions are both empty, set "template"
            // names, unless "closedDomain" is specified
            if
            (
                inletRegions.empty()
             && outletRegions.empty()
             && !closedDomain
            )
            {
                inletRegions.append("<inletRegion>");
                outletRegions.append("<outletRegion>");
            }
        }
    }

    swapExternalIndexZero(externalID);

    if (!rotatingZones.empty())
    {
        setSurfaceTypes(rotatingZones, surfaceType::rotatingZone);
    }

    if (!baffles.empty())
    {
        setSurfaceTypes(baffles, surfaceType::baffle);
    }

    setRotatingZoneBounds();

    if (cellZones.empty())
    {
        identifyCellZones();
    }
    else
    {
        setSurfaceTypes(cellZones, surfaceType::cellZone);
    }

    reportSurfaces();

    if (bb.volume() != 0)
    {
        setBounds(bb);
    }
}


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

Foam::meshingSurfaceList::~meshingSurfaceList()
{}


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