#ifndef SRC_ASSEMBLERS_HH
#define SRC_ASSEMBLERS_HH

#include <dune/common/bitsetvector.hh>
#include <dune/common/function.hh>
#include <dune/istl/bcrsmatrix.hh>
#include <dune/istl/bvector.hh>

#include <dune/fufem/assemblers/assembler.hh>
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wsign-compare"
#include <dune/fufem/functionspacebases/p0basis.hh>
#pragma clang diagnostic pop
#include <dune/fufem/functionspacebases/p1nodalbasis.hh>
#include <dune/fufem/boundarypatch.hh>

#include "data-structures/friction/globalfriction.hh"
#include "data-structures/friction/globalfrictiondata.hh"

#include "data-structures/enums.hh"

template <class GridView, int dimension>
class MyAssembler {
public:
    using GV = GridView;
    using ScalarMatrix = Dune::BCRSMatrix<Dune::FieldMatrix<double, 1, 1>>;
    using Matrix =
      Dune::BCRSMatrix<Dune::FieldMatrix<double, dimension, dimension>>;
    using ScalarVector = Dune::BlockVector<Dune::FieldVector<double, 1>>;
    using Vector = Dune::BlockVector<Dune::FieldVector<double, dimension>>;

    using CellBasis = P0Basis<GridView, double>;
    using VertexBasis = P1NodalBasis<GridView, double>;

    CellBasis const cellBasis;
    VertexBasis const vertexBasis;
    GridView const &gridView;

private:
    using Grid = typename GridView::Grid;
    using LocalVector = typename Vector::block_type;
    using LocalScalarVector = typename ScalarVector::block_type;

    using LocalCellBasis = typename CellBasis::LocalFiniteElement;
    using LocalVertexBasis = typename VertexBasis::LocalFiniteElement;

    Assembler<CellBasis, CellBasis> cellAssembler;
    Assembler<VertexBasis, VertexBasis> vertexAssembler;

public:
    MyAssembler(GridView const &gridView);

    template <class LocalBoundaryFunctionalAssemblerType, class GlobalVectorType>
    void assembleBoundaryFunctional(LocalBoundaryFunctionalAssemblerType& localAssembler,
                                    GlobalVectorType& b,
                                    const BoundaryPatch<GridView>& boundaryPatch,
                                    bool initializeVector=true) const;

    void assembleFrictionalBoundaryMass(
            BoundaryPatch<GridView> const &frictionalBoundary,
            ScalarMatrix &frictionalBoundaryMass) const;

    void assembleMass(
            Dune::VirtualFunction<
            LocalVector, LocalScalarVector> const &densityFunction,
            Matrix &M) const;

    void assembleElasticity(
            double E,
            double nu,
            Matrix &A) const;

    void assembleViscosity(
            Dune::VirtualFunction<LocalVector, LocalScalarVector> const & shearViscosity,
            Dune::VirtualFunction<LocalVector, LocalScalarVector> const & bulkViscosity,
            Matrix &C) const;

    void assembleBodyForce(
            Dune::VirtualFunction<LocalVector, LocalVector> const &gravityField,
            Vector &f) const;

    void assembleNeumann(
            const BoundaryPatch<GridView>& neumannBoundary,
            const Dune::BitSetVector<dimension>& neumannNodes,
            Vector& f,
            const Dune::VirtualFunction<double, double>& neumann,
            double relativeTime) const;

    void assembleWeightedNormalStress(
            BoundaryPatch<GridView> const &frictionalBoundary,
            ScalarVector &weightedNormalStress,
            ScalarVector &weights,
            double youngModulus,
            double poissonRatio,
            Vector const &displacement) const;

    auto assembleFrictionNonlinearity(
            Config::FrictionModel frictionModel,
            BoundaryPatch<GridView> const &frictionalBoundary,
            GlobalFrictionData<dimension> const &frictionInfo,
            ScalarVector const &weightedNormalStress) const
    -> std::shared_ptr<GlobalFriction<Matrix, Vector>>;

    void assembleVonMisesStress(
            double youngModulus,
            double poissonRatio,
            Vector const &u,
            ScalarVector &stress) const;
};
#endif