/***************************************************************************
**
**  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)
**
***************************************************************************/

#include "ComplexMatrix.h"
#include "DoubleMatrix.h"
#include "Random.h"

namespace QGpCoreMath {

  /*!
    \class ComplexMatrix ComplexMatrix.h
    \brief Brief description of class still missing

    Full description of class still missing
  */

  void ComplexMatrix::operator*=(double v)
  {
    Complex * values=ComplexMatrix::values();
    for(int i=columnCount()*rowCount()-1; i>=0; i--) {
      values[i]*=v;
    }
  }

  void ComplexMatrix::rand()
  {
    Random r;
    Complex * values=ComplexMatrix::values();
    for(int i=columnCount()*rowCount()-1; i>=0; i--) {
      values[i]=Complex(r.ran2(), r.ran2());
    }
  }

  ComplexMatrix ComplexMatrix::conjugate() const
  {
    ComplexMatrix r(rowCount(),columnCount());
    MatrixIterator<Complex> itThis(*this);
    MutableMatrixIterator<Complex> itR(r);
    while(itThis.hasNext()) {
      itThis.next();
      itR.next();
      *itR=QGpCoreTools::conjugate(*itThis);
    }
    return r;
  }

  ComplexMatrix ComplexMatrix::dotMultiply(const DoubleMatrix& m) const
  {
    ASSERT(columnCount()==m.columnCount());
    ASSERT(rowCount()==m.rowCount());
    ComplexMatrix r(rowCount(), columnCount());
    Complex * rValues=r.values();
    const Complex * thisValues=values();
    const double * mValues=m.values();
    for(int i=rowCount()*columnCount()-1; i>=0; i--) {
      rValues[i]+=thisValues[i]*mValues[i];
    }
    return r;
  }

  // LAPACK calls
  extern "C" int zgetrf_(int * m, int * n, Complex * a, int * lda, int * ipiv, int * info);
  extern "C" int zgetri_(int * n, Complex * a, int * lda, int * ipiv, Complex * work, int * lwork, int * info);
  extern "C" int zpotrf_(char * uplo, int * n, Complex * a, int * lda, int * info);
  extern "C" int zpotri_(char * uplo, int * n, Complex * a, int * lda, int * info);
  extern "C" int zgesvd_(char *jobu, char *jobvt, int *m, int *n,
                         Complex *a, int *lda, double *s, Complex *u,
                         int *ldu, Complex *vt, int *ldvt, Complex *work,
                         int *lwork, double *rwork, int *info);

  /*!
    Based on LAPACK LU decomposition, for general matrix type.
  */
  bool ComplexMatrix::invert()
  {
    ASSERT(rowCount()==columnCount());
    Complex * values=_d->values();
    int m=rowCount();
    if(m==1) {  // Not accepted by dgetrf
      values[0]=inverse(values[0]);
      return true;
    }
    int n=columnCount();                // The order of matrix A
    int lda=m;                          // The leading dimension of A
    int * ipiv=new int[n];              // Pivot indices
    int lwork=5*n;                      // Dimension of workspace
    Complex * work=new Complex[lwork];  // Work space
    int info;                           // Return value
    zgetrf_(&m, &n, values, &lda, ipiv, &info);
    if(info!=0 || hasNullOnDiagonal(1e-10)) { // singular matrix
      delete [] ipiv;
      delete [] work;
      return false;
    }
    zgetri_(&n, values, &lda, ipiv, work, &lwork, &info);
    if(info!=0) {
      delete [] ipiv;
      delete [] work;
      return false;
    }
    delete [] ipiv;
    delete [] work;
    _d->swapRowColumn();
    return true;
  }

  /*!
    Based on LAPACK Cholesky decomposition, for Hermitian positive defined matrix.
  */
  bool ComplexMatrix::choleskyInvert()
  {
    ASSERT(rowCount()==columnCount());
    Complex * values=_d->values();
    int n=rowCount();                   // The order of matrix A
    if(n==1) {                          // Not accepted by dpotrf
      values[0]=inverse(values[0]);
      return true;
    }
    char uplo='U';
    int lda=n;                          // The leading dimension of A
    int info;                           // Return value
    zpotrf_(&uplo, &n, values, &lda, &info);
    if(info!=0 || hasNullOnDiagonal(1e-10)) { // singular matrix
      return false;
    }
    zpotri_(&uplo, &n, values, &lda, &info);
    if(info!=0) {
      return false;
    }
    hermitianUpToLow();
    return true;
  }

  void ComplexMatrix::hermitianUpToLow()
  {
    int n=rowCount();
    ASSERT(n=columnCount());
    for(int i=1; i<n; i++) {
      for(int j=0; j<i; j++) {
        at(i, j)=QGpCoreTools::conjugate(at(j, i));
      }
    }
  }

  /*!
     Compute the singular value decomposition (SVD).

     *this = \a u * \a sigma * \a vt

     \a vt is the conjugate-transpose of v

     *this^-1 = v * \a sigma^-1 * ut

     ut is the conjugate-transpose of \a u.
  */
  bool ComplexMatrix::singularValue(QVector<double>& sigma,
                                    ComplexMatrix& u,
                                    ComplexMatrix& vt) const
  {
    char jobu='A';       // [INPUT] All columns of U are computed
    char jobvt='A';      // [INPUT] All rows of V^H are returned
    int m=rowCount();    // [INPUT] The number of rows in input matrix A
    int n=columnCount(); // [INPUT] The number of columns in input matrix A
    int mn=m*n;
    Complex * a=new Complex[mn];
                         // [INPUT] A is destroyed during process
    memcpy(a, _d->values(), mn*sizeof(Complex));
    int lda=m;           // [INPUT] The leading dimension of the array A
    sigma.resize(m<n ? m : n);
                         // [OUTPUT] Sorted singular values s[i]>=s[i+1]
    u.resize(m, m);      // [OUTPUT] Unitary matrix U
    int ldu=m;           // [INPUT] The leading dimension of the array U
    vt.resize(n, n);     // [OUTPUT] Unitary matrix V^H
    int ldvt=n;          // [INPUT] The leading dimension of the array V^H
    int lwork=20*(2*(m<n ? m : n)+(m<n ? n : m));
                         // [INPUT] The dimension of the array WORK
                         // Must be larger than 2*MIN(M,N)+MAX(M,N)
                         // For good performance, LWORK should generally be larger ????
                         // In original HRFK code, it was 50*n+1 when n==m
    Complex * work=new Complex[lwork];
    double * rwork=new double[5*(m<n ? n : m)];
    int info=0;          // [OUTPUT] 0=successful, >0 did not converge

     zgesvd_(&jobu, &jobvt, &m, &n, a, &lda, sigma.data(), u.values(), &ldu, vt.values(), &ldvt, work, &lwork, rwork, &info);
     delete [] work;
     delete [] rwork;
     delete [] a;
     return (info==0);
  }

  /*!
     Compute the inverse from the singular value decomposition (SVD).

     A = \a u * \a sigma * \a vt

     \a vt is the conjugate-transpose of v

     A^-1 = v * \a sigma^-1 * ut

     ut is the conjugate-transpose of \a u.

     Only square matrices A are supported: dimension of \a u and \a vt must be the same.
  */
  bool ComplexMatrix::invert(const ComplexMatrix& u, const QVector<double>& sigma, const ComplexMatrix& vt)
  {
    ASSERT(u.rowCount()==vt.rowCount());
    *this=vt.conjugate();
    transpose();

    int n=rowCount();
    for(int col=0; col<n; col++) {
      double si=1.0/sigma.at(col);
      for(int row=0; row<n; row++) {
        at(row, col)*=si;
      }
    }
    ComplexMatrix ut=u.conjugate();
    ut.transpose();
    operator*=(ut);
    return true;
  }

  /*!
     Compute the pseudo-inverse from the singular value decomposition (SVD).

     A = \a u * \a sigma * \a vt

     \a vt is the conjugate-transpose of v

     A^+ = v * \a sigma^+ * ut

     ut is the conjugate-transpose of \a u.
     sigma^+ is the pseudo-inverse of sigma, computed by taking
     the inverse of all non null diagonal elements.
  */
  bool ComplexMatrix::pseudoInvert(const ComplexMatrix& u, const QVector<double>& sigma, const ComplexMatrix& vt)
  {
    *this=vt.conjugate();
    transpose();

    int m=u.rowCount();
    int n=vt.rowCount();
    for(int col=0; col<n; col++) {
      double si=col<m ? sigma.at(col) : 0.0;
      if(si!=0.0) {
        si=1.0/si;
      }
      for(int row=0; row<m; row++) {
        at(row, col)*=si;
      }
    }
    ComplexMatrix ut=u.conjugate();
    ut.transpose();
    operator*=(ut);
    return true;
  }

  QString ComplexMatrix::toUserString(int precision, char format) const
  {
    QString str("    | ");
    for(int j=0; j<columnCount(); j++) {
      str+=QString(" %1 |").arg(j, 24, 10, QChar(' '));
    }
    str+="\n----|-";
    for(int j=0; j<columnCount(); j++) {
      str+="--------------------------|";
    }
    str+="\n";
    for(int i=0; i<rowCount(); i++) {
      str+=QString("%1 | ").arg(i, 3, 10, QChar(' '));
      for(int j=0; j<columnCount(); j++) {
        str+=QString(" %1+i%2 |")
             .arg(at(i, j).re(), 11, format, precision, QChar(' '))
             .arg(at(i, j).im(), 11, format, precision, QChar(' '));
      }
      str+="\n";
    }
    str+="----|-";
    for(int j=0; j<columnCount(); j++) {
      str+="--------------------------|";
    }
    str+="\n";
    return str;
  }

  QTextStream& operator<<(QTextStream& s, const ComplexMatrix& m)
  {
    TRACE;
    s << "\t";
    for(int j=0;j < m.columnCount();j++ ) s << j << "\t";
    s << "\n";
    for(int i=0;i < m.rowCount();i++ ) {
      MatrixRowIterator<Complex> it(m, i);
      s << i << ":\t";
      while(it.hasNext()) {
        it.next();
        s << (*it).re() << "+i*" << (*it).im() << "\t";
      }
      s << "\n";
    }
    s << flush;
    return s;
  }

} // namespace QGpCoreMath
