/* ========================================================================== */
/* === Source/Mongoose_BoundaryHeap.cpp ===================================== */
/* ========================================================================== */

/* -----------------------------------------------------------------------------
 * Mongoose Graph Partitioning Library, Copyright (C) 2017-2018,
 * Scott P. Kolodziej, Nuri S. Yeralan, Timothy A. Davis, William W. Hager
 * Mongoose is licensed under Version 3 of the GNU General Public License.
 * Mongoose is also available under other licenses; contact authors for details.
 * SPDX-License-Identifier: GPL-3.0-only
 * -------------------------------------------------------------------------- */

#include "Mongoose_BoundaryHeap.hpp"
#include "Mongoose_CutCost.hpp"
#include "Mongoose_Debug.hpp"
#include "Mongoose_Internal.hpp"
#include "Mongoose_Logger.hpp"

namespace Mongoose
{

//-----------------------------------------------------------------------------
// This function inserts the specified vertex into the graph
//-----------------------------------------------------------------------------
void bhLoad(EdgeCutProblem *graph, const EdgeCut_Options *options)
{
    /* Load the boundary heaps. */
    Int n               = graph->n;
    Int *Gp             = graph->p;
    Int *Gi             = graph->i;
    double *Gx          = graph->x;
    double *Gw          = graph->w;
    bool *partition     = graph->partition;
    double *gains       = graph->vertexGains;
    Int *externalDegree = graph->externalDegree;

    /* Keep track of the cut cost. */
    CutCost cost;
    cost.heuCost   = 0.0;
    cost.cutCost   = 0.0;
    cost.W[0]      = 0.0;
    cost.W[1]      = 0.0;
    cost.imbalance = 0.0;

    /* Compute the gains & discover if the vertex is on the boundary. */
    for (Int k = 0; k < n; k++)
    {
        bool kPartition = partition[k];
        cost.W[kPartition] += (Gw) ? Gw[k] : 1;

        double gain = 0.0;
        Int exD     = 0;
        for (Int p = Gp[k]; p < Gp[k + 1]; p++)
        {
            double edgeWeight = (Gx) ? Gx[p] : 1;
            bool onSameSide   = (kPartition == partition[Gi[p]]);
            gain += (onSameSide ? -edgeWeight : edgeWeight);
            if (!onSameSide)
            {
                exD++;
                cost.cutCost += edgeWeight;
            }
        }
        gains[k]          = gain;
        externalDegree[k] = exD;
        if (exD > 0)
            bhInsert(graph, k);
    }

    /* Save the cut cost to the graph. */
    graph->cutCost = cost.cutCost;
    graph->W0      = cost.W[0];
    graph->W1      = cost.W[1];

    double targetSplit = options->target_split;
    ASSERT(targetSplit > 0);
    ASSERT(targetSplit <= 0.5);

    graph->imbalance = targetSplit - std::min(graph->W0, graph->W1) / graph->W;
    graph->heuCost   = (graph->cutCost
                      + (fabs(graph->imbalance) > options->soft_split_tolerance
                             ? fabs(graph->imbalance) * graph->H
                             : 0.0));
}

//-----------------------------------------------------------------------------
// This function inserts the specified vertex into the graph's boundary heap
//-----------------------------------------------------------------------------
void bhInsert(EdgeCutProblem *graph, Int vertex)
{
    /* Unpack structures */
    Int vp        = graph->partition[vertex];
    Int *bhHeap   = graph->bhHeap[vp];
    Int size      = graph->bhSize[vp];
    double *gains = graph->vertexGains;

    bhHeap[size] = vertex;
    graph->BH_putIndex(vertex, size);

    heapifyUp(graph, bhHeap, gains, vertex, size, gains[vertex]);

    /* Save the size. */
    graph->bhSize[vp] = size + 1;
}

//-----------------------------------------------------------------------------
// Removes the specified vertex from its heap.
// To do this, we swap the last element in the heap with the element we
// want to remove. Then we heapify up and heapify down.
//-----------------------------------------------------------------------------
void bhRemove(EdgeCutProblem *graph, const EdgeCut_Options *options, Int vertex, double gain,
              bool partition, Int bhPosition)
{
    (void)options; // Unused variable
    (void)gain;    // Unused variable

    double *gains = graph->vertexGains;
    Int *bhIndex  = graph->bhIndex;
    Int *bhHeap   = graph->bhHeap[partition];
    Int size      = (--graph->bhSize[partition]);

    /* If we removed the last position in the heap, there's nothing to do. */
    if (bhPosition == size)
    {
        bhIndex[vertex] = 0;
        return;
    }

    /* Replace the vertex with the last element in the heap. */
    Int v = bhHeap[bhPosition] = bhHeap[size];
    graph->BH_putIndex(v, bhPosition);

    /* Finish the delete of "vertex" from the heap. */
    bhIndex[vertex] = 0;

    /* Bubble up then bubble down with a reread of v because it may have
     * changed during heapifyUp. */
    heapifyUp(graph, bhHeap, gains, v, bhPosition, gains[v]);
    v = bhHeap[bhPosition];
    heapifyDown(graph, bhHeap, size, gains, v, bhPosition, gains[v]);
}

//-----------------------------------------------------------------------------
// Starting at a position, this function will heapify from a vertex upwards
//-----------------------------------------------------------------------------
void heapifyUp(EdgeCutProblem *graph, Int *bhHeap, double *gains, Int vertex,
               Int position, double gain)
{
    if (position == 0)
        return;

    Int posParent = graph->BH_getParent(position);
    Int pVertex   = bhHeap[posParent];
    double pGain  = gains[pVertex];

    /* If we need to swap this vertex with the parent then: */
    if (pGain < gain)
    {
        bhHeap[posParent] = vertex;
        bhHeap[position]  = pVertex;
        graph->BH_putIndex(vertex, posParent);
        graph->BH_putIndex(pVertex, position);
        heapifyUp(graph, bhHeap, gains, vertex, posParent, gain);
    }
}

//-----------------------------------------------------------------------------
// Starting at a position, this function will heapify from a vertex downwards
//-----------------------------------------------------------------------------
void heapifyDown(EdgeCutProblem *graph, Int *bhHeap, Int size, double *gains, Int vertex,
                 Int position, double gain)
{
    if (position >= size)
        return;

    Int lp = graph->BH_getLeftChild(position);
    Int rp = graph->BH_getRightChild(position);

    Int lv = (lp < size ? bhHeap[lp] : -1);
    Int rv = (rp < size ? bhHeap[rp] : -1);

    double lg = (lv >= 0 ? gains[lv] : -INFINITY);
    double rg = (rv >= 0 ? gains[rv] : -INFINITY);

    if (gain < lg || gain < rg)
    {
        if (lg > rg)
        {
            bhHeap[position] = lv;
            graph->BH_putIndex(lv, position);
            bhHeap[lp] = vertex;
            graph->BH_putIndex(vertex, lp);
            heapifyDown(graph, bhHeap, size, gains, vertex, lp, gain);
        }
        else
        {
            bhHeap[position] = rv;
            graph->BH_putIndex(rv, position);
            bhHeap[rp] = vertex;
            graph->BH_putIndex(vertex, rp);
            heapifyDown(graph, bhHeap, size, gains, vertex, rp, gain);
        }
    }
}

} // end namespace Mongoose
