//------------------------------------------------------------------------------
// CHOLMOD/Cholesky/cholmod_postorder: postordering of a tree
//------------------------------------------------------------------------------

// CHOLMOD/Cholesky Module.  Copyright (C) 2005-2023, Timothy A. Davis
// All Rights Reserved.
// SPDX-License-Identifier: LGPL-2.1+

//------------------------------------------------------------------------------

// Compute the postorder of a tree.

#include "cholmod_internal.h"

#ifndef NCHOLESKY

//------------------------------------------------------------------------------
// dfs
//------------------------------------------------------------------------------

// The code below includes both a recursive and non-recursive depth-first-search
// of a tree.  The recursive code is simpler, but can lead to stack overflow.
// It is left here for reference, to understand what the non-recursive code
// is computing.  To try the recursive version, uncomment the following
// #define, or compile the code with -DRECURSIVE.  Be aware that stack
// overflow may occur.
// #define RECURSIVE
//

#ifdef RECURSIVE

// recursive version: a working code for reference only, not actual use

static Int dfs          // return the new value of k
(
    Int p,              // start a DFS at node p
    Int k,              // start the node numbering at k
    Int Post [ ],       // Post ordering, modified on output
    Int Head [ ],       // Head [p] = youngest child of p; EMPTY on output
    Int Next [ ],       // Next [j] = sibling of j; unmodified
    Int Pstack [ ]      // unused
)
{
    Int j ;
    // start a DFS at each child of node p
    for (j = Head [p] ; j != EMPTY ; j = Next [j])
    {
        // start a DFS at child node j
        k = dfs (j, k, Post, Head, Next, Pstack) ;
    }
    Post [k++] = p ;    // order node p as the kth node
    Head [p] = EMPTY ;  // link list p no longer needed
    return (k) ;        // the next node will be numbered k
}

#else

// non-recursive version for actual use

static Int dfs          // return the new value of k
(
    Int p,              // start the DFS at a root node p
    Int k,              // start the node numbering at k
    Int Post [ ],       // Post ordering, modified on output
    Int Head [ ],       // Head [p] = youngest child of p; EMPTY on output
    Int Next [ ],       // Next [j] = sibling of j; unmodified
    Int Pstack [ ]      // workspace of size n, undefined on input or output
)
{
    Int j, phead ;

    // put the root node on the stack
    Pstack [0] = p ;
    phead = 0 ;

    // while the stack is not empty, do:
    while (phead >= 0)
    {
        // grab the node p from top of the stack and get its youngest child j
        p = Pstack [phead] ;
        j = Head [p] ;
        if (j == EMPTY)
        {
            // all children of p ordered.  remove p from stack and order it
            phead-- ;
            Post [k++] = p ;    // order node p as the kth node
        }
        else
        {
            // leave p on the stack.  Start a DFS at child node j by putting
            // j on the stack and removing j from the list of children of p.
            Head [p] = Next [j] ;
            Pstack [++phead] = j ;
        }
    }
    return (k) ;        // the next node will be numbered k
}

#endif

//------------------------------------------------------------------------------
// cholmod_postorder
//------------------------------------------------------------------------------

// Postorder a tree.  The tree is either an elimination tree (the output from
// from cholmod_etree) or a component tree (from cholmod_nested_dissection).
//
// An elimination tree is a complete tree of n nodes with Parent [j] > j or
// Parent [j] = EMPTY if j is a root.  On output Post [0..n-1] is a complete
// permutation vector.
//
// A component tree is a subset of 0..n-1.  Parent [j] = -2 if node j is not
// in the component tree.  Parent [j] = EMPTY if j is a root of the component
// tree, and Parent [j] is in the range 0 to n-1 if j is in the component
// tree but not a root.  On output, Post [k] is defined only for nodes in
// the component tree.  Post [k] = j if node j is the kth node in the
// postordered component tree, where k is in the range 0 to the number of
// components minus 1.
//
// Node j is ignored and not included in the postorder if Parent [j] < EMPTY.
//
// As a result, check_parent (Parent, n,...) may fail on input, since
// cholmod_check_parent assumes Parent is an elimination tree.  Similarly,
// cholmod_check_perm (Post, ...) may fail on output, since Post is a partial
// permutation if Parent is a component tree.
//
// An optional node weight can be given.  When starting a postorder at node j,
// the children of j are ordered in increasing order of their weight.
// If no weights are given (Weight is NULL) then children are ordered in
// increasing order of their node number.  The weight of a node must be in the
// range 0 to n-1.  Weights outside that range are silently converted to that
// range (weights < 0 are treated as zero, and weights >= n are treated as n-1).
//
//
// workspace: Head (n), Iwork (2*n)

Int CHOLMOD(postorder)  // return # of nodes postordered
(
    // input:
    Int *Parent,        // size n. Parent [j] = p if p is the parent of j
    size_t n_input,
    Int *Weight,        // size n, optional. Weight [j] is weight of node j
    // output:
    Int *Post,          // size n. Post [k] = j is kth in postordered tree
    cholmod_common *Common
)
{

    //--------------------------------------------------------------------------
    // check inputs
    //--------------------------------------------------------------------------

    Int *Head, *Next, *Pstack, *Iwork ;
    Int j, p, k, w, nextj ;
    size_t s ;
    int ok = TRUE ;
    Int n = (Int) n_input ;

    RETURN_IF_NULL_COMMON (EMPTY) ;
    RETURN_IF_NULL (Parent, EMPTY) ;
    RETURN_IF_NULL (Post, EMPTY) ;
    Common->status = CHOLMOD_OK ;

    //--------------------------------------------------------------------------
    // allocate workspace
    //--------------------------------------------------------------------------

    // s = 2*n
    s = CHOLMOD(mult_size_t) (n_input, (size_t) 2, &ok) ;
    if (!ok)
    {
        ERROR (CHOLMOD_TOO_LARGE, "problem too large") ;
        return (EMPTY) ;
    }

    CHOLMOD(allocate_work) (n, s, 0, Common) ;
    if (Common->status < CHOLMOD_OK)
    {
        return (EMPTY) ;
    }
    ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, 0, Common)) ;

    //--------------------------------------------------------------------------
    // get inputs
    //--------------------------------------------------------------------------

    Head  = Common->Head ;      // size n+1, initially all EMPTY
    Iwork = Common->Iwork ;
    Next  = Iwork ;             // size n
    Pstack = Iwork + n ;        // size n

    //--------------------------------------------------------------------------
    // construct a link list of children for each node
    //--------------------------------------------------------------------------

    if (Weight == NULL)
    {

        // in reverse order so children are in ascending order in each list
        for (j = n-1 ; j >= 0 ; j--)
        {
            p = Parent [j] ;
            if (p >= 0 && p < ((Int) n))
            {
                // add j to the list of children for node p
                Next [j] = Head [p] ;
                Head [p] = j ;
            }
        }

        // Head [p] = j if j is the youngest (least-numbered) child of p
        // Next [j1] = j2 if j2 is the next-oldest sibling of j1

    }
    else
    {

        // First, construct a set of link lists according to Weight.
        //
        // Whead [w] = j if node j is the first node in bucket w.
        // Next [j1] = j2 if node j2 follows j1 in a link list.

        Int *Whead = Pstack ;       // use Pstack as workspace for Whead [

        for (w = 0 ; w < ((Int) n) ; w++)
        {
            Whead [w] = EMPTY ;
        }
        // do in forward order, so nodes that ties are ordered by node index
        for (j = 0 ; j < ((Int) n) ; j++)
        {
            p = Parent [j] ;
            if (p >= 0 && p < ((Int) n))
            {
                w = Weight [j] ;
                w = MAX (0, w) ;
                w = MIN (w, ((Int) n) - 1) ;
                // place node j at the head of link list for weight w
                Next [j] = Whead [w] ;
                Whead [w] = j ;
            }
        }

        // traverse weight buckets, placing each node in its parent's list
        for (w = n-1 ; w >= 0 ; w--)
        {
            for (j = Whead [w] ; j != EMPTY ; j = nextj)
            {
                nextj = Next [j] ;
                // put node j in the link list of its parent
                p = Parent [j] ;
                ASSERT (p >= 0 && p < ((Int) n)) ;
                Next [j] = Head [p] ;
                Head [p] = j ;
            }
        }

        // Whead no longer needed ]
        // Head [p] = j if j is the lightest child of p
        // Next [j1] = j2 if j2 is the next-heaviest sibling of j1
    }

    //--------------------------------------------------------------------------
    // start a DFS at each root node of the etree
    //--------------------------------------------------------------------------

    k = 0 ;
    for (j = 0 ; j < ((Int) n) ; j++)
    {
        if (Parent [j] == EMPTY)
        {
            // j is the root of a tree; start a DFS here
            k = dfs (j, k, Post, Head, Next, Pstack) ;
        }
    }

    // this would normally be EMPTY already, unless Parent is invalid
    for (j = 0 ; j < ((Int) n) ; j++)
    {
        Head [j] = EMPTY ;
    }

    PRINT1 (("postordered "ID" nodes\n", k)) ;
    ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, 0, Common)) ;
    return (k) ;
}
#endif

