#ifndef DUNE_GFE_LINEARALGEBRA_HH
#define DUNE_GFE_LINEARALGEBRA_HH

#include <dune/common/fmatrix.hh>
#include <dune/common/version.hh>

///////////////////////////////////////////////////////////////////////////////////////////
//  Various vector-space matrix methods
///////////////////////////////////////////////////////////////////////////////////////////

#if ADOLC_ADOUBLE_H
#if DUNE_VERSION_LT(DUNE_FUNCTIONS,2,7)
template< int m, int n, int p >
auto operator* ( const Dune::FieldMatrix< adouble, m, n > &A, const Dune::FieldMatrix< adouble, n, p > &B)
  -> Dune::FieldMatrix<adouble, m, p>
{
    typedef typename Dune::FieldMatrix< adouble, m, p > :: size_type size_type;
    Dune::FieldMatrix< adouble, m, p > ret;

    for( size_type i = 0; i < m; ++i ) {

        for( size_type j = 0; j < p; ++j ) {
            ret[ i ][ j ] = 0.0;
            for( size_type k = 0; k < n; ++k )
                ret[ i ][ j ] += A[ i ][ k ] * B[ k ][ j ];
        }
    }
    return ret;
}

template< int m, int n, int p >
auto operator* ( const Dune::FieldMatrix< adouble, m, n > &A, const Dune::FieldMatrix< double, n, p > &B)
  -> Dune::FieldMatrix<adouble, m, p>
{
    typedef typename Dune::FieldMatrix< adouble, m, p > :: size_type size_type;
    Dune::FieldMatrix< adouble, m, p > ret;

    for( size_type i = 0; i < m; ++i ) {

        for( size_type j = 0; j < p; ++j ) {
            ret[ i ][ j ] = 0.0;
            for( size_type k = 0; k < n; ++k )
                ret[ i ][ j ] += A[ i ][ k ] * B[ k ][ j ];
        }
    }
    return ret;
}

template< int m, int n, int p >
auto operator* ( const Dune::FieldMatrix< double, m, n > &A, const Dune::FieldMatrix< adouble, n, p > &B)
  -> Dune::FieldMatrix<adouble, m, p>
{
    typedef typename Dune::FieldMatrix< adouble, m, p > :: size_type size_type;
    Dune::FieldMatrix< adouble, m, p > ret;

    for( size_type i = 0; i < m; ++i ) {

        for( size_type j = 0; j < p; ++j ) {
            ret[ i ][ j ] = 0.0;
            for( size_type k = 0; k < n; ++k )
                ret[ i ][ j ] += A[ i ][ k ] * B[ k ][ j ];
        }
    }
    return ret;
}
#endif
#endif

#if DUNE_VERSION_LT(DUNE_FUNCTIONS,2,7)
//! calculates ret = A + B
template< class K, int m, int n>
Dune::FieldMatrix<K,m,n> operator+ ( const Dune::FieldMatrix<K, m, n> &A, const Dune::FieldMatrix<K,m,n> &B)
{
    typedef typename Dune::FieldMatrix<K,m,n> :: size_type size_type;
    Dune::FieldMatrix<K,m,n> ret;

    for( size_type i = 0; i < m; ++i )
        for( size_type j = 0; j < n; ++j )
            ret[i][j] = A[i][j] + B[i][j];

    return ret;
}

//! calculates ret = A - B
template <class T, int m, int n>
auto operator- ( const Dune::FieldMatrix< T, m, n > &A, const Dune::FieldMatrix< T, m, n > &B)
  -> Dune::FieldMatrix<T, m, n>
{
    Dune::FieldMatrix<T,m,n> result;
    typedef typename decltype(result)::size_type size_type;

    for( size_type i = 0; i < m; ++i )
        for( size_type j = 0; j < n; ++j )
            result[i][j] = A[i][j] - B[i][j];

    return result;
}
#endif

#if ADOLC_ADOUBLE_H
#if DUNE_VERSION_LT(DUNE_FUNCTIONS,2,7)
//! calculates ret = A - B
template <int m, int n>
auto operator- ( const Dune::FieldMatrix< adouble, m, n > &A, const Dune::FieldMatrix< double, m, n > &B)
  -> Dune::FieldMatrix<adouble, m, n>
{
    Dune::FieldMatrix<adouble,m,n> result;
    typedef typename decltype(result)::size_type size_type;

    for( size_type i = 0; i < m; ++i )
        for( size_type j = 0; j < n; ++j )
            result[i][j] = A[i][j] - B[i][j];

    return result;
}
#endif

//! calculates ret = s*A
template< int m, int n>
auto operator* ( const double& s, const Dune::FieldMatrix<adouble, m, n> &A)
  -> Dune::FieldMatrix<adouble,m,n>
{
    typedef typename Dune::FieldMatrix<adouble,m,n> :: size_type size_type;
    Dune::FieldMatrix<adouble,m,n> ret;

    for( size_type i = 0; i < m; ++i )
        for( size_type j = 0; j < n; ++j )
            ret[i][j] = s * A[i][j];

    return ret;
}
#endif

#if DUNE_VERSION_LT(DUNE_FUNCTIONS,2,7)
//! calculates ret = s*A
template< int m, int n>
auto operator* ( const double& s, const Dune::FieldMatrix<double, m, n> &A)
  -> Dune::FieldMatrix<double,m,n>
{
    typedef typename Dune::FieldMatrix<double,m,n> :: size_type size_type;
    Dune::FieldMatrix<double,m,n> ret;

    for( size_type i = 0; i < m; ++i )
        for( size_type j = 0; j < n; ++j )
            ret[i][j] = s * A[i][j];

    return ret;
}

//! calculates ret = A/s
template< class K, int m, int n>
Dune::FieldMatrix<K,m,n> operator/ ( const Dune::FieldMatrix<K, m, n> &A, const K& s)
{
    typedef typename Dune::FieldMatrix<K,m,n> :: size_type size_type;
    Dune::FieldMatrix<K,m,n> ret;

    for( size_type i = 0; i < m; ++i )
        for( size_type j = 0; j < n; ++j )
            ret[i][j] = A[i][j] / s;

    return ret;
}

//! calculates ret = A/s
template< class K, int m>
Dune::FieldVector<K,m> operator/ ( const Dune::FieldVector<K, m> &A, const K& s)
{
    typedef typename Dune::FieldVector<K,m> :: size_type size_type;
    Dune::FieldVector<K,m> result;

    for( size_type i = 0; i < m; ++i )
        result[i] = A[i] / s;

    return result;
}
#endif


///////////////////////////////////////////////////////////////////////////////////////////
//  Various other matrix methods
///////////////////////////////////////////////////////////////////////////////////////////

namespace Dune {

  namespace GFE {

  /** \brief Return the trace of a matrix */
  template <class T, int n>
  static T trace(const FieldMatrix<T,n,n>& A)
  {
    T trace = 0;
    for (int i=0; i<n; i++)
      trace += A[i][i];
    return trace;
  }

  /** \brief Return the square of the trace of a matrix */
  template <class T, int n>
  static T traceSquared(const FieldMatrix<T,n,n>& A)
  {
    T trace = 0;
    for (int i=0; i<n; i++)
      trace += A[i][i];
    return trace*trace;
  }

  /** \brief Compute the symmetric part of a matrix A, i.e. \f$ \frac 12 (A + A^T) \f$ */
  template <class T, int n>
  static FieldMatrix<T,n,n> sym(const FieldMatrix<T,n,n>& A)
  {
    FieldMatrix<T,n,n> result;
    for (int i=0; i<n; i++)
      for (int j=0; j<n; j++)
        result[i][j] = 0.5 * (A[i][j] + A[j][i]);
    return result;
  }

  /** \brief Compute the antisymmetric part of a matrix A, i.e. \f$ \frac 12 (A - A^T) \f$ */
  template <class T, int n>
  static FieldMatrix<T,n,n> skew(const FieldMatrix<T,n,n>& A)
  {
    FieldMatrix<T,n,n> result;
    for (int i=0; i<n; i++)
      for (int j=0; j<n; j++)
        result[i][j] = 0.5 * (A[i][j] - A[j][i]);
    return result;
  }

  /** \brief Compute the deviator of a matrix A */
  template <class T, int n>
  static FieldMatrix<T,n,n> dev(const FieldMatrix<T,n,n>& A)
  {
    FieldMatrix<T,n,n> result = A;
    auto t = trace(A);
    for (int i=0; i<n; i++)
      result[i][i] -= t / n;
    return result;
  }

  /** \brief Return the transposed matrix */
  template <class T, int n>
  static FieldMatrix<T,n,n> transpose(const FieldMatrix<T,n,n>& A)
  {
    FieldMatrix<T,n,n> result;

    for (int i=0; i<n; i++)
      for (int j=0; j<n; j++)
        result[i][j] = A[j][i];

    return result;
  }

  /** \brief The Frobenius (i.e., componentwise) product of two matrices */
  template <class T, int n>
  static T frobeniusProduct(const FieldMatrix<T,n,n>& A, const FieldMatrix<T,n,n>& B)
  {
    T result(0.0);

    for (int i=0; i<n; i++)
      for (int j=0; j<n; j++)
        result += A[i][j] * B[i][j];

    return result;
  }

  template <class T, int n>
  static auto dyadicProduct(const FieldVector<T,n>& A, const FieldVector<T,n>& B)
  {
    FieldMatrix<T,n,n> result;

    for (int i=0; i<n; i++)
      for (int j=0; j<n; j++)
        result[i][j] = A[i]*B[j];

    return result;
  }

#if ADOLC_ADOUBLE_H
  template <int n>
  static auto dyadicProduct(const FieldVector<adouble,n>& A, const FieldVector<double,n>& B)
    -> FieldMatrix<adouble,n,n>
  {
    FieldMatrix<adouble,n,n> result;

    for (int i=0; i<n; i++)
      for (int j=0; j<n; j++)
        result[i][j] = A[i]*B[j];

    return result;
  }
#endif

  }
}


#endif