/***************************************************************************
**
**  This file is part of QGpCoreMath.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This file is distributed in the hope that it will be useful, but WITHOUT
**  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
**  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
**  License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2004-05-17
**  Copyright: 2004-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#ifndef MATRIX_H
#define MATRIX_H

#include <QGpCoreTools.h>

#include "MatrixData.h"
#include "Point.h"

namespace QGpCoreMath {

  template <typename T> class MatrixIterator;
  template <typename T> class MutableMatrixIterator;
  template <typename T> class MatrixRowIterator;
  template <typename T> class MutableMatrixRowIterator;
  template <typename T> class MatrixColumnIterator;
  template <typename T> class MutableMatrixColumnIterator;

  template <typename T> class QGPCOREMATH_EXPORT Matrix
  {
  public:
    Matrix() {_d=new MatrixData<T>;}
    Matrix(int ndim) {_d=new MatrixData<T>; _d->resize(ndim);}
    Matrix(int nrow, int ncol) {_d=new MatrixData<T>; _d->resize(nrow, ncol);}
    Matrix(const Matrix<T>& o) : _d(o._d) {}

    Matrix<T> operator=(const Matrix<T>& m) {_d=m._d; return *this;}
    bool operator==(const Matrix<T>& m) const {return _d->operator==(*m._d);}
    bool operator!=(const Matrix<T>& m) const {return !_d->operator==(*m._d);}
    Matrix<T> operator+(const Matrix<T>& m) const {return _d->operator+(*m._d);}
    void operator+=(const Matrix<T>& m);
    Matrix<T> operator-(const Matrix<T>& m) const {return _d->operator-(*m._d);}
    void operator-=(const Matrix<T>& m);
    Matrix<T> operator*(const Matrix<T>& m) const {return _d->operator*(*m._d);}
    Matrix<T> dotMultiply(const Matrix<T>& m) const {return _d->dotMultiply(*m._d);}
    void operator*=(const Matrix<T>& m);
    void operator*=(const T& v) {_d->operator*=(v);}
    void zero(const T& null) {_d->fill(null);}
    void identity(const T& null) {_d->identity(null);}
    void fill(const T& v) {_d->fill(v);}
    T& at(int row, int col) {return _d->at(row,col);}
    const T& at(int row, int col) const {return _d->at(row,col);}
    const T * values() const {return _d->values();}
    T * values() {return _d->values();}
    T * copyOfValues() const {return _d->copyOfValues();}
    QVector<T> rowAt(int row) const;
    QVector<T> columnAt(int col) const;
    Matrix<T> transposed() const {return _d->transposed();}
    void transpose() {*this=transposed();}
    T trace(const T& null) const {return _d->trace(null);}
    void mergeRow(const Matrix<T>& row1, const Matrix<T>& row2) {_d->mergeRow(*row1._d, *row2._d);}
    void mergeColumn(const Matrix<T>& col1, const Matrix<T>& col2) {_d->mergeColumn(*col1._d, *col2._d);}
    void copyAt(int row, int col, const Matrix<T>& m, int nRow=-1, int nCol=-1) {_d->copyAt(row, col, *m._d, nRow, nCol);}
    Matrix<T> subMatrix(int rowStart, int colStart, int rowEnd, int colEnd) const {return _d->subMatrix(rowStart, colStart, rowEnd, colEnd);}
    Matrix<T> sortedRows(const PermutationVector& p) {return _d->sortedRows(p);}
    Matrix<T> sortedColumns(const PermutationVector& p) {return _d->sortedColumns(p);}

    bool isEmpty() const {return _d->columnCount()==0 || _d->rowCount()==0;}
    int columnCount() const {return _d->columnCount();}
    int rowCount() const {return _d->rowCount();}
    int nextNullRow(int startRow) const {return _d->nextNullRow(startRow);}
    bool hasNullOnDiagonal(double relativePrecision) const {return _d->hasNullOnDiagonal(relativePrecision);}
    void resize(int ndim) {_d->resize(ndim);}
    void resize(int nrow, int ncol) {_d->resize(nrow, ncol);}

    QString toUserString(char format='g', int precision=6) const {return _d->toUserString(format, precision);}
    QString toOctaveString(char format='g', int precision=6) const {return _d->toOctaveString(format, precision);}
    QString toGridString() const {return _d->toGridString();}
    QString toString() const {return _d->toString();}
    bool fromString(const StringSection& s) {return _d->fromString(s);}
  protected:
    void swapRowColumn() {_d->swapRowColumn();}

    friend class MatrixData<T>;
    friend class MatrixIterator<T>;
    friend class MutableMatrixIterator<T>;
    friend class MatrixRowIterator<T>;
    friend class MutableMatrixRowIterator<T>;
    friend class MatrixColumnIterator<T>;
    friend class MutableMatrixColumnIterator<T>;

    QSharedDataPointer< MatrixData<T> > _d;
  };

  template <typename T> Matrix<T> MatrixData<T>::operator+(const MatrixData<T>& m) const
  {
    ASSERT(_nrow==m._nrow && _ncol==m._ncol);
    Matrix<T> r(_nrow, _ncol);
    T * rValues=r._d->values();
    for(int i=_nrow*_ncol-1; i>=0; i--) {
      rValues[i]=_values[i]+m._values[i];
    }
    return r;
  }

  template <typename T> Matrix<T> MatrixData<T>::operator-(const MatrixData<T>& m) const
  {
    ASSERT(_nrow==m._nrow && _ncol==m._ncol);
    Matrix<T> r(_nrow, _ncol);
    T * rValues=r._d->values();
    for(int i=_nrow*_ncol-1; i>=0; i--) {
      rValues[i]=_values[i]-m._values[i];
    }
    return r;
  }

  template <typename T> Matrix<T> MatrixData<T>::operator*(const MatrixData<T>& m) const
  {
    ASSERT(_ncol==m._nrow);
    Matrix<T> r(_nrow, m._ncol);
    r.zero(0.0);
    T * rValues=r._d->_values;
    const T * thisValues=_values;
    const T * mValues=m._values;
    /*if(MatrixMultiply::isValid(_nrow, _ncol, m._ncol)) {
      const MatrixMultiply& multiplier=*MatrixMultiply::begin(_nrow, _ncol, m._ncol);
      int n=multiplier.indexCount();
      for(int i=0;i<n;i++) {
        const MatrixMultiply::IndexMap& index=multiplier.indexMap(i);
        rValues[index.resultIndex()]+=thisValues[index.factor1Index()]*mValues[index.factor2Index()];
      }
      MatrixMultiply::end(&multiplier);
    } else {*/
      // Finally faster method than caching indexe
      int mIndex;
      for(int i=0; i<_nrow; i++) {
        for(int j=0; j<m._ncol; j++) {
          T& resultValue=rValues[i+j*_nrow];
          mIndex=j*_ncol;
          for(int k=0; k<_ncol; k++) {
            resultValue+=thisValues[i+k*_nrow]*mValues[k+mIndex];
          }
        }
      }
    //}
    return r;
  }

  template <typename T> Matrix<T> MatrixData<T>::dotMultiply(const MatrixData<T>& m) const
  {
    ASSERT(_ncol==m._ncol);
    ASSERT(_nrow==m._nrow);
    Matrix<T> r(_nrow, _ncol);
    T * rValues=r._d->_values;
    const T * thisValues=_values;
    const T * mValues=m._values;
    for(int i=_nrow*_ncol-1; i>=0; i--) {
      rValues[i]=thisValues[i]*mValues[i];
    }
    return r;
  }

  template <typename T> Matrix<T> MatrixData<T>::transposed() const
  {
    Matrix<T> t(_ncol, _nrow);
    T * pt=t._d->_values;
    for(int i=0; i<_nrow; i++) {
      for(int j=0; j<_ncol; j++) {
        (*pt++)=_values[j*_nrow+i];
      }
    }
    return t;
  }

  template <typename T> Matrix<T> MatrixData<T>::subMatrix(int rowStart, int colStart, int rowEnd, int colEnd) const
  {
    ASSERT(rowStart>=0 && rowStart<=rowEnd && rowEnd<_nrow);
    ASSERT(colStart>=0 && colStart<=colEnd && colEnd<_ncol);
    int mnrow=rowEnd-rowStart+1;
    int mncol=colEnd-colStart+1;
    Matrix<T> m(mnrow, mncol);
    int length=sizeof(T)*mnrow;
    T * values=m._d->_values;
    T * lastValue=values+mnrow*mncol;
    T * thisValues=_values+rowStart+colStart*_nrow;
    while(values<lastValue) {
      memcpy(values, thisValues, length);
      values+=mnrow;
      thisValues+=_nrow;
    }
    return m;
  }

  template <typename T> Matrix<T> MatrixData<T>::sortedRows(const PermutationVector& p)
  {
    Matrix<T> m(_nrow, _ncol);
    for(int i=0; i<_ncol; i++) {
      int offset=i*_nrow;
      T * values=m._d->_values+offset;
      T * thisValues=_values+offset;
      for(int j=0; j<_nrow; j++) {
        values[p.newIndex(j)]=thisValues[j];
      }
    }
    return m;
  }

  template <typename T> Matrix<T> MatrixData<T>::sortedColumns(const PermutationVector& p)
  {
    Matrix<T> m(_nrow, _ncol);
    T * values=m._d->_values;
    for(int i=0; i<_ncol; i++) {
      memcpy(values+p.newIndex(i)*_nrow, _values+i*_nrow, sizeof(T)*_nrow);
    }
    return m;
  }

  template <typename T> inline void Matrix<T>::operator*=(const Matrix<T>& m)
  {
    *this=_d->operator*(*m._d);
  }

  template <typename T> inline void Matrix<T>::operator+=(const Matrix<T>& m)
  {
    *this=_d->operator+(*m._d);
  }

  template <typename T> inline void Matrix<T>::operator-=(const Matrix<T>& m)
  {
    *this=_d->operator-(*m._d);
  }

  template <typename T> QVector<T> Matrix<T>::rowAt(int row) const
  {
    QVector<T> r(columnCount());
    MatrixColumnIterator<T> it(*this, row);
    int i=0;
    while(it.hasNext()) {
      r[i++]=*it.next();
    }
    return r;
  }

  template <typename T> QVector<T> Matrix<T>::columnAt(int col) const
  {
    QVector<T> r(rowCount());
    MatrixColumnIterator<T> it(*this, col);
    int i=0;
    while(it.hasNext()) {
      r[i++]=*it.next();
    }
    return r;
  }


  template <typename T> QDataStream& operator<<(QDataStream& s, const Matrix<T>& m)
  {
    s << m.rowCount() << m.columnCount();
    for(int i=m.rowCount()*m.columnCount()-1; i>=0; i--) {
      s << m.data()[i];
    }
    return s;
  }

  template <typename T> QDataStream& operator>>(QDataStream& s, Matrix<T>& m)
  {
    int nrow, ncol;
    s >> nrow >> ncol;
    m.resize(nrow, ncol);
    for(int i=nrow*ncol-1; i>=0; i--) {
      s >> m.data()[i];
    }
    return s;
  }

} // namespace QGpCoreMath

#endif // MATRIX_H
