////////////////////////////////////////////////////////////////////////////////
// 
// Matrix.cc
//
//    produced: 13/03/98 jr
// 
////////////////////////////////////////////////////////////////////////////////
#include <iostream>
#include <ctype.h>
#include <string.h>
#include <iostream>

#include "Matrix.hh"

namespace topcom {

  const parameter_type Matrix::col_capacity = 0;

  const Vector Matrix::row(const parameter_type index) const {
    Vector result(coldim());
    for (parameter_type i = 0; i < coldim(); ++i) {
      result[i] = (*this)(index, i);
    }
    return result;
  }
  void Matrix::row_resize(const parameter_type new_memsize, const Field& init_entry) {
    for (parameter_type i = 0; i < coldim(); ++i) {
      this->col(i).resize(new_memsize,init_entry);
    }
  }
  Matrix& Matrix::canonicalize() {
    for (parameter_type i = 0; i < coldim(); ++i) {
      this->col(i).canonicalize();
    }
    return *this;  
  }

  Matrix& Matrix::add(const Matrix& matrix) {
#ifdef INDEX_CHECK
    assert((rowdim() == matrix.rowdim()) && (coldim() == matrix.coldim()));
#endif
    for (parameter_type i = 0; i < coldim(); ++i) {
      this->col(i).add(matrix.col(i));
    }
    return *this;
  }
  Matrix& Matrix::scale(const Field& entry) {
    for (parameter_type i = 0; i < coldim(); ++i) {
      this->col(i).scale(entry);
    }
    return *this;
  }

  Matrix& Matrix::augment(const Matrix& matrix, const IntegerSet& ignored_cols) {
#ifdef INDEX_CHECK
    if (coldim() > 0) {
      assert(rowdim() == matrix.rowdim());
    }
#endif
#ifdef TOPCOM_CONTAINERS  
    push_back(matrix);
#else
    // reserve(coldim() + matrix.coldim());
    for (parameter_type col = 0; col < matrix.coldim(); ++col) {
      if (ignored_cols.contains(col)) {
	continue;
      }
      push_back(matrix.col(col));
    }
#endif
    return *this;
  }

  Matrix& Matrix::pop_back() {
    if (empty()) {
      return *this;
    }
    resize(coldim() - 1);
    return *this;
  }
  
  Matrix& Matrix::stack(const Matrix& matrix) {
#ifdef INDEX_CHECK
    assert(coldim() == matrix.coldim());
#endif
    for (parameter_type i = 0; i < coldim(); ++i) {
      this->col(i).stack(matrix.col(i));
    }
    return *this;
  }

  Matrix& Matrix::swap_cols(const parameter_type i, const parameter_type j) {
#ifdef INDEX_CHECK
    assert((i < coldim()) && (j < coldim()));
#endif
    this->col(i).swap(this->col(j));
    // const Vector tmp = (*this)[i];
    // (*this)[i] = (*this)[j];
    // (*this)[j] = tmp;
    return *this;
  }
  
  Matrix& Matrix::swap_rows(const parameter_type i, const parameter_type j) {
#ifdef INDEX_CHECK
    assert((i < rowdim()) && (j < rowdim()));
#endif
    for (parameter_type k = 0; k < coldim(); ++k) {
      std::swap((*this)(i, k), (*this)(j, k));
    }
    return *this;
  }
  
  Matrix& Matrix::row_echelon_form(const parameter_type start_row, const parameter_type start_col, const Field& scale) {
#ifdef SUPER_VERBOSE
    MessageStreams::debug() << message::lock;
    MessageStreams::debug() << "calling elimination of" << std::endl;
    this->pretty_print(MessageStreams::debug());
    MessageStreams::debug() << "below row " << start_row << " to the right of column " << start_col << std::endl;
    MessageStreams::debug() << message::unlock;
#endif

    // swap row with left-most non-zero to the top at start_row:
#ifdef SUPER_VERBOSE
    MessageStreams::debug() << message::lock;
    MessageStreams::debug() << "swapping left-most non-zero to the top ..." << std::endl;
    MessageStreams::debug() << message::unlock;
#endif
    if ((start_row == rowdim()) || (start_col == coldim())) {
      return *this;
    }
    parameter_type minnonzerocol(coldim());
    parameter_type minnonzerocolrow(rowdim());
    Field new_scale(scale);
    for (parameter_type i = start_row; i < rowdim(); ++i) {
      for (parameter_type j = start_col; j < coldim(); ++j) {
	if (((*this)(i, j) != FieldConstants::ZERO) && (j < minnonzerocol)) {
	  minnonzerocol = j;
	  minnonzerocolrow = i;
	}
      }
    }
    if (minnonzerocolrow != start_row) {
#ifdef SUPER_VERBOSE
      MessageStreams::debug() << message::lock;
      MessageStreams::debug() << "... by swapping row " << start_row << " and row " << minnonzerocolrow << " ..." << std::endl;
      MessageStreams::debug() << message::unlock;
#endif
      swap_rows(minnonzerocolrow, start_row);
      new_scale = new_scale * FieldConstants::MINUSONE;
    }
#ifdef SUPER_VERBOSE
    MessageStreams::debug() << message::lock;
    this->pretty_print(MessageStreams::debug());
    MessageStreams::debug() << "... done." << std::endl;
    MessageStreams::debug() << message::unlock;
#endif
  
    // eliminate under left-most non-zero:
#ifdef SUPER_VERBOSE
    MessageStreams::debug() << message::lock;
    MessageStreams::debug() << "eliminating all non-zeros below element at index (" << start_row << "," << minnonzerocol << ") ..." << std::endl;
    MessageStreams::debug() << message::unlock;
#endif
    const Field eraser((*this)(start_row, minnonzerocol));
    for (parameter_type i = start_row + 1; i < rowdim(); ++i) {
      if ((*this)(i, minnonzerocol) != FieldConstants::ZERO) {
#ifdef SUPER_VERBOSE
	MessageStreams::debug() << message::lock;
	MessageStreams::debug() << "\t eliminating row " << i << " ..." << std::endl;
	MessageStreams::debug() << message::unlock;
#endif
	const Field delinquent((*this)(i, minnonzerocol));
	const Field multiplier = delinquent / eraser;

	for (parameter_type j = minnonzerocol; j < coldim(); ++j) {
	  (*this)(i,j) -= multiplier * (*this)(start_row,j);
	}
#ifdef SUPER_VERBOSE
	MessageStreams::debug() << message::lock;
	this->pretty_print(MessageStreams::debug());
	MessageStreams::debug() << "\t ... done." << std::endl;
	MessageStreams::debug() << message::unlock;
#endif
      }
    }
#ifdef SUPER_VERBOSE
    MessageStreams::debug() << message::lock;
    this->pretty_print(MessageStreams::debug());
    MessageStreams::debug() << "... done." << std::endl;
    MessageStreams::debug() << message::unlock;
#endif
    
    // delete trailing zero rows below start_row:
#ifdef SUPER_VERBOSE
    MessageStreams::debug() << message::lock;
    MessageStreams::debug() << "deleting all-zero rows ..." << std::endl;
    MessageStreams::debug() << message::unlock;
#endif
    while (row(rowdim() - 1).is_zero()) {
#ifdef SUPER_VERBOSE
      MessageStreams::debug() << message::lock;
      MessageStreams::debug() << "\t deleting row " << rowdim() - 1 << " ..." << std::endl;
      MessageStreams::debug() << message::unlock;
#endif
      row_resize(rowdim() - 1);
    }
#ifdef SUPER_VERBOSE
    MessageStreams::debug() << message::lock;
    this->pretty_print(MessageStreams::debug());
    MessageStreams::debug() << "... done." << std::endl;
    MessageStreams::debug() << message::unlock;
#endif
    return row_echelon_form(start_row + 1, minnonzerocol + 1, new_scale);
  }
  
  Matrix& Matrix::row_normal_form() {

    // first, generate row echelon form:
    row_echelon_form();

    // now, that the matrix is in row echelon form,
    // eliminate basis columns from right to left:
    for (parameter_type i = rowdim(); i > 0; --i) {
      const parameter_type pivot_row(i - 1);

      // find first nonzero-column in row to get the basis column
      // (in row echelon form there should be no zero-row:
      parameter_type pivot_col(0);
      while (pivot_col < coldim() && ((*this)(pivot_row, pivot_col) == FieldConstants::ZERO)) {
	++pivot_col;
      }
      if (pivot_col == coldim()) {

	// should never happen in a row echelon form:
	continue;
      }
      Field pivot_elm = (*this)(pivot_row, pivot_col);

      // normalize pivot row:
      for (parameter_type j = pivot_col; j < coldim(); ++j) {
	(*this)(pivot_row, j) /= pivot_elm;
      }

      // eliminate above in pivot column:
      for (parameter_type elim_row = 0; elim_row < pivot_row; ++elim_row) {
	Field delinquent((*this)(elim_row, pivot_col));
	if (delinquent != FieldConstants::ZERO) {
	  for (parameter_type j = pivot_col; j < coldim(); ++j) {
	    (*this)(elim_row, j) -= delinquent * (*this)(pivot_row, j);
	  }
	}
      }
    }
    return *this;
  }

  const Field Matrix::left_upper_det() const {
    parameter_type n = coldim();
    if (coldim() > rowdim()) {
      n = rowdim();
    }
    Matrix tmp(*this);
    tmp.resize(n);
    Field scale(FieldConstants::ONE);
    for (parameter_type i = 0; i < n - 1; ++i) {
      if (tmp(i,i) == FieldConstants::ZERO) {
	for (parameter_type k = i + 1; k < n; ++k) {
	  if (tmp(i,k) != FieldConstants::ZERO) {
	    tmp.swap_cols(i,k);
	    scale *= -1;
	    continue;
	  }
	}
	if (tmp(i,i) == FieldConstants::ZERO) {
	  return FieldConstants::ZERO;
	}
      }
      Field eraser = tmp(i,i);
#ifdef SUPER_VERBOSE
      MessageStreams::debug() << message::lock;
      MessageStreams::debug() << "eraser = " << eraser << std::endl;
      MessageStreams::debug() << message::unlock;
#endif
      for (parameter_type j = i + 1; j < n; ++j) {
	Field delinquent = tmp(i,j);
	if (delinquent == FieldConstants::ZERO) {
	  continue;
	}
#ifdef SUPER_VERBOSE
	MessageStreams::debug() << message::lock;
	MessageStreams::debug() << "delinquent = " << delinquent << std::endl;
	MessageStreams::debug() << message::unlock;
#endif
	for (parameter_type k = i + 1; k < n; ++k) {
	  tmp(k,j) -= (tmp(k,i) * delinquent / eraser);
	}
	tmp(i,j) = FieldConstants::ZERO;
      }
#ifdef SUPER_VERBOSE
      MessageStreams::debug() << message::lock;
      MessageStreams::debug() << "current tmp = " << std::endl;
      tmp.pretty_print(MessageStreams::debug());
      MessageStreams::debug() << message::unlock;
#endif
    }
#ifdef SUPER_VERBOSE
    MessageStreams::debug() << message::lock;
    MessageStreams::debug() << "Triangular transformation is:" << std::endl;
    tmp.pretty_print(MessageStreams::debug());
    MessageStreams::debug() << "Scale factor is " << scale << std::endl;
    MessageStreams::debug() << message::unlock;
#endif
    Field result(FieldConstants::ONE);
    for (parameter_type i = 0; i < n; ++i) {
      result *= tmp(i,i);
    }
#ifdef SUPER_VERBOSE
    MessageStreams::debug() << message::lock;
    MessageStreams::debug() << "Triangular determinant is " << result << std::endl;
    MessageStreams::debug() << message::unlock;
#endif
    return result * scale;
  }

  Matrix Matrix::transpose() const {
    Matrix result(coldim(), rowdim());
    for (parameter_type i = 0; i < coldim(); ++i) {
      for (parameter_type j = 0; j < rowdim(); ++j) {
	result(i, j) = (*this)(j, i);
      }
    }
    return result;
  }

  Matrix Matrix::inverse() const {
    if (coldim() != rowdim()) {
      MessageStreams::forced() << message::lock
			       << "Matrix::inverse(): matrix not quadratic - exiting" << std::endl
			       << message::unlock;
      exit(1);
    }
    Matrix GJmatrix(*this);
    GJmatrix.augment(identity_matrix(rowdim()));
    GJmatrix.row_echelon_form();
    if (rowdim() < coldim()) {
      MessageStreams::forced() << message::lock
			       << "Matrix::inverse(): matrix singular - exiting" << std::endl
			       << message::unlock;
      exit(1);
    }
    GJmatrix.row_normal_form();
    return Matrix(GJmatrix, coldim(), 2 * coldim());
  }
  
  const Matrix multiply(const Matrix& matrix1, const Matrix& matrix2) {
#ifdef INDEX_CHECK
    assert(matrix1.coldim() == matrix2.rowdim());
#endif
    Matrix result(matrix1.rowdim(), matrix2.coldim());
    for (parameter_type i = 0; i < matrix1.rowdim(); ++i) {
      const Vector row = matrix1.row(i);
      for (parameter_type j = 0; j < matrix2.coldim(); ++j) {
	result(i, j) = inner_product(row, matrix2.col(j));
      }
    }
    return result;
  }

  // transformation:
  Vector Matrix::StackOfAllColumns() const {
    Vector result;
    for (parameter_type j = 0; j < this->coldim(); ++j) {
      result.stack(this->col(j));
    }
    return result;
  }

  Message& Matrix::pretty_print(Message& msg) const {
    if (msg.is_active()) {
      for (parameter_type i = 0; i < rowdim(); ++i) {
	const Vector row_vector = row(i);
	for (parameter_type j = 0; j < coldim(); ++j) {
	  msg << row_vector(j) << ContainerChars::col_delim_char;
	}
	msg << std::endl;
      }
    }
    return msg;
  }

  std::ostream& Matrix::pretty_print(std::ostream& ost) const {
    Message msg(ost);
    pretty_print(msg);
    return ost;
  }

  std::istream& Matrix::read(std::istream& ist) {
    MessageStreams::debug() << message::lock;
    MessageStreams::debug() << "Matrix::read(std::istream& ist): "
			    << "current matrix: " << *this << std::endl;
    MessageStreams::debug() << message::unlock;

    char c;
    Vector column;
    Vector check_rank_col;
    parameter_type check_rank = 0;
    bool have_rank = false;

    matrix_data::clear();
    ist >> std::ws >> c;
    if (c == ContainerChars::list_start_char) {
      while (ist >> std::ws >> c) {
	if (c == ContainerChars::list_end_char) {
	  break;
	}
	if (c == ContainerChars::delim_char) {
	  continue;
	}
	ist.putback(c);
	if (ist >> column) {
	  if (have_rank) {
	    if (check_rank != column.dim()) {
	      MessageStreams::forced() << message::lock;
	      MessageStreams::forced() << "Matrix::read(std::istream& ist): "
				       << check_rank_col << " and " << column << " are points with incompatible ranks." << std::endl;
	      MessageStreams::forced() << message::unlock;
	      ist.clear(std::ios::failbit);
	      return ist;
	    }
	  }
	  else {
	    check_rank_col = column;
	    check_rank = column.dim();
	    have_rank = true;
	  }   
	  check_rank = column.dim();
	  matrix_data::push_back(column);	
	}
	else {
	  MessageStreams::forced() << message::lock;
	  MessageStreams::forced() << "Matrix::read(std::istream& ist):"
				   << c << " not of appropriate type." << std::endl;
	  MessageStreams::forced() << message::unlock;
	  ist.clear(std::ios::failbit);
	  return ist;
	}
      }
    }
    else {
      MessageStreams::forced() << message::lock;
      MessageStreams::forced() << "Matrix::read(std::istream& ist):"
			       << " missing `" << ContainerChars::list_start_char << "'." << std::endl;
      MessageStreams::forced() << message::unlock;
      ist.clear(std::ios::failbit);
      return ist;
    }
    ist.clear(std::ios::goodbit);
    return ist;
  }

  Message& Matrix::write(Message& ost) const {
    ost << ContainerChars::list_start_char;
    if (coldim() > 0) {
      for (parameter_type i = 0; i < coldim() - 1; ++i) {
	ost << (*this)[i] << ContainerChars::delim_char;
      }
      if (coldim() > 0) {
	ost << (*this)[coldim() - 1];
      }
    }
    ost << ContainerChars::list_end_char;
    return ost;
  }

  std::ostream& Matrix::write(std::ostream& ost) const {
    Message msg(ost);
    write(msg);
    return ost;
  }

}; // namespace topcom

// eof Matrix.cc
