#include <iostream>
#include <iomanip>
#include <sstream>
#include <vector>
#include "matrix.h"
using namespace std;

/*****************************************************
 * range class
 ****************************************************/

range::range(int start) : _start(start), _stop(start+1), _stride(1) { }

// supports construction such as range(3,6), which includes values {3, 4, 5}
range::range(int start, int stop) : _start(start), _stop(stop), _stride(1) { }

// supports construction such as range(3,2,8), which includes values {3, 5, 7}
range::range(int start, int stride, int stop) : _start(start), _stop(stop), _stride(stride) {
  if (stride < 1)
    throw invalid_argument("stride must be positive.");
}

// Returns starting index
int range::start() const {
  return _start;
}

// Returns stopping index
int range::stop() const {
  return _stop;
}

// Returns stopping index
int range::stride() const {
  return _stride;
}

// Returns the number of values included within the range
int range::size() const {
  // partials strides should count as one.  e.g. range(1,2,4).size() should be 2
  return max(0, (_stop - _start + _stride - 1) / _stride);  // trucates properly
}



/*****************************************************
 * matrix class
 ****************************************************/

matrix::matrix() : _nr(0), _nc(0), _data() {};

matrix::matrix(int numRows, int numColumns, double value)
  : _nr(numRows), _nc(numColumns), _data(numRows*numColumns, value) {}

int matrix::numRows() const {
  return _nr;
}

int matrix::numColumns() const {
  return _nc;
}

matrix matrix::size() const {
  matrix result(1,2);
  result(0,0) = numRows();
  result(0,1) = numColumns();
  return result;
}

void matrix::reshape(int r, int c) {
  if (r * c != _nr * _nc)
    throw invalid_argument("To reshape, the number of elements must not change.");

  _nr = r;
  _nc = c;
}

bool matrix::operator==(const matrix &other) const {
  return (_nr == other._nr && _nc == other._nc && _data == other._data);
}

bool matrix::operator!=(const matrix &other) const {
  return !(*this == other);
}

// provides read-only access to a matrix entry
double matrix::operator()(int r, int c) const {
  if (r < 0 || r >= _nr || c < 0 || c >= _nc)
    throw out_of_range("Invalid indices for matrix");

  return _data[r +  c * _nr];   // column-major
}

// provides write access to a matrix entry (albeit, without expansion)
double& matrix::operator()(int r, int c) {
  if (r < 0 || r >= _nr || c < 0 || c >= _nc)
    throw out_of_range("Invalid indices for matrix");

  return _data[r +  c * _nr];   // column-major
}


// provides write access to a submatrix as a proxy
matrix_proxy matrix::operator()(range rows, range cols) {
  return matrix_proxy(*this, rows, cols);
}


//-----------------------------------------------
// addition
//-----------------------------------------------
matrix matrix::operator+(const matrix& other) const {  // produce sum of two matrices
  if (_nr != other._nr && _nc != other._nc)
    throw invalid_argument("Matrix dimensions must agree.");

  matrix result(*this);         // start with copy
  for (int r=0; r < _nr; r++)
    for (int c=0; c < _nc; c++)
      result(r,c) += other(r,c);
  return result;
}

matrix matrix::operator+(double scalar) const {   // add scalar to all elements
  matrix result(*this);         // start with copy
  for (int r=0; r < _nr; r++)
    for (int c=0; c < _nc; c++)
      result(r,c) += scalar;
  return result;
}


//-----------------------------------------------
// multiplication
//-----------------------------------------------
matrix matrix::operator*(double scalar) const {   // multiply each element by scalar
  matrix result(*this);         // start with copy
  for (int r=0; r < _nr; r++)
    for (int c=0; c < _nc; c++)
      result(r,c) *= scalar;
  return result;
}

matrix matrix::operator*(const matrix& other) const {  // matrix multiplicaiton
  if (_nc != other._nr)
    throw invalid_argument("Inner matrix dimensions must agree.");

  matrix result(_nr, other._nc);
  for (int r=0; r < _nr; r++)
    for (int c=0; c < other._nc; c++) {
      // let's compute the proper entry
      float subtotal = 0;
      for(int k=0; k < _nc; k++) {
	subtotal += (*this)(r,k) * other(k,c);
      }
      result(r,c) = subtotal;
    }

  return result;

}


/*****************************************************
 * IO operations
 ****************************************************/

ostream& operator<<(ostream& out, const matrix& m) {
  string temp;

  unsigned int maxfield = 0;
  for (int r=0; r < m.numRows(); r++) {
    for (int c=0; c < m.numColumns(); c++) {
      stringstream s;
      s << fixed << setprecision(3);
      s << m(r,c);
      s >> temp;
      if (temp.size() > maxfield)
	maxfield = temp.size();
    }
  }

  for (int r=0; r < m.numRows(); r++) {
    for (int c=0; c < m.numColumns(); c++) {
      stringstream s;
      s << fixed << setprecision(3);
      s << m(r,c);
      s >> temp;
      out << " " << setw(maxfield) << temp;
    }
    out << endl;
  }

  return out;
}

ostream& operator<<(ostream& out, const matrix_proxy& m) {
  string temp;

  unsigned int maxfield = 0;
  for (int r=0; r < m.numRows(); r++) {
    for (int c=0; c < m.numColumns(); c++) {
      stringstream s;
      s << fixed << setprecision(3);
      s << m(r,c);
      s >> temp;
      if (temp.size() > maxfield)
	maxfield = temp.size();
    }
  }

  for (int r=0; r < m.numRows(); r++) {
    for (int c=0; c < m.numColumns(); c++) {
      stringstream s;
      s << fixed << setprecision(3);
      s << m(r,c);
      s >> temp;
      out << " " << setw(maxfield) << temp;
    }
    out << endl;
  }

  return out;
}


istream& operator>>(istream& in, matrix& m) {
  // presumes that there is a blank line to terminate the matrix
  vector< vector<double> >  _data;

  unsigned int numColumns = 0;
  bool done = false;
  while (!done) {
    vector<double> row;
    string temp;
    getline(in, temp);
    stringstream s(temp);
    double val;

    while (s >> val) {
      row.push_back(val);
    }

    if (row.size() == 0)
      done = true;
    else if (numColumns > 0 and numColumns != row.size())
      done = true;
    else {
      numColumns = row.size();
      _data.push_back(row);
    }
  }

  m = matrix(_data.size(), numColumns);
  for (int r=0; r < m.numRows(); r++)
    for (int c=0; c< m.numColumns(); c++)
      m(r,c) = _data[r][c];

  return in;
}

