// Copyright (c) 2010-2024, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.

#ifndef MFEM_LIBCEED_INTEG
#define MFEM_LIBCEED_INTEG

#include "../../../config/config.hpp"
#include "../../fespace.hpp"
#include "../../gridfunc.hpp"
#include "operator.hpp"
#include "coefficient.hpp"
#include "restriction.hpp"
#include "util.hpp"
#include "ceed.hpp"

namespace mfem
{

namespace ceed
{

/** The different evaluation modes available for PA and MF CeedIntegrator. */
enum class EvalMode { None, Interp, Grad, InterpAndGrad };

#ifdef MFEM_USE_CEED
/** This structure is a template interface for the Assemble methods of
    PAIntegrator and MFIntegrator. See ceed/mass.cpp for an example. */
struct OperatorInfo
{
   /** The path to the qFunction header. */
   const char *header;
   /** The name of the qFunction to build a partially assembled CeedOperator
       with a constant Coefficient. */
   const char *build_func_const;
   /** The qFunction to build a partially assembled CeedOperator with a constant
       Coefficient. */
   CeedQFunctionUser build_qf_const;
   /** The name of the qFunction to build a partially assembled CeedOperator
       with a variable Coefficient. */
   const char *build_func_quad;
   /** The qFunction to build a partially assembled CeedOperator with a variable
       Coefficient. */
   CeedQFunctionUser build_qf_quad;
   /** The name of the qFunction to apply a partially assembled CeedOperator. */
   const char *apply_func;
   /** The qFunction to apply a partially assembled CeedOperator. */
   CeedQFunctionUser apply_qf;
   /** The name of the qFunction to apply a matrix-free CeedOperator with a
       constant Coefficient. */
   const char *apply_func_mf_const;
   /** The qFunction to apply a matrix-free CeedOperator with a constant
       Coefficient. */
   CeedQFunctionUser apply_qf_mf_const;
   /** The name of the qFunction to apply a matrix-free CeedOperator with a
       variable Coefficient. */
   const char *apply_func_mf_quad;
   /** The qFunction to apply a matrix-free CeedOperator with a variable
       Coefficient. */
   CeedQFunctionUser apply_qf_mf_quad;
   /** The EvalMode on the trial basis functions. */
   EvalMode trial_op;
   /** The EvalMode on the test basis functions. */
   EvalMode test_op;
   /** The size of the data at each quadrature point. */
   int qdatasize;
};
#endif

/** This class represent a partially assembled operator using libCEED. */
class PAIntegrator : public ceed::Operator
{
#ifdef MFEM_USE_CEED
protected:
   CeedBasis  trial_basis, test_basis, mesh_basis;
   CeedElemRestriction trial_restr, test_restr, mesh_restr, restr_i;
   CeedQFunction build_qfunc, apply_qfunc;
   CeedVector node_coords, qdata;
   Coefficient *coeff;
   CeedQFunctionContext build_ctx;
   CeedOperator build_oper;

public:
   PAIntegrator()
      : Operator(),
        trial_basis(nullptr), test_basis(nullptr), mesh_basis(nullptr),
        trial_restr(nullptr), test_restr(nullptr), mesh_restr(nullptr),
        restr_i(nullptr),
        build_qfunc(nullptr), apply_qfunc(nullptr), node_coords(nullptr),
        qdata(nullptr), coeff(nullptr), build_ctx(nullptr), build_oper(nullptr)
   { }

   /** @brief This method assembles the `PAIntegrator` with the given
       `CeedOperatorInfo` @a info, an `mfem::FiniteElementSpace` @a fes, an
       `mfem::IntegrationRule` @a ir, and `mfem::Coefficient` or
       `mfem::VectorCoefficient` @a Q.
       The `CeedOperatorInfo` type is expected to inherit from `OperatorInfo`,
       and contain a `Context` type relevant to the qFunctions.

       @param[in] info is the structure describing the CeedOperator to assemble.
       @param[in] fes is the finite element space.
       @param[in] ir is the integration rule for the operator.
       @param[in] Q is the coefficient from the `Integrator`. */
   template <typename CeedOperatorInfo, typename CoeffType>
   void Assemble(CeedOperatorInfo &info,
                 const mfem::FiniteElementSpace &fes,
                 const mfem::IntegrationRule &ir,
                 CoeffType *Q)
   {
      Assemble(info, fes, ir, fes.GetNE(), nullptr, Q);
   }

   /** @brief This method assembles the `PAIntegrator` with the given
       `CeedOperatorInfo` @a info, an `mfem::FiniteElementSpace` @a fes, an
       `mfem::IntegrationRule` @a ir, and `mfem::Coefficient` or
       `mfem::VectorCoefficient` @a Q for the elements given by the indices
       @a indices.
       The `CeedOperatorInfo` type is expected to inherit from `OperatorInfo`,
       and contain a `Context` type relevant to the qFunctions.

       @param[in] info is the structure describing the CeedOperator to assemble.
       @param[in] fes is the finite element space.
       @param[in] ir is the integration rule for the operator.
       @param[in] nelem The number of elements.
       @param[in] indices The indices of the elements of same type in the
                          `FiniteElementSpace`. If `indices == nullptr`, assumes
                          that the `FiniteElementSpace` is not mixed.
       @param[in] Q is the coefficient from the `Integrator`. */
   template <typename CeedOperatorInfo, typename CoeffType>
   void Assemble(CeedOperatorInfo &info,
                 const mfem::FiniteElementSpace &fes,
                 const mfem::IntegrationRule &ir,
                 int nelem,
                 const int* indices,
                 CoeffType *Q)
   {
      Assemble(info, fes, fes, ir, nelem, indices, Q);
   }

   /** This method assembles the PAIntegrator for mixed forms.

       @param[in] info the `CeedOperatorInfo` describing the `CeedOperator`,
                       the `CeedOperatorInfo` type is expected to inherit from
                       `OperatorInfo` and contain a `Context` type relevant to
                       the qFunctions.
       @param[in] trial_fes the trial `FiniteElementSpace` for the form,
       @param[in] test_fes the test `FiniteElementSpace` for the form,
       @param[in] ir the `IntegrationRule` for the numerical integration,
       @param[in] Q `Coefficient` or `VectorCoefficient`. */
   template <typename CeedOperatorInfo, typename CoeffType>
   void Assemble(CeedOperatorInfo &info,
                 const mfem::FiniteElementSpace &trial_fes,
                 const mfem::FiniteElementSpace &test_fes,
                 const mfem::IntegrationRule &ir,
                 CoeffType *Q)
   {
      Assemble(info, trial_fes, test_fes, ir, trial_fes.GetNE(), nullptr, Q);
   }

   /** This method assembles the PAIntegrator for mixed forms on mixed meshes.

       @param[in] info the `CeedOperatorInfo` describing the `CeedOperator`,
                       the `CeedOperatorInfo` type is expected to inherit from
                       `OperatorInfo` and contain a `Context` type relevant to
                       the qFunctions.
       @param[in] trial_fes the trial `FiniteElementSpace` for the form,
       @param[in] test_fes the test `FiniteElementSpace` for the form,
       @param[in] ir the `IntegrationRule` for the numerical integration,
       @param[in] nelem The number of elements,
       @param[in] indices The indices of the elements of same type in the
                          `FiniteElementSpace`. If `indices == nullptr`, assumes
                          that the `FiniteElementSpace` is not mixed,
       @param[in] Q `Coefficient` or `VectorCoefficient`. */
   template <typename CeedOperatorInfo, typename CoeffType>
   void Assemble(CeedOperatorInfo &info,
                 const mfem::FiniteElementSpace &trial_fes,
                 const mfem::FiniteElementSpace &test_fes,
                 const mfem::IntegrationRule &ir,
                 int nelem,
                 const int* indices,
                 CoeffType *Q)
   {
      Ceed ceed(internal::ceed);
      mfem::Mesh &mesh = *trial_fes.GetMesh();
      MFEM_VERIFY(!(!indices && mesh.GetNumGeometries(mesh.Dimension()) > 1),
                  "Use ceed::MixedIntegrator on mixed meshes.");
      InitCoefficient(Q, mesh, ir, nelem, indices, coeff, info.ctx);
      bool const_coeff = coeff->IsConstant();
      std::string build_func = const_coeff ? info.build_func_const
                               : info.build_func_quad;
      CeedQFunctionUser build_qf = const_coeff ? info.build_qf_const
                                   : info.build_qf_quad;
      PAOperator op {info.qdatasize, info.header,
                     build_func, build_qf,
                     info.apply_func, info.apply_qf,
                     info.trial_op,
                     info.test_op
                    };
      CeedInt dim = mesh.SpaceDimension();
      CeedInt trial_vdim = trial_fes.GetVDim();
      CeedInt test_vdim = test_fes.GetVDim();

      mesh.EnsureNodes();
      if ( &trial_fes == &test_fes )
      {
         InitBasisAndRestriction(trial_fes, ir, nelem, indices,
                                 ceed, &trial_basis, &trial_restr);
         test_basis = trial_basis;
         test_restr = trial_restr;
      }
      else
      {
         InitBasisAndRestriction(trial_fes, ir, nelem, indices,
                                 ceed, &trial_basis, &trial_restr);
         InitBasisAndRestriction(test_fes, ir, nelem, indices,
                                 ceed, &test_basis, &test_restr);
      }

      const mfem::FiniteElementSpace *mesh_fes = mesh.GetNodalFESpace();
      MFEM_VERIFY(mesh_fes, "the Mesh has no nodal FE space");
      InitBasisAndRestriction(*mesh_fes, ir, nelem, indices,
                              ceed, &mesh_basis, &mesh_restr);

      CeedInt trial_nqpts, test_nqpts;
      CeedBasisGetNumQuadraturePoints(trial_basis, &trial_nqpts);
      CeedBasisGetNumQuadraturePoints(test_basis, &test_nqpts);
      MFEM_VERIFY(trial_nqpts == test_nqpts,
                  "Trial and test basis must have the same number of quadrature"
                  " points.");
      CeedInt nqpts = trial_nqpts;

      const int qdatasize = op.qdatasize;
      InitStridedRestriction(*mesh_fes, nelem, nqpts, qdatasize,
                             CEED_STRIDES_BACKEND,
                             &restr_i);

      InitVector(*mesh.GetNodes(), node_coords);

      CeedVectorCreate(ceed, nelem * nqpts * qdatasize, &qdata);

      // Context data to be passed to the Q-function.
      info.ctx.dim = mesh.Dimension();
      info.ctx.space_dim = mesh.SpaceDimension();
      info.ctx.vdim = trial_fes.GetVDim();

      std::string qf_file = GetCeedPath() + op.header;
      std::string qf = qf_file + op.build_func;
      CeedQFunctionCreateInterior(ceed, 1, op.build_qf, qf.c_str(),
                                  &build_qfunc);

      // Create the Q-function that builds the operator (i.e. computes its
      // quadrature data) and set its context data.
      if (VariableCoefficient *var_coeff =
             dynamic_cast<VariableCoefficient*>(coeff))
      {
         CeedQFunctionAddInput(build_qfunc, "coeff", coeff->ncomp,
                               var_coeff->emode);
      }
      CeedQFunctionAddInput(build_qfunc, "dx", dim * dim, CEED_EVAL_GRAD);
      CeedQFunctionAddInput(build_qfunc, "weights", 1, CEED_EVAL_WEIGHT);
      CeedQFunctionAddOutput(build_qfunc, "qdata", qdatasize, CEED_EVAL_NONE);

      CeedQFunctionContextCreate(ceed, &build_ctx);
      CeedQFunctionContextSetData(build_ctx, CEED_MEM_HOST,
                                  CEED_COPY_VALUES,
                                  sizeof(info.ctx),
                                  &info.ctx);
      CeedQFunctionSetContext(build_qfunc, build_ctx);

      // Create the operator that builds the quadrature data for the operator.
      CeedOperatorCreate(ceed, build_qfunc, NULL, NULL, &build_oper);
      if (GridCoefficient *gridCoeff = dynamic_cast<GridCoefficient*>(coeff))
      {
         InitBasisAndRestriction(*gridCoeff->gf.FESpace(), ir,
                                 nelem, indices, ceed,
                                 &gridCoeff->basis,
                                 &gridCoeff->restr);
         CeedOperatorSetField(build_oper, "coeff", gridCoeff->restr,
                              gridCoeff->basis, gridCoeff->coeffVector);
      }
      else if (QuadCoefficient *quadCoeff =
                  dynamic_cast<QuadCoefficient*>(coeff))
      {
         const int ncomp = quadCoeff->ncomp;
         CeedInt strides[3] = {ncomp, 1, ncomp*nqpts};
         InitStridedRestriction(*mesh.GetNodalFESpace(),
                                nelem, nqpts, ncomp, strides,
                                &quadCoeff->restr);
         CeedOperatorSetField(build_oper, "coeff", quadCoeff->restr,
                              CEED_BASIS_NONE, quadCoeff->coeffVector);
      }
      CeedOperatorSetField(build_oper, "dx", mesh_restr,
                           mesh_basis, CEED_VECTOR_ACTIVE);
      CeedOperatorSetField(build_oper, "weights", CEED_ELEMRESTRICTION_NONE,
                           mesh_basis, CEED_VECTOR_NONE);
      CeedOperatorSetField(build_oper, "qdata", restr_i,
                           CEED_BASIS_NONE, CEED_VECTOR_ACTIVE);

      // Compute the quadrature data for the operator.
      CeedOperatorApply(build_oper, node_coords, qdata, CEED_REQUEST_IMMEDIATE);

      // Create the Q-function that defines the action of the operator.
      qf = qf_file + op.apply_func;
      CeedQFunctionCreateInterior(ceed, 1, op.apply_qf, qf.c_str(),
                                  &apply_qfunc);
      // input
      switch (op.trial_op)
      {
         case EvalMode::None:
            CeedQFunctionAddInput(apply_qfunc, "u", trial_vdim, CEED_EVAL_NONE);
            break;
         case EvalMode::Interp:
            CeedQFunctionAddInput(apply_qfunc, "u", trial_vdim, CEED_EVAL_INTERP);
            break;
         case EvalMode::Grad:
            CeedQFunctionAddInput(apply_qfunc, "gu", trial_vdim*dim, CEED_EVAL_GRAD);
            break;
         case EvalMode::InterpAndGrad:
            CeedQFunctionAddInput(apply_qfunc, "u", trial_vdim, CEED_EVAL_INTERP);
            CeedQFunctionAddInput(apply_qfunc, "gu", trial_vdim*dim, CEED_EVAL_GRAD);
            break;
      }
      // qdata
      CeedQFunctionAddInput(apply_qfunc, "qdata", qdatasize, CEED_EVAL_NONE);
      // output
      switch (op.test_op)
      {
         case EvalMode::None:
            CeedQFunctionAddOutput(apply_qfunc, "v", test_vdim, CEED_EVAL_NONE);
            break;
         case EvalMode::Interp:
            CeedQFunctionAddOutput(apply_qfunc, "v", test_vdim, CEED_EVAL_INTERP);
            break;
         case EvalMode::Grad:
            CeedQFunctionAddOutput(apply_qfunc, "gv", test_vdim*dim, CEED_EVAL_GRAD);
            break;
         case EvalMode::InterpAndGrad:
            CeedQFunctionAddOutput(apply_qfunc, "v", test_vdim, CEED_EVAL_INTERP);
            CeedQFunctionAddOutput(apply_qfunc, "gv", test_vdim*dim, CEED_EVAL_GRAD);
            break;
      }
      CeedQFunctionSetContext(apply_qfunc, build_ctx);

      // Create the operator.
      CeedOperatorCreate(ceed, apply_qfunc, NULL, NULL, &oper);
      // input
      switch (op.trial_op)
      {
         case EvalMode::None:
            CeedOperatorSetField(oper, "u", trial_restr,
                                 CEED_BASIS_NONE, CEED_VECTOR_ACTIVE);
            break;
         case EvalMode::Interp:
            CeedOperatorSetField(oper, "u", trial_restr, trial_basis, CEED_VECTOR_ACTIVE);
            break;
         case EvalMode::Grad:
            CeedOperatorSetField(oper, "gu", trial_restr, trial_basis, CEED_VECTOR_ACTIVE);
            break;
         case EvalMode::InterpAndGrad:
            CeedOperatorSetField(oper, "u", trial_restr, trial_basis, CEED_VECTOR_ACTIVE);
            CeedOperatorSetField(oper, "gu", trial_restr, trial_basis, CEED_VECTOR_ACTIVE);
            break;
      }
      // qdata
      CeedOperatorSetField(oper, "qdata", restr_i, CEED_BASIS_NONE,
                           qdata);
      // output
      switch (op.test_op)
      {
         case EvalMode::None:
            CeedOperatorSetField(oper, "v", test_restr,
                                 CEED_BASIS_NONE, CEED_VECTOR_ACTIVE);
            break;
         case EvalMode::Interp:
            CeedOperatorSetField(oper, "v", test_restr, test_basis, CEED_VECTOR_ACTIVE);
            break;
         case EvalMode::Grad:
            CeedOperatorSetField(oper, "gv", test_restr, test_basis, CEED_VECTOR_ACTIVE);
            break;
         case EvalMode::InterpAndGrad:
            CeedOperatorSetField(oper, "v", test_restr, test_basis, CEED_VECTOR_ACTIVE);
            CeedOperatorSetField(oper, "gv", test_restr, test_basis, CEED_VECTOR_ACTIVE);
            break;
      }

      CeedVectorCreate(ceed, trial_vdim*trial_fes.GetNDofs(), &u);
      CeedVectorCreate(ceed, test_vdim*test_fes.GetNDofs(), &v);
   }

   virtual ~PAIntegrator()
   {
      CeedQFunctionDestroy(&build_qfunc);
      CeedQFunctionDestroy(&apply_qfunc);
      CeedQFunctionContextDestroy(&build_ctx);
      CeedVectorDestroy(&node_coords);
      CeedVectorDestroy(&qdata);
      delete coeff;
      CeedOperatorDestroy(&build_oper);
   }

private:
   /** This structure contains the data to assemble a partially assembled
       operator with libCEED. */
   struct PAOperator
   {
      /** The number of quadrature data at each quadrature point. */
      int qdatasize;
      /** The path to the header containing the functions for libCEED. */
      std::string header;
      /** The name of the Qfunction to build the quadrature data. */
      std::string build_func;
      /** The Qfunction to build the quadrature data. */
      CeedQFunctionUser build_qf;
      /** The name of the Qfunction to apply the operator. */
      std::string apply_func;
      /** The Qfunction to apply the operator. */
      CeedQFunctionUser apply_qf;
      /** The evaluation mode to apply to the trial function (CEED_EVAL_INTERP,
          CEED_EVAL_GRAD, etc.) */
      EvalMode trial_op;
      /** The evaluation mode to apply to the test function ( CEED_EVAL_INTERP,
          CEED_EVAL_GRAD, etc.)*/
      EvalMode test_op;
   };
#endif
};

/** This class represent a matrix-free operator using libCEED. */
class MFIntegrator : public ceed::Operator
{
#ifdef MFEM_USE_CEED
protected:
   CeedBasis trial_basis, test_basis, mesh_basis;
   CeedElemRestriction trial_restr, test_restr, mesh_restr, restr_i;
   CeedQFunction apply_qfunc;
   CeedVector node_coords, qdata;
   Coefficient *coeff;
   CeedQFunctionContext build_ctx;

public:
   MFIntegrator()
      : Operator(),
        trial_basis(nullptr), test_basis(nullptr), mesh_basis(nullptr),
        trial_restr(nullptr), test_restr(nullptr), mesh_restr(nullptr),
        restr_i(nullptr),
        apply_qfunc(nullptr), node_coords(nullptr),
        qdata(nullptr), coeff(nullptr), build_ctx(nullptr) { }

   /** @brief This method assembles the `MFIntegrator` with the given
       `CeedOperatorInfo` @a info, an `mfem::FiniteElementSpace` @a fes, an
       `mfem::IntegrationRule` @a ir, and `mfem::Coefficient` or
       `mfem::VectorCoefficient` @a Q.
       The `CeedOperatorInfo` type is expected to inherit from `OperatorInfo`,
       and contain a `Context` type relevant to the qFunctions.

       @param[in] info is the structure describing the CeedOperator to assemble.
       @param[in] fes is the finite element space.
       @param[in] ir is the integration rule for the operator.
       @param[in] Q is the coefficient from the `Integrator`. */
   template <typename CeedOperatorInfo, typename CoeffType>
   void Assemble(CeedOperatorInfo &info,
                 const mfem::FiniteElementSpace &fes,
                 const mfem::IntegrationRule &ir,
                 CoeffType *Q)
   {
      Assemble(info, fes, ir, fes.GetNE(), nullptr, Q);
   }

   /** @brief This method assembles the `MFIntegrator` with the given
       `CeedOperatorInfo` @a info, an `mfem::FiniteElementSpace` @a fes, an
       `mfem::IntegrationRule` @a ir, and `mfem::Coefficient` or
       `mfem::VectorCoefficient` @a Q for the elements given by the indices
       @a indices.
       The `CeedOperatorInfo` type is expected to inherit from `OperatorInfo`,
       and contain a `Context` type relevant to the qFunctions.

       @param[in] info is the structure describing the CeedOperator to assemble.
       @param[in] fes is the finite element space.
       @param[in] ir is the integration rule for the operator.
       @param[in] nelem The number of elements.
       @param[in] indices The indices of the elements of same type in the
                          `FiniteElementSpace`. If `indices == nullptr`, assumes
                          that the `FiniteElementSpace` is not mixed.
       @param[in] Q is the coefficient from the `Integrator`. */
   template <typename CeedOperatorInfo, typename CoeffType>
   void Assemble(CeedOperatorInfo &info,
                 const mfem::FiniteElementSpace &fes,
                 const mfem::IntegrationRule &ir,
                 int nelem,
                 const int* indices,
                 CoeffType *Q)
   {
      Assemble(info, fes, fes, ir, nelem, indices, Q);
   }

   /** This method assembles the MFIntegrator for mixed forms.

       @param[in] info the `CeedOperatorInfo` describing the `CeedOperator`,
                       the `CeedOperatorInfo` type is expected to inherit from
                       `OperatorInfo` and contain a `Context` type relevant to
                       the qFunctions.
       @param[in] trial_fes the trial `FiniteElementSpace` for the form,
       @param[in] test_fes the test `FiniteElementSpace` for the form,
       @param[in] ir the `IntegrationRule` for the numerical integration,
       @param[in] Q `Coefficient` or `VectorCoefficient`. */
   template <typename CeedOperatorInfo, typename CoeffType>
   void Assemble(CeedOperatorInfo &info,
                 const mfem::FiniteElementSpace &trial_fes,
                 const mfem::FiniteElementSpace &test_fes,
                 const mfem::IntegrationRule &ir,
                 CoeffType *Q)
   {
      Assemble(info, trial_fes, test_fes, ir, trial_fes.GetNE(), nullptr, Q);
   }

   /** This method assembles the MFIntegrator for mixed forms.

       @param[in] info the `CeedOperatorInfo` describing the `CeedOperator`,
                       the `CeedOperatorInfo` type is expected to inherit from
                       `OperatorInfo` and contain a `Context` type relevant to
                       the qFunctions.
       @param[in] trial_fes the trial `FiniteElementSpace` for the form,
       @param[in] test_fes the test `FiniteElementSpace` for the form,
       @param[in] ir the `IntegrationRule` for the numerical integration,
       @param[in] nelem The number of elements,
       @param[in] indices The indices of the elements of same type in the
                          `FiniteElementSpace`. If `indices == nullptr`, assumes
                          that the `FiniteElementSpace` is not mixed,
       @param[in] Q `Coefficient` or `VectorCoefficient`. */
   template <typename CeedOperatorInfo, typename CoeffType>
   void Assemble(CeedOperatorInfo &info,
                 const mfem::FiniteElementSpace &trial_fes,
                 const mfem::FiniteElementSpace &test_fes,
                 const mfem::IntegrationRule &ir,
                 int nelem,
                 const int* indices,
                 CoeffType *Q)
   {
      Ceed ceed(internal::ceed);
      Mesh &mesh = *trial_fes.GetMesh();
      MFEM_VERIFY(!(!indices && mesh.GetNumGeometries(mesh.Dimension()) > 1),
                  "Use ceed::MixedIntegrator on mixed meshes.");
      InitCoefficient(Q, mesh, ir, nelem, indices, coeff, info.ctx);
      bool const_coeff = coeff->IsConstant();
      std::string apply_func = const_coeff ? info.apply_func_mf_const
                               : info.apply_func_mf_quad;
      CeedQFunctionUser apply_qf = const_coeff ? info.apply_qf_mf_const
                                   : info.apply_qf_mf_quad;
      MFOperator op {info.header,
                     apply_func, apply_qf,
                     info.trial_op,
                     info.test_op
                    };

      CeedInt dim = mesh.SpaceDimension();
      CeedInt trial_vdim = trial_fes.GetVDim();
      CeedInt test_vdim = test_fes.GetVDim();

      mesh.EnsureNodes();
      if ( &trial_fes == &test_fes )
      {
         InitBasisAndRestriction(trial_fes, ir, nelem, indices, ceed,
                                 &trial_basis, &trial_restr);
         test_basis = trial_basis;
         test_restr = trial_restr;
      }
      else
      {
         InitBasisAndRestriction(trial_fes, ir, nelem, indices, ceed,
                                 &trial_basis, &trial_restr);
         InitBasisAndRestriction(test_fes, ir, nelem, indices, ceed,
                                 &test_basis, &test_restr);
      }

      const mfem::FiniteElementSpace *mesh_fes = mesh.GetNodalFESpace();
      MFEM_VERIFY(mesh_fes, "the Mesh has no nodal FE space");
      InitBasisAndRestriction(*mesh_fes, ir, nelem, indices, ceed, &mesh_basis,
                              &mesh_restr);

      CeedInt trial_nqpts, test_nqpts;
      CeedBasisGetNumQuadraturePoints(trial_basis, &trial_nqpts);
      CeedBasisGetNumQuadraturePoints(trial_basis, &test_nqpts);
      MFEM_VERIFY(trial_nqpts == test_nqpts,
                  "Trial and test basis must have the same number of quadrature"
                  " points.");
      CeedInt nqpts = trial_nqpts;

      InitVector(*mesh.GetNodes(), node_coords);

      // Context data to be passed to the Q-function.
      info.ctx.dim = mesh.Dimension();
      info.ctx.space_dim = mesh.SpaceDimension();
      info.ctx.vdim = trial_fes.GetVDim();

      std::string qf_file = GetCeedPath() + op.header;
      std::string qf = qf_file + op.apply_func;
      CeedQFunctionCreateInterior(ceed, 1, op.apply_qf, qf.c_str(),
                                  &apply_qfunc);

      // Create the Q-function that builds the operator (i.e. computes its
      // quadrature data) and set its context data.
      if (VariableCoefficient *var_coeff =
             dynamic_cast<VariableCoefficient*>(coeff))
      {
         CeedQFunctionAddInput(apply_qfunc, "coeff", coeff->ncomp,
                               var_coeff->emode);
      }
      // input
      switch (op.trial_op)
      {
         case EvalMode::None:
            CeedQFunctionAddInput(apply_qfunc, "u", trial_vdim,
                                  CEED_EVAL_NONE);
            break;
         case EvalMode::Interp:
            CeedQFunctionAddInput(apply_qfunc, "u", trial_vdim,
                                  CEED_EVAL_INTERP);
            break;
         case EvalMode::Grad:
            CeedQFunctionAddInput(apply_qfunc, "gu", trial_vdim*dim,
                                  CEED_EVAL_GRAD);
            break;
         case EvalMode::InterpAndGrad:
            CeedQFunctionAddInput(apply_qfunc, "u", trial_vdim,
                                  CEED_EVAL_INTERP);
            CeedQFunctionAddInput(apply_qfunc, "gu", trial_vdim*dim,
                                  CEED_EVAL_GRAD);
            break;
      }
      CeedQFunctionAddInput(apply_qfunc, "dx", dim * dim, CEED_EVAL_GRAD);
      CeedQFunctionAddInput(apply_qfunc, "weights", 1, CEED_EVAL_WEIGHT);
      // output
      switch (op.test_op)
      {
         case EvalMode::None:
            CeedQFunctionAddOutput(apply_qfunc, "v", test_vdim,
                                   CEED_EVAL_NONE);
            break;
         case EvalMode::Interp:
            CeedQFunctionAddOutput(apply_qfunc, "v", test_vdim,
                                   CEED_EVAL_INTERP);
            break;
         case EvalMode::Grad:
            CeedQFunctionAddOutput(apply_qfunc, "gv", test_vdim*dim,
                                   CEED_EVAL_GRAD);
            break;
         case EvalMode::InterpAndGrad:
            CeedQFunctionAddOutput(apply_qfunc, "v", test_vdim,
                                   CEED_EVAL_INTERP);
            CeedQFunctionAddOutput(apply_qfunc, "gv", test_vdim*dim,
                                   CEED_EVAL_GRAD);
            break;
      }

      CeedQFunctionContextCreate(ceed, &build_ctx);
      CeedQFunctionContextSetData(build_ctx, CEED_MEM_HOST,
                                  CEED_COPY_VALUES,
                                  sizeof(info.ctx),
                                  &info.ctx);
      CeedQFunctionSetContext(apply_qfunc, build_ctx);

      // Create the operator.
      CeedOperatorCreate(ceed, apply_qfunc, NULL, NULL, &oper);
      // coefficient
      if (GridCoefficient *gridCoeff = dynamic_cast<GridCoefficient*>(coeff))
      {
         InitBasisAndRestriction(*gridCoeff->gf.FESpace(), ir, nelem, indices,
                                 ceed, &gridCoeff->basis, &gridCoeff->restr);
         CeedOperatorSetField(oper, "coeff", gridCoeff->restr,
                              gridCoeff->basis, gridCoeff->coeffVector);
      }
      else if (QuadCoefficient *quadCoeff =
                  dynamic_cast<QuadCoefficient*>(coeff))
      {
         const int ncomp = quadCoeff->ncomp;
         CeedInt strides[3] = {ncomp, 1, ncomp*nqpts};
         InitStridedRestriction(*mesh.GetNodalFESpace(),
                                nelem, nqpts, ncomp, strides,
                                &quadCoeff->restr);
         CeedOperatorSetField(oper, "coeff", quadCoeff->restr,
                              CEED_BASIS_NONE, quadCoeff->coeffVector);
      }
      // input
      switch (op.trial_op)
      {
         case EvalMode::None:
            CeedOperatorSetField(oper, "u", trial_restr,
                                 CEED_BASIS_NONE, CEED_VECTOR_ACTIVE);
            break;
         case EvalMode::Interp:
            CeedOperatorSetField(oper, "u", trial_restr, trial_basis,
                                 CEED_VECTOR_ACTIVE);
            break;
         case EvalMode::Grad:
            CeedOperatorSetField(oper, "gu", trial_restr, trial_basis,
                                 CEED_VECTOR_ACTIVE);
            break;
         case EvalMode::InterpAndGrad:
            CeedOperatorSetField(oper, "u", trial_restr, trial_basis,
                                 CEED_VECTOR_ACTIVE);
            CeedOperatorSetField(oper, "gu", trial_restr, trial_basis,
                                 CEED_VECTOR_ACTIVE);
            break;
      }
      CeedOperatorSetField(oper, "dx", mesh_restr,
                           mesh_basis, node_coords);
      CeedOperatorSetField(oper, "weights", CEED_ELEMRESTRICTION_NONE,
                           mesh_basis, CEED_VECTOR_NONE);
      // output
      switch (op.test_op)
      {
         case EvalMode::None:
            CeedOperatorSetField(oper, "v", test_restr,
                                 CEED_BASIS_NONE, CEED_VECTOR_ACTIVE);
            break;
         case EvalMode::Interp:
            CeedOperatorSetField(oper, "v", test_restr, test_basis,
                                 CEED_VECTOR_ACTIVE);
            break;
         case EvalMode::Grad:
            CeedOperatorSetField(oper, "gv", test_restr, test_basis,
                                 CEED_VECTOR_ACTIVE);
            break;
         case EvalMode::InterpAndGrad:
            CeedOperatorSetField(oper, "v", test_restr, test_basis,
                                 CEED_VECTOR_ACTIVE);
            CeedOperatorSetField(oper, "gv", test_restr, test_basis,
                                 CEED_VECTOR_ACTIVE);
            break;
      }

      CeedVectorCreate(ceed, trial_vdim*trial_fes.GetNDofs(), &u);
      CeedVectorCreate(ceed, test_vdim*test_fes.GetNDofs(), &v);
   }

   virtual ~MFIntegrator()
   {
      CeedQFunctionDestroy(&apply_qfunc);
      CeedQFunctionContextDestroy(&build_ctx);
      CeedVectorDestroy(&node_coords);
      CeedVectorDestroy(&qdata);
      delete coeff;
   }

private:
   /** This structure contains the data to assemble a matrix-free operator with
       libCEED. */
   struct MFOperator
   {
      /** The path to the header containing the functions for libCEED. */
      std::string header;
      /** The name of the Qfunction to apply the operator. */
      std::string apply_func;
      /** The Qfunction to apply the operator. */
      CeedQFunctionUser apply_qf;
      /** The evaluation mode to apply to the trial function (CEED_EVAL_INTERP,
          CEED_EVAL_GRAD, etc.) */
      EvalMode trial_op;
      /** The evaluation mode to apply to the test function ( CEED_EVAL_INTERP,
          CEED_EVAL_GRAD, etc.) */
      EvalMode test_op;
   };
#endif
};

} // namespace ceed

} // namespace mfem

#endif // MFEM_LIBCEED_INTEG
