// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2012 Désiré Nuentsa-Wakam <desire.nuentsa_wakam@inria.fr>
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.

/*

 * NOTE: This file is the modified version of [s,d,c,z]panel_dfs.c file in SuperLU

 * -- SuperLU routine (version 2.0) --
 * Univ. of California Berkeley, Xerox Palo Alto Research Center,
 * and Lawrence Berkeley National Lab.
 * November 15, 1997
 *
 * Copyright (c) 1994 by Xerox Corporation.  All rights reserved.
 *
 * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY
 * EXPRESSED OR IMPLIED.  ANY USE IS AT YOUR OWN RISK.
 *
 * Permission is hereby granted to use or copy this program for any
 * purpose, provided the above notices are retained on all copies.
 * Permission to modify the code and to distribute modified code is
 * granted, provided the above notices are retained, and a notice that
 * the code was modified is included with the above copyright notice.
 */
#ifndef SPARSELU_PANEL_DFS_H
#define SPARSELU_PANEL_DFS_H

// IWYU pragma: private
#include "./InternalHeaderCheck.h"

namespace Eigen {

namespace internal {

template <typename IndexVector>
struct panel_dfs_traits {
  typedef typename IndexVector::Scalar StorageIndex;
  panel_dfs_traits(Index jcol, StorageIndex* marker) : m_jcol(jcol), m_marker(marker) {}
  bool update_segrep(Index krep, StorageIndex jj) {
    if (m_marker[krep] < m_jcol) {
      m_marker[krep] = jj;
      return true;
    }
    return false;
  }
  void mem_expand(IndexVector& /*glu.lsub*/, Index /*nextl*/, Index /*chmark*/) {}
  enum { ExpandMem = false };
  Index m_jcol;
  StorageIndex* m_marker;
};

template <typename Scalar, typename StorageIndex>
template <typename Traits>
void SparseLUImpl<Scalar, StorageIndex>::dfs_kernel(const StorageIndex jj, IndexVector& perm_r, Index& nseg,
                                                    IndexVector& panel_lsub, IndexVector& segrep,
                                                    Ref<IndexVector> repfnz_col, IndexVector& xprune,
                                                    Ref<IndexVector> marker, IndexVector& parent, IndexVector& xplore,
                                                    GlobalLU_t& glu, Index& nextl_col, Index krow, Traits& traits) {
  StorageIndex kmark = marker(krow);

  // For each unmarked krow of jj
  marker(krow) = jj;
  StorageIndex kperm = perm_r(krow);
  if (kperm == emptyIdxLU) {
    // krow is in L : place it in structure of L(*, jj)
    panel_lsub(nextl_col++) = StorageIndex(krow);  // krow is indexed into A

    traits.mem_expand(panel_lsub, nextl_col, kmark);
  } else {
    // krow is in U : if its supernode-representative krep
    // has been explored, update repfnz(*)
    // krep = supernode representative of the current row
    StorageIndex krep = glu.xsup(glu.supno(kperm) + 1) - 1;
    // First nonzero element in the current column:
    StorageIndex myfnz = repfnz_col(krep);

    if (myfnz != emptyIdxLU) {
      // Representative visited before
      if (myfnz > kperm) repfnz_col(krep) = kperm;

    } else {
      // Otherwise, perform dfs starting at krep
      StorageIndex oldrep = emptyIdxLU;
      parent(krep) = oldrep;
      repfnz_col(krep) = kperm;
      StorageIndex xdfs = glu.xlsub(krep);
      Index maxdfs = xprune(krep);

      StorageIndex kpar;
      do {
        // For each unmarked kchild of krep
        while (xdfs < maxdfs) {
          StorageIndex kchild = glu.lsub(xdfs);
          xdfs++;
          StorageIndex chmark = marker(kchild);

          if (chmark != jj) {
            marker(kchild) = jj;
            StorageIndex chperm = perm_r(kchild);

            if (chperm == emptyIdxLU) {
              // case kchild is in L: place it in L(*, j)
              panel_lsub(nextl_col++) = kchild;
              traits.mem_expand(panel_lsub, nextl_col, chmark);
            } else {
              // case kchild is in U :
              // chrep = its supernode-rep. If its rep has been explored,
              // update its repfnz(*)
              StorageIndex chrep = glu.xsup(glu.supno(chperm) + 1) - 1;
              myfnz = repfnz_col(chrep);

              if (myfnz != emptyIdxLU) {  // Visited before
                if (myfnz > chperm) repfnz_col(chrep) = chperm;
              } else {  // Cont. dfs at snode-rep of kchild
                xplore(krep) = xdfs;
                oldrep = krep;
                krep = chrep;  // Go deeper down G(L)
                parent(krep) = oldrep;
                repfnz_col(krep) = chperm;
                xdfs = glu.xlsub(krep);
                maxdfs = xprune(krep);

              }  // end if myfnz != -1
            }    // end if chperm == -1

          }  // end if chmark !=jj
        }    // end while xdfs < maxdfs

        // krow has no more unexplored nbrs :
        //    Place snode-rep krep in postorder DFS, if this
        //    segment is seen for the first time. (Note that
        //    "repfnz(krep)" may change later.)
        //    Baktrack dfs to its parent
        if (traits.update_segrep(krep, jj))
        // if (marker1(krep) < jcol )
        {
          segrep(nseg) = krep;
          ++nseg;
          // marker1(krep) = jj;
        }

        kpar = parent(krep);            // Pop recursion, mimic recursion
        if (kpar == emptyIdxLU) break;  // dfs done
        krep = kpar;
        xdfs = xplore(krep);
        maxdfs = xprune(krep);

      } while (kpar != emptyIdxLU);  // Do until empty stack

    }  // end if (myfnz = -1)

  }  // end if (kperm == -1)
}

/**
 * \brief Performs a symbolic factorization on a panel of columns [jcol, jcol+w)
 *
 * A supernode representative is the last column of a supernode.
 * The nonzeros in U[*,j] are segments that end at supernodes representatives
 *
 * The routine returns a list of the supernodal representatives
 * in topological order of the dfs that generates them. This list is
 * a superset of the topological order of each individual column within
 * the panel.
 * The location of the first nonzero in each supernodal segment
 * (supernodal entry location) is also returned. Each column has
 * a separate list for this purpose.
 *
 * Two markers arrays are used for dfs :
 *    marker[i] == jj, if i was visited during dfs of current column jj;
 *    marker1[i] >= jcol, if i was visited by earlier columns in this panel;
 *
 * \param[in] m number of rows in the matrix
 * \param[in] w Panel size
 * \param[in] jcol Starting  column of the panel
 * \param[in] A Input matrix in column-major storage
 * \param[in] perm_r Row permutation
 * \param[out] nseg Number of U segments
 * \param[out] dense Accumulate the column vectors of the panel
 * \param[out] panel_lsub Subscripts of the row in the panel
 * \param[out] segrep Segment representative i.e first nonzero row of each segment
 * \param[out] repfnz First nonzero location in each row
 * \param[out] xprune The pruned elimination tree
 * \param[out] marker work vector
 * \param  parent The elimination tree
 * \param xplore work vector
 * \param glu The global data structure
 *
 */

template <typename Scalar, typename StorageIndex>
void SparseLUImpl<Scalar, StorageIndex>::panel_dfs(const Index m, const Index w, const Index jcol, MatrixType& A,
                                                   IndexVector& perm_r, Index& nseg, ScalarVector& dense,
                                                   IndexVector& panel_lsub, IndexVector& segrep, IndexVector& repfnz,
                                                   IndexVector& xprune, IndexVector& marker, IndexVector& parent,
                                                   IndexVector& xplore, GlobalLU_t& glu) {
  Index nextl_col;  // Next available position in panel_lsub[*,jj]

  // Initialize pointers
  VectorBlock<IndexVector> marker1(marker, m, m);
  nseg = 0;

  panel_dfs_traits<IndexVector> traits(jcol, marker1.data());

  // For each column in the panel
  for (StorageIndex jj = StorageIndex(jcol); jj < jcol + w; jj++) {
    nextl_col = (jj - jcol) * m;

    VectorBlock<IndexVector> repfnz_col(repfnz, nextl_col, m);  // First nonzero location in each row
    VectorBlock<ScalarVector> dense_col(dense, nextl_col, m);   // Accumulate a column vector here

    // For each nnz in A[*, jj] do depth first search
    for (typename MatrixType::InnerIterator it(A, jj); it; ++it) {
      Index krow = it.row();
      dense_col(krow) = it.value();

      StorageIndex kmark = marker(krow);
      if (kmark == jj) continue;  // krow visited before, go to the next nonzero

      dfs_kernel(jj, perm_r, nseg, panel_lsub, segrep, repfnz_col, xprune, marker, parent, xplore, glu, nextl_col, krow,
                 traits);
    }  // end for nonzeros in column jj

  }  // end for column jj
}

}  // end namespace internal
}  // end namespace Eigen

#endif  // SPARSELU_PANEL_DFS_H
