/***************************************************************************
**
**  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: 2013-02-12
**  Copyright: 2013-2019
**    Marc Wathelet (ISTerre, Grenoble, France)
**
***************************************************************************/

#ifndef SPARSEMATRIX_H
#define SPARSEMATRIX_H

#include "Matrix.h"
#include "QGpCoreMathDLLExport.h"

namespace QGpCoreMath {

  template <typename T> class QGPCOREMATH_EXPORT SparseMatrix
  {
  public:
    SparseMatrix();
    SparseMatrix(int nrow, int ncol);

    bool operator==(const Matrix<T>& m) const;

    int rowCount() const {return _nrow;}
    int columnCount() const {return _ncol;}
    int realRowCount() const {return _values.count();}
    int valueCount() const;

    void clear();
    void setRowCount(int nrow);
    void setColumnCount(int ncol);

    T at(int row, int col) const;
    T& at(int row, int col);

    typedef QHash<int, T> Row;
    typedef typename QHash<int, Row>::ConstIterator ConstRowIterator;
    typedef typename QHash<int, Row>::Iterator RowIterator;
    typedef typename Row::ConstIterator ConstColumnIterator;
    typedef typename Row::Iterator ColumnIterator;

    Row& addRow(int row);

    ConstRowIterator begin() const {return _values.begin();}
    RowIterator begin() {return _values.begin();}
    ConstRowIterator end() const {return _values.end();}
    RowIterator end() {return _values.end();}

    Matrix<T> plainMatrix() const;
    void setPlainMatrix(const Matrix<T>& m);

    Matrix<T> operator*(const Matrix<T>& m) const;

    QString toUserString(int precision=6, char format='g') const;
  private:
    int _nrow, _ncol;
    QHash<int, Row> _values;
  };

  template <typename T> SparseMatrix<T>::SparseMatrix()
  {
    _nrow=0;
    _ncol=0;
  }

  template <typename T> SparseMatrix<T>::SparseMatrix(int nrow, int ncol)
  {
    _nrow=nrow;
    _ncol=ncol;
  }

  template <typename T> bool SparseMatrix<T>::operator==(const Matrix<T>& m) const
  {
    if(_nrow!=m.rowCount() || _ncol!=m.columnCount()) {
      return false;
    }
    for(int ir=0; ir<_nrow; ir++) {
      for(int ic=0; ic<_ncol; ic++) {
        const T& v=m.at(ir, ic);
        if(v!=0.0 && at(ir, ic)!=v) {
          return false;
        }
      }
    }
    return true;
  }

  template <typename T> Matrix<T> SparseMatrix<T>::plainMatrix() const
  {
    Matrix<T> m(_nrow, _ncol);
    for(int ir=0; ir<_nrow; ir++) {
      ConstRowIterator itr=_values.constFind(ir);
      if(itr!=_values.end()) {
        const Row& rowHash=itr.value();
        for(int ic=0; ic<_ncol; ic++) {
          ConstColumnIterator itc=rowHash.constFind(ic);
          if(itc!=rowHash.end()) {
            m.at(ir, ic)=itc.value();
          } else {
            m.at(ir, ic)=0.0;
          }
        }
      } else {
        for(int ic=0; ic<_ncol; ic++) {
          m.at(ir,ic)=0.0;
        }
      }
    }
    return m;
  }

  template <typename T> void SparseMatrix<T>::setPlainMatrix(const Matrix<T>& m)
  {
    clear();
    setRowCount(m.rowCount());
    setColumnCount(m.columnCount());
    for(int ir=0; ir<_nrow; ir++) {
      for(int ic=0; ic<_ncol; ic++) {
        const T& v=m.at(ir, ic);
        if(v!=0.0) {
          at(ir, ic)=v;
        }
      }
    }
  }

  template <typename T> int SparseMatrix<T>::valueCount() const
  {
    int n=0;
    for(ConstRowIterator itr=_values.begin(); itr!=_values.end(); itr++) {
      const Row& rowHash=itr.value();
      n+=rowHash.count();
    }
    return n;
  }

  template <typename T> Matrix<T> SparseMatrix<T>::operator*(const Matrix<T>& m) const
  {
    Matrix<T> r(_nrow, m.columnCount());
    for(int ir=0; ir<_nrow; ir++) {
     ConstRowIterator itr=_values.constFind(ir);
      if(itr!=_values.end()) {
        const Row& rowHash=itr.value();
        for(int ic=0; ic<m.columnCount(); ic++) {
          T s=0.0;
          for(ConstColumnIterator itc=rowHash.begin(); itc!=rowHash.end(); itc++) {
            s+=itc.value()*m.at(itc.key(), ic);
          }
          r.at(ir, ic)=s;
        }
      } else {
        for(int ic=0; ic<r.columnCount(); ic++) {
          r.at(ir,ic)=0.0;
        }
      }
    }
    return r;
  }

  template <typename T> void SparseMatrix<T>::clear()
  {
    _nrow=0;
    _ncol=0;
    _values.clear();
  }

  template <typename T> void SparseMatrix<T>::setRowCount(int nrow)
  {
    _nrow=nrow;
    for(RowIterator itr=_values.begin(); itr!=_values.end(); itr++) {
      if(itr.key()>=nrow) {
        _values.remove(itr.key());
      }
    }
  }

  template <typename T> void SparseMatrix<T>::setColumnCount(int ncol)
  {
    _ncol=ncol;
    for(RowIterator itr=_values.begin(); itr!=_values.end(); itr++) {
      Row& rowHash=itr.value();
      for(ColumnIterator itc=rowHash.begin(); itc!=rowHash.end(); itc++) {
        if(itc.key()>=ncol) {
          rowHash.remove(itc.key());
        }
      }
    }
  }

  template <typename T> typename SparseMatrix<T>::Row& SparseMatrix<T>::addRow(int row)
  {
    RowIterator itr=_values.insert(row, QHash<int, T>());
    return itr.value();
  }

  template <typename T> T SparseMatrix<T>::at(int row, int col) const
  {
    ConstRowIterator itr=_values.constFind(row);
    if(itr!=_values.end()) {
      const Row& rowHash=itr.value();
      ConstColumnIterator itc=rowHash.constFind(col);
      if(itc!=rowHash.end()) {
        return itc.value();
      } else {
        return 0.0;
      }
    } else {
      return 0.0;
    }
  }

  template <typename T> T& SparseMatrix<T>::at(int row, int col)
  {
    RowIterator itr=_values.find(row);
    if(itr!=_values.end()) {
      Row& rowHash=itr.value();
      ColumnIterator itc=rowHash.find(col);
      if(itc!=rowHash.end()) {
        return itc.value();
      } else {
        ColumnIterator itc=rowHash.insert(col, 0.0);
        return itc.value();
      }
    } else {
      Row& rowHash=addRow(row);
      ColumnIterator itc=rowHash.insert(col, 0.0);
      return itc.value();
    }
  }

  template <typename T> QString SparseMatrix<T>::toUserString(int precision, char format) 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> QDataStream& operator<<(QDataStream& s, const SparseMatrix<T>& m)
  {
    s << m.rowCount() << m.columnCount();
    s << m.realRowCount();
    for(typename SparseMatrix<T>::ConstRowIterator itr=m.begin(); itr!=m.end(); itr++) {
      const typename SparseMatrix<T>::Row& rowHash=itr.value();
      s << itr.key() << rowHash.count();
      for(typename SparseMatrix<T>::ConstColumnIterator itc=rowHash.begin(); itc!=rowHash.end(); itc++) {
        s<< itc.key() << itc.value();
      }
    }
    return s;
  }

  template <typename T> QDataStream& operator>>(QDataStream& s, SparseMatrix<T>& m)
  {
    m.clear();
    int nrow, ncol;
    s >> nrow >> ncol;
    m.setRowCount(nrow);
    m.setColumnCount(ncol);
    s >> nrow;
    int index;
    T value;
    for(int ir=0; ir<nrow; ir++) {
      s >> index >> ncol;
      typename SparseMatrix<T>::Row& rowHash=m.addRow(index);
      for(int ic=0; ic<ncol; ic++) {
        s >> index >> value;
        rowHash.insert(index, value);
      }
    }
    return s;
  }

} // namespace QGpCoreMath

#endif // SPARSEMATRIX_H
