/***************************************************************************
**
**  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: 2008-07-15
**  Copyright: 2008-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#ifndef MATRIXDATA_H
#define MATRIXDATA_H

#include <QGpCoreTools.h>

#include "MatrixMultiply.h"
#include "PermutationVector.h"

namespace QGpCoreMath {

template <typename T> class Matrix;

template <typename T> class MatrixData : public QSharedData
{
public:
  inline MatrixData();
  inline MatrixData(const MatrixData<T>& m);
  inline ~MatrixData();

  inline void operator=(const MatrixData<T>& m);
  inline bool operator==(const MatrixData<T>& m) const;
  Matrix<T> operator+(const MatrixData<T>& m) const;
  Matrix<T> operator-(const MatrixData<T>& m) const;
  Matrix<T> operator*(const MatrixData<T>& m) const;
  Matrix<T> dotMultiply(const MatrixData<T>& m) const;
  void operator*=(const T& v) const;
  void zero(const T& null) {fill(null);}
  void identity(const T& null);
  void fill(const T& v);
  inline T& at(int row, int col);
  inline const T& at(int row, int col) const;
  Matrix<T> transposed() const;
  T trace(const T& null) const;
  void mergeRow(const MatrixData<T>& row1, const MatrixData<T>& row2);
  void mergeColumn(const MatrixData<T>& col1, const MatrixData<T>& col2);
  void copyAt(int row, int col, const MatrixData<T>& m, int nRow, int nCol);
  Matrix<T> subMatrix(int rowStart, int colStart, int rowEnd, int colEnd) const;
  Matrix<T> sortedRows(const PermutationVector& p);
  Matrix<T> sortedColumns(const PermutationVector& p);

  int columnCount() const {return _ncol;}
  int rowCount() const {return _nrow;}
  T * values() {return _values;}
  const T * values() const {return _values;}
  T * copyOfValues() const;
  void setValues(T * values) {delete _values; _values=values;}
  int nextNullRow(int startRow) const;
  bool hasNullOnDiagonal(double relativePrecision) const;
  inline void resize(int ndim) {resize(ndim, ndim);}
  inline void resize(int nrow, int ncol);

  QString toUserString(char format='g', int precision=6) const;
  QString toOctaveString(char format='g', int precision=6) const;
  QString toGridString() const;
  QString toString() const;
  bool fromString(const StringSection& s);

  void swapRowColumn();
protected:
  int _nrow, _ncol;
  T * _values;
};

template <typename T> inline MatrixData<T>::MatrixData()
   : QSharedData()
{
  _nrow=0;
  _ncol=0;
  _values=nullptr;
}

template <typename T> inline MatrixData<T>::MatrixData(const MatrixData<T>& m)
   : QSharedData()
{
  _values=nullptr;
  resize(m._nrow, m._ncol);
  // Using memcpy, we get awful warnings about non trivial copy constructor for Complex
  for(int i=_nrow*_ncol-1; i>=0; i--) {
    _values[i]=m._values[i];
  }
}

template <typename T> inline MatrixData<T>::~MatrixData()
{
  delete [] _values;
}

template <typename T> inline void MatrixData<T>::operator=(const MatrixData<T>& m)
{
  resize(m._nrow, m._ncol);
  // Using memcpy, we get awful warnings about non trivial copy constructor for Complex
  for(int i=_nrow*_ncol-1; i>=0; i--) {
    _values[i]=m._values[i];
  }
}

template <typename T> inline void MatrixData<T>::resize(int nrow, int ncol)
{
  ASSERT(nrow>0 && ncol>0);
  _nrow=nrow;
  _ncol=ncol;
  delete [] _values;
  _values=new T[_nrow*_ncol];
}

template <typename T> inline void MatrixData<T>::swapRowColumn()
{
  qSwap(_nrow, _ncol);
}

template <typename T> inline bool MatrixData<T>::operator==(const MatrixData<T>& m) const
{
  if(_nrow!=m._nrow || _ncol!=m._ncol) return false;
  for(int i=_nrow * _ncol-1; i>=0;i--) {
    if(_values[i]!=m._values[i]) return false;
  }
  return true;
}

template <typename T> inline void MatrixData<T>::identity(const T& null)
{
  fill(null);
  int offset=_ncol+1;
  int n=_nrow*_ncol;
  for(int i=0; i<n; i+=offset) _values[i]=1.0;
}

template <typename T> void MatrixData<T>::fill(const T& v)
{
  for(int i=_nrow*_ncol-1; i>=0; i--) {
    _values[i]=v;
  }
}

template <typename T> T MatrixData<T>::trace(const T& null) const
{
  ASSERT(_nrow==_ncol);
  T sum=null;
  for(int i=_nrow-1; i>=0; i--) {
    sum+=at(i, i);
  }
  return sum;
}

template <typename T> inline T& MatrixData<T>::at(int row, int col)
{
  ASSERT(row>=0 && row<_nrow);
  ASSERT(col>=0 && col<_ncol);
  return _values[col*_nrow+row];
}

template <typename T> inline const T& MatrixData<T>::at(int row, int col) const
{
  ASSERT(row>=0 && row<_nrow);
  ASSERT(col>=0 && col<_ncol);
  return _values[col*_nrow+row];
}

template <typename T> void MatrixData<T>::mergeRow(const MatrixData<T>& row1, const MatrixData<T>& row2)
{
  ASSERT(row1._ncol==row2._ncol);
  resize(row1._nrow+row2._nrow, row1._ncol);
  int length1=sizeof(T)*row1._nrow;
  int length2=sizeof(T)*row2._nrow;
  T * values=_values;
  T * lastValue=_values+_nrow*_ncol;
  T * row1Values=row1._values;
  T * row2Values=row2._values;
  while(values<lastValue) {
    memcpy(values, row1Values, length1);
    values+=row1._nrow;
    row1Values+=row1._nrow;
    memcpy(values, row2Values, length2);
    values+=row2._nrow;
    row2Values+=row2._nrow;
  }
}

template <typename T> void MatrixData<T>::mergeColumn(const MatrixData<T>& col1, const MatrixData<T>& col2)
{
  ASSERT(col1._nrow==col2._nrow);
  resize(col1._nrow, col1._ncol+col2._ncol);
  int offset=_nrow*col1._ncol;
  memcpy(_values, col1._values, sizeof(T)*offset);
  memcpy(_values+offset, col2._values, sizeof(T)*_nrow*col2._ncol);
}

  template <typename T> int MatrixData<T>::nextNullRow(int startRow) const
  {
    for(int ir=startRow; ir<_nrow; ir++) {
      int ic=0;
      for(; ic<_ncol; ic++) {
        if(_values[ic*_nrow+ir]!=0.0) {
          break;
        }
      }
      if(ic==_ncol) {
        return ir;
      }
    }
    return -1;
  }

  template <typename T> bool MatrixData<T>::hasNullOnDiagonal(double relativePrecision) const
  {
    ASSERT(_ncol==_nrow);
    // Get maximum value
    double max=-std::numeric_limits<double>::infinity();
    double * values=new double[_nrow];
    for(int i=0; i<_nrow; i++) {
      values[i]=QGpCoreTools::abs(_values[i*_nrow+i]);
      if(values[i]>max) {
        max=values[i];
      }
    }
    // Check that no value is lower that relativePrecision*max
    max*=relativePrecision;
    for(int i=0; i<_nrow; i++) {
      if(values[i]<max) {
        delete [] values;
        return true;
      }
    }
    delete [] values;
    return false;
  }

  template <typename T> QString MatrixData<T>::toUserString(char format, int precision) const
  {
    QString str("    | ");
    for(int j=0; j<_ncol; j++) {
      str+=QString(" %1 |").arg(j, 12, 10, QChar(' '));
    }
    str+="\n----|-";
    for(int j=0; j<_ncol; j++) {
      str+="--------------|";
    }
    str+="\n";
    for(int i=0; i<_nrow; i++) {
      str+=QString("%1 | ").arg(i, 3, 10, QChar(' '));
      for(int j=0; j<_ncol; j++) {
        str+=QString(" %1 |").arg(Number::toDouble(at(i, j)), 12, format, precision, QChar(' '));
      }
      str+="\n";
    }
    str+="----|-";
    for(int j=0; j<_ncol; j++) {
      str+="--------------|";
    }
    str+="\n";
    return str;
  }

  template <typename T> QString MatrixData<T>::toOctaveString(char format, int precision) const
  {
    QString str("[");
    for(int i=0; i<rowCount(); i++) {
      for(int j=0; j<columnCount(); j++) {
        str+=QString(" %1").arg(Number::toString(at(i, j), format, precision));
      }
      str+=";";
    }
    str+=" ]";
    return str;
  }

  template <typename T> QString MatrixData<T>::toString() const
  {
    QString str;
    int n=_nrow*_ncol;
    for(int i=0; i<n; i++) {
      str+=Number::toString(_values[i], 'g', 20);
      str+=" ";
    }
    return str;
  }

  template <typename T> QString MatrixData<T>::toGridString() const
  {
    QString str;
    str+="x y val\n";
    for(int i=0; i<_ncol; i++) {
      for(int j=0; j<_nrow; j++) {
        str+=QString::number(i);
        str+=" ";
        str+=QString::number(j);
        str+=" ";
        str+=Number::toString(Number::toDouble(at(j, i)), 'g', 20);
        str+="\n";
      }
    }
    return str;
  }

template <typename T> bool MatrixData<T>::fromString(const StringSection& s)
{
  const QChar * ptr=nullptr;
  int n=_nrow*_ncol;
  for(int i=0; i<n; i++) {
    if(!s.nextNumber(_values[i], ptr)) {
      return false;
    }
  }
  return true;
}

template <typename T> void MatrixData<T>::copyAt(int row, int col, const MatrixData<T>& m, int nRow, int nCol)
{
  T * thisValues=_values;
  T * values=m._values;

  int mRow=m.rowCount();
  if(nRow<0) {
    nRow=mRow;
  }
  int rowCopyCount=nRow;
  if(row<0) {
    values-=row;
    rowCopyCount+=row;
  } else {
    thisValues+=row;
  }
  if(row+rowCopyCount>rowCount()) {
    rowCopyCount=rowCount()-row;
  }

  int mCol=m.columnCount();
  if(nCol<0) {
    nCol=mCol;
  }
  int colCopyCount=nCol;
  if(col<0) {
    values-=col*mRow;
    colCopyCount+=col;
  } else {
    thisValues+=col*_nrow;
  }
  if(col+colCopyCount>columnCount()) {
    colCopyCount=columnCount()-col;
  }

  int length=sizeof(T)*rowCopyCount;
  if(length<=0) {
    return;
  }
  T * lastValue=values+colCopyCount*mRow;
  while(values<lastValue) {
    memcpy(thisValues, values, length);
    values+=mRow;
    thisValues+=_nrow;
  }
}

template <typename T> T* MatrixData<T>::copyOfValues() const
{
  int mn=_nrow*_ncol;
  double * a=new T[mn];
  for(int i=mn-1; i>=0; i--) {
    a[i]=_values[i];
  }
  return a;
}

} // namespace QGpCoreMath

#endif // MATRIXDATA_H
