#ifndef DUNE_TECTONIC_GLOBALFRICTION_HH
#define DUNE_TECTONIC_GLOBALFRICTION_HH

#include <dune/common/fmatrix.hh>
#include <dune/common/fvector.hh>
#include <dune/istl/bcrsmatrix.hh>
#include <dune/istl/bvector.hh>
#include <dune/istl/matrixindexset.hh>

#include <dune/solvers/common/interval.hh>

#include "localfriction.hh"

template <class Matrix, class Vector>
class GlobalFriction {
protected:
  using ScalarVector = Dune::BlockVector<Dune::FieldVector<double, 1>>;

public:
  using IndexSet = Dune::MatrixIndexSet;
  using MatrixType = Matrix;
  using VectorType = Vector;
  using LocalMatrix = typename Matrix::block_type;
  using LocalVectorType = typename Vector::block_type;
  size_t static const block_size = LocalVectorType::dimension;
  using LocalNonlinearity = LocalFriction<block_size>;

  double operator()(Vector const &x) const {
    double tmp = 0;
    for (size_t i = 0; i < x.size(); ++i) {
      tmp += restriction(i)(x[i]);
    }
    return tmp;
  }

  /*
    Return a restriction of the outer function to the i'th node.
  */
  LocalNonlinearity const virtual &restriction(size_t i) const = 0;

  void addHessian(Vector const &v, Matrix &hessian) const {
    for (size_t i = 0; i < v.size(); ++i) {
      restriction(i).addHessian(v[i], hessian[i][i]);
    }
  }

  void directionalDomain(Vector const &, Vector const &,
                         Dune::Solvers::Interval<double> &dom) const {
    dom[0] = -std::numeric_limits<double>::max();
    dom[1] = std::numeric_limits<double>::max();
  }

  void directionalSubDiff(
      Vector const &u, Vector const &v,
      Dune::Solvers::Interval<double> &subdifferential) const {
    subdifferential[0] = subdifferential[1] = 0;
    for (size_t i = 0; i < u.size(); ++i) {
      Dune::Solvers::Interval<double> D;
      restriction(i).directionalSubDiff(u[i], v[i], D);
      subdifferential[0] += D[0];
      subdifferential[1] += D[1];
    }
  }

  void addHessianIndices(Dune::MatrixIndexSet &indices) const {
    for (size_t i = 0; i < indices.rows(); ++i)
      indices.add(i, i);
  }

  void addGradient(Vector const &v, Vector &gradient) const {
    for (size_t i = 0; i < v.size(); ++i)
      restriction(i).addGradient(v[i], gradient[i]);
  }

  double regularity(size_t i, typename Vector::block_type const &x) const {
    return restriction(i).regularity(x);
  }

  ScalarVector coefficientOfFriction(Vector const &x) const {
    ScalarVector ret(x.size());
    for (size_t i = 0; i < x.size(); ++i) {
      ret[i] = restriction(i).coefficientOfFriction(x[i]);
    }
    return ret;
  }

  void virtual updateAlpha(const std::vector<ScalarVector>& alpha) = 0;

};
#endif