// 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/.
#ifndef METIS_SUPPORT_H
#define METIS_SUPPORT_H

namespace Eigen {
/**
 * Get the fill-reducing ordering from the METIS package
 *
 * If A is the original matrix and Ap is the permuted matrix,
 * the fill-reducing permutation is defined as follows :
 * Row (column) i of A is the matperm(i) row (column) of Ap.
 * WARNING: As computed by METIS, this corresponds to the vector iperm (instead of perm)
 */
template <typename StorageIndex>
class MetisOrdering
{
  public:
    typedef PermutationMatrix<Dynamic,Dynamic,StorageIndex> PermutationType;
    typedef Matrix<StorageIndex,Dynamic,1> IndexVector;

    template <typename MatrixType>
    void
    get_symmetrized_graph(const MatrixType& A)
    {
        Index m = A.cols();
        eigen_assert((A.rows() == A.cols()) && "ONLY FOR SQUARED MATRICES");
        // Get the transpose of the input matrix
        MatrixType At = A.transpose();
        // Get the number of nonzeros elements in each row/col of At+A
        Index TotNz = 0;
        IndexVector visited(m);
        visited.setConstant(-1);

        for (StorageIndex j = 0; j < m; j++)
        {
            // Compute the union structure of of A(j,:) and At(j,:)
            visited(j) = j; // Do not include the diagonal element

            // Get the nonzeros in row/column j of A
            for (typename MatrixType::InnerIterator it(A, j); it; ++it)
            {
                Index idx = it.index(); // Get the row index (for column major) or column index (for row major)

                if (visited(idx) != j )
                {
                    visited(idx) = j;
                    ++TotNz;
                }
            }

            //Get the nonzeros in row/column j of At
            for (typename MatrixType::InnerIterator it(At, j); it; ++it)
            {
                Index idx = it.index();

                if (visited(idx) != j)
                {
                    visited(idx) = j;
                    ++TotNz;
                }
            }
        }

        // Reserve place for A + At
        m_indexPtr.resize(m+1);
        m_innerIndices.resize(TotNz);

        // Now compute the real adjacency list of each column/row
        visited.setConstant(-1);
        StorageIndex CurNz = 0;

        for (StorageIndex j = 0; j < m; j++)
        {
            m_indexPtr(j) = CurNz;

            visited(j) = j; // Do not include the diagonal element

            // Add the pattern of row/column j of A to A+At
            for (typename MatrixType::InnerIterator it(A,j); it; ++it)
            {
                StorageIndex idx = it.index(); // Get the row index (for column major) or column index (for row major)

                if (visited(idx) != j )
                {
                    visited(idx) = j;
                    m_innerIndices(CurNz) = idx;
                    CurNz++;
                }
            }

            //Add the pattern of row/column j of At to A+At
            for (typename MatrixType::InnerIterator it(At, j); it; ++it)
            {
                StorageIndex idx = it.index();

                if (visited(idx) != j)
                {
                    visited(idx) = j;
                    m_innerIndices(CurNz) = idx;
                    ++CurNz;
                }
            }
        }

        m_indexPtr(m) = CurNz;
    }

    template <typename MatrixType>
    void
    operator() (const MatrixType& A, PermutationType& matperm)
    {
        StorageIndex m = internal::convert_index<StorageIndex>(A.cols()); // must be StorageIndex, because it is passed by address to METIS
        IndexVector perm(m),iperm(m);
        // First, symmetrize the matrix graph.
        get_symmetrized_graph(A);
        int output_error;

        // Call the fill-reducing routine from METIS
        output_error = METIS_NodeND(&m, m_indexPtr.data(), m_innerIndices.data(), NULL, NULL, perm.data(), iperm.data());

        if (output_error != METIS_OK)
        {
            //FIXME The ordering interface should define a class of possible errors
            std::cerr << "ERROR WHILE CALLING THE METIS PACKAGE \n";
            return;
        }

        // Get the fill-reducing permutation
        //NOTE:  If Ap is the permuted matrix then perm and iperm vectors are defined as follows
        // Row (column) i of Ap is the perm(i) row(column) of A, and row (column) i of A is the iperm(i) row(column) of Ap

        matperm.resize(m);

        for (int j = 0; j < m; j++)
        {
            matperm.indices()(iperm(j)) = j;
        }

    }

  protected:
    IndexVector m_indexPtr; // Pointer to the adjacenccy list of each row/column
    IndexVector m_innerIndices; // Adjacency list
};

}// end namespace eigen
#endif
