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

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

#include "globalIndex.H"
#include "Pair.H"

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

void Foam::globalIndex::reportOverflowAndExit
(
    const label idx,
    const label prevOffset,
    const label count
)
{
    if (idx < 0)
    {
        // No overflow tagged
        return;
    }

    FatalErrorInFunction
        << "Overflow : sum of sizes exceeds labelMax ("
        << labelMax << ") after index " << idx;

    if (prevOffset >= 0 && count >= 0)
    {
        FatalError
            << " while trying to add (" << count
            << ") to offset (" << prevOffset << ")";
    }

    FatalError
        << nl
        << "Please recompile with larger datatype for label." << nl
        << exit(FatalError);
}


Foam::labelList
Foam::globalIndex::calcOffsets
(
    const labelUList& counts,
    const bool checkOverflow
)
{
    labelList values;

    if (const label len = counts.size(); len > 0)
    {
        values.resize(len+1);

        label start = 0;
        for (label i = 0; i < len; ++i)
        {
            const auto count = counts[i];
            values[i] = start;
            start += count;

            if (checkOverflow && start < values[i])
            {
                reportOverflowAndExit(i, values[i], count);
            }
        }
        values[len] = start;
    }

    return values;
}


Foam::List<Foam::labelRange>
Foam::globalIndex::calcRanges
(
    const labelUList& counts,
    const bool checkOverflow
)
{
    List<labelRange> values;

    if (const label len = counts.size(); len > 0)
    {
        values.resize(len);

        label start = 0;
        for (label i = 0; i < len; ++i)
        {
            const auto count = counts[i];
            values[i].reset(start, count);
            start += count;

            if
            (
                checkOverflow
             && (start < values[i].start())
             && (i < len-1)  // Do not check the one beyond the end range
            )
            {
                reportOverflowAndExit(i, values[i].start(), count);
            }
        }
    }

    return values;
}


bool Foam::globalIndex::splitNodeOffsets
(
    labelList& interNodeOffsets,
    labelList& localNodeOffsets,
    const label communicator,
    const bool absoluteLocalNodeOffsets
) const
{
    // Require const-world as the starting point
    if (!UPstream::parRun() || communicator != UPstream::commConstWorld())
    {
        interNodeOffsets.clear();
        localNodeOffsets.clear();
        return false;
    }

    const auto interNodeComm = UPstream::commInterNode();

    // Only generate information on the node leaders
    if (!UPstream::is_rank(interNodeComm))
    {
        interNodeOffsets.clear();
        localNodeOffsets.clear();
        return true;  // Not involved, but return true to match others...
    }

    const label numProc = UPstream::nProcs(UPstream::commConstWorld());
    const auto& procIds = UPstream::procID(interNodeComm);
    const int ranki = UPstream::myProcNo(interNodeComm);

    if (FOAM_UNLIKELY(procIds.empty()))
    {
        // Should not happen...
        interNodeOffsets.clear();
        localNodeOffsets.clear();
        return true;  // Return true to match others...
    }

    // The inter-node offsets from the node-specific segment of the
    // overall offsets, but avoiding MPI_Scatterv (slow, doesn't
    // handle overlaps) and using MPI_Bcast() instead.

    // Send top-level offsets to the node leaders.
    // Could also be a mutable operation and use offsets_ directly.
    //
    // - number of overall offsets is always (nProc+1) [worldComm]
    labelList allOffsets;
    if (UPstream::master(interNodeComm))
    {
        allOffsets = offsets_;
    }
    else  // ie, UPstream::is_subrank(interNodeComm)
    {
        allOffsets.resize_nocopy(numProc+1);
    }

    UPstream::broadcast(allOffsets.data(), allOffsets.size(), interNodeComm);


    if (FOAM_UNLIKELY(allOffsets.empty()))
    {
        // Should not happen...
        interNodeOffsets.clear();
        localNodeOffsets.clear();
        return true;  // Return true to match others...
    }

    // The local node span
    const label firstProc = procIds[ranki];
    const label lastProc =
    (
        (ranki+1 < procIds.size())
      ? procIds[ranki+1]
      : numProc
    );

    // Offsets (within a node)
    localNodeOffsets = allOffsets.slice
    (
        firstProc,
        (lastProc - firstProc) + 1  // +1 since offsets
    );

    if (!absoluteLocalNodeOffsets && !localNodeOffsets.empty())
    {
        const auto start0 = localNodeOffsets.front();
        for (auto& val : localNodeOffsets)
        {
            val -= start0;
        }
    }

    // Offsets (between nodes)
    interNodeOffsets.resize_nocopy(procIds.size()+1);
    {
        forAll(procIds, i)
        {
            interNodeOffsets[i] = allOffsets[procIds[i]];
        }
        interNodeOffsets.back() = allOffsets.back();
    }

    return true;
}


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

Foam::globalIndex::globalIndex(Istream& is)
{
    is >> offsets_;
}


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

Foam::CompactListList<Foam::label>
Foam::globalIndex::bin
(
    const labelUList& offsets,
    const labelUList& globalIds,
    labelList& order,
    DynamicList<label>& validBins
)
{
    Foam::sortedOrder(globalIds, order);
    validBins.clear();

    CompactListList<label> bins;

    if (!globalIds.empty())
    {
        labelList& binOffsets = bins.offsets();
        binOffsets.resize(offsets.size(), Foam::zero{});

        labelList& binValues = bins.values();
        binValues = UIndirectList<label>(globalIds, order);

        const label id = binValues[0];
        label proci = findLower(offsets, id+1);

        validBins.push_back(proci);
        label binSize = 1;

        for (label i = 1; i < order.size(); i++)
        {
            const label id = binValues[i];

            if (id < offsets[proci+1])
            {
                ++binSize;
            }
            else
            {
                // Not local. Reset proci
                const label oldProci = proci;
                proci = findLower(offsets, id+1);

                // Set offsets
                for (label j = oldProci+1; j < proci; ++j)
                {
                    binOffsets[j] = binOffsets[oldProci]+binSize;
                }
                binOffsets[proci] = i;
                validBins.push_back(proci);
                binSize = 1;
            }
        }

        for (label j = proci+1; j < binOffsets.size(); ++j)
        {
            binOffsets[j] = binOffsets[proci]+binSize;
        }
    }

    return bins;
}


void Foam::globalIndex::resize(const label n)
{
    if (n < 1)
    {
        offsets_.clear();
    }
    else
    {
        offsets_.resize(n+1, end_value());
    }
}


void Foam::globalIndex::reset
(
    const label localSize,
    const label comm,
    const bool parallel
)
{
    if (const label len = UPstream::nProcs(comm); len > 0)
    {
        labelList counts;

        if (parallel && UPstream::parRun())  // or UPstream::is_parallel(comm)
        {
            counts = UPstream::allGatherValues(localSize, comm);
        }
        else
        {
            // Non-parallel branch: use localSize on-proc, zero elsewhere
            // TBD: check for (proci >= 0) ?
            const auto proci = UPstream::myProcNo(comm);

            counts.resize(len, Foam::zero{});
            counts[proci] = localSize;
        }

        reset(counts, true);  // checkOverflow = true
    }
    else
    {
        // Nothing to do
        offsets_.clear();
    }
}


void Foam::globalIndex::reset
(
    const labelUList& counts,
    const bool checkOverflow
)
{
    if (const label len = counts.size(); len > 0)
    {
        offsets_.resize_nocopy(len+1);

        label start = 0;
        for (label i = 0; i < len; ++i)
        {
            const label count = counts[i];
            offsets_[i] = start;
            start += count;

            if (checkOverflow && start < offsets_[i])
            {
                reportOverflowAndExit(i, offsets_[i], count);
            }
        }
        offsets_[len] = start;
    }
    else
    {
        offsets_.clear();
    }
}


void Foam::globalIndex::setLocalSize(const label proci, const label len)
{
    if (proci >= 0 && proci+1 < offsets_.size() && len >= 0)
    {
        const label delta = (len - (offsets_[proci+1] - offsets_[proci]));

        // TBD: additional overflow check
        if (delta)
        {
            for (label i = proci+1; i < offsets_.size(); ++i)
            {
                offsets_[i] += delta;
            }
        }
    }
}


Foam::labelList Foam::globalIndex::localSizes() const
{
    labelList values;

    const label len = (offsets_.size() - 1);

    if (len < 1)
    {
        return values;
    }

    values.resize(len);

    for (label proci=0; proci < len; ++proci)
    {
        values[proci] = offsets_[proci+1] - offsets_[proci];
    }

    return values;
}


Foam::List<Foam::labelRange>
Foam::globalIndex::ranges() const
{
    List<labelRange> values;

    const label len = (offsets_.size() - 1);

    if (len < 1)
    {
        return values;
    }

    values.resize(len);

    for (label proci=0; proci < len; ++proci)
    {
        values[proci].reset
        (
            offsets_[proci],
            (offsets_[proci+1] - offsets_[proci])
        );
    }

    return values;
}


Foam::label Foam::globalIndex::maxNonLocalSize(const label proci) const
{
    const label len = (offsets_.size() - 1);

    if (len < 1)
    {
        return 0;
    }

    label maxLen = 0;

    for (label i=0; i < len; ++i)
    {
        if (i != proci)
        {
            const label count = (offsets_[i+1] - offsets_[i]);
            maxLen = std::max(maxLen, count);
        }
    }

    return maxLen;
}


Foam::labelRange Foam::globalIndex::front() const
{
    return
    (
        (offsets_.size() < 2)
      ? labelRange()
      : labelRange(Pair<label>(offsets_[0], offsets_[1]))
    );
}


Foam::labelRange Foam::globalIndex::back() const
{
    return
    (
        (offsets_.size() < 2)
      ? labelRange()
      : labelRange
        (
            Pair<label>
            (
                offsets_[offsets_.size()-2],
                offsets_[offsets_.size()-1]
            )
        )
    );
}


// * * * * * * * * * * * * * * * IOstream Operators  * * * * * * * * * * * * //

Foam::Istream& Foam::operator>>(Istream& is, globalIndex& gi)
{
    return is >> gi.offsets();
}


Foam::Ostream& Foam::operator<<(Ostream& os, const globalIndex& gi)
{
    return os << gi.offsets();
}


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