#include <Eigen/Core>

USING_PART_OF_NAMESPACE_EIGEN

namespace Eigen {

/* Echelon a matrix in-place:
 *
 * Meta-Unrolled version, for small fixed-size matrices
 */
template<typename Derived, int Step>
struct unroll_echelon
{
  enum { k = Step - 1,
         Rows = Derived::RowsAtCompileTime,
         Cols = Derived::ColsAtCompileTime,
         CornerRows = Rows - k,
         CornerCols = Cols - k
  };
  static void run(MatrixBase<Derived>& m)
  {
    unroll_echelon<Derived, Step-1>::run(m);
    int rowOfBiggest, colOfBiggest;
    m.template corner<CornerRows, CornerCols>(BottomRight)
     .cwise().abs()
     .maxCoeff(&rowOfBiggest, &colOfBiggest);
    m.row(k).swap(m.row(k+rowOfBiggest));
    m.col(k).swap(m.col(k+colOfBiggest));
    m.template corner<CornerRows-1, CornerCols>(BottomRight)
      -= m.col(k).template end<CornerRows-1>()
       * (m.row(k).template end<CornerCols>() / m(k,k));
  }
};

template<typename Derived>
struct unroll_echelon<Derived, 0>
{
  static void run(MatrixBase<Derived>& m) {}
};

/* Echelon a matrix in-place:
 *
 * Non-unrolled version, for dynamic-size matrices.
 * (this version works for all matrices, but in the fixed-size case the other
 * version is faster).
 */
template<typename Derived>
struct unroll_echelon<Derived, Dynamic>
{
  static void run(MatrixBase<Derived>& m)
  {
    for(int k = 0; k < m.diagonal().size() - 1; k++)
    {
      int rowOfBiggest, colOfBiggest;
      int cornerRows = m.rows()-k, cornerCols = m.cols()-k;
      m.corner(BottomRight, cornerRows, cornerCols)
      .cwise().abs()
      .maxCoeff(&rowOfBiggest, &colOfBiggest);
      m.row(k).swap(m.row(k+rowOfBiggest));
      m.col(k).swap(m.col(k+colOfBiggest));
      m.corner(BottomRight, cornerRows-1, cornerCols)
        -= m.col(k).end(cornerRows-1) * (m.row(k).end(cornerCols) / m(k,k));
    }
  }
};

using namespace std;
template<typename Derived>
void echelon(MatrixBase<Derived>& m)
{
  const int size = DiagonalCoeffs<Derived>::SizeAtCompileTime;
  const bool unroll = size <= 4;
  unroll_echelon<Derived, unroll ? size-1 : Dynamic>::run(m);
}

template<typename Derived>
void doSomeRankPreservingOperations(MatrixBase<Derived>& m)
{
  for(int a = 0; a < 3*(m.rows()+m.cols()); a++)
  {
    double d = ei_random<double>(-1,1);
    int i = ei_random<int>(0,m.rows()-1); // i is a random row number
    int j;
    do {
      j = ei_random<int>(0,m.rows()-1);
    } while (i==j); // j is another one (must be different)
    m.row(i) += d * m.row(j);

    i = ei_random<int>(0,m.cols()-1); // i is a random column number
    do {
      j = ei_random<int>(0,m.cols()-1);
    } while (i==j); // j is another one (must be different)
    m.col(i) += d * m.col(j);
  }
}

} // namespace Eigen

using namespace std;

int main(int, char **)
{
  srand((unsigned int)time(0));
  const int Rows = 6, Cols = 4;
  typedef Matrix<double, Rows, Cols> Mat;
  const int N = Rows < Cols ? Rows : Cols;

  // start with a matrix m that's obviously of rank N-1
  Mat m = Mat::identity(Rows, Cols); // args just in case of dyn. size
  m.row(0) = m.row(1) = m.row(0) + m.row(1);

  doSomeRankPreservingOperations(m);

  // now m is still a matrix of rank N-1
  cout << "Here's the matrix m:" << endl << m << endl;

  cout << "Now let's echelon m (repeating many times for benchmarking purposes):" << endl;
  for(int i = 0; i < 1000000; i++) echelon(m);

  cout << "Now m is:" << endl << m << endl;
}
