// Copyright (c) 2010-2022, 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_PLINEARFORM
#define MFEM_PLINEARFORM

#include "../config/config.hpp"

#ifdef MFEM_USE_MPI

#include "pgridfunc.hpp"
#include "linearform.hpp"

namespace mfem
{

/// Class for parallel linear form
class ParLinearForm : public LinearForm
{
protected:
   ParFiniteElementSpace *pfes; ///< Points to the same object as #fes

private:
   /// Copy construction is not supported; body is undefined.
   ParLinearForm(const ParLinearForm &);

public:
   /** @brief Create an empty ParLinearForm without an associated
       ParFiniteElementSpace.

       The associated ParFiniteElementSpace can be set later using one of the
       methods: Update(ParFiniteElementSpace *) or
       Update(ParFiniteElementSpace *, Vector &, int). */
   ParLinearForm() : LinearForm() { pfes = NULL; }

   /// Create a ParLinearForm on the FE space @a *pf.
   /** The pointer @a pf is not owned by the newly constructed object. */
   ParLinearForm(ParFiniteElementSpace *pf) : LinearForm(pf) { pfes = pf; }

   /// Construct a ParLinearForm using previously allocated array @a data.
   /** The ParLinearForm does not assume ownership of @a data which is assumed
       to be of size at least `pf->GetVSize()`. Similar to the LinearForm and
       Vector constructors for externally allocated array, the pointer @a data
       can be NULL. The data array can be replaced later using the method
       SetData(). */
   ParLinearForm(ParFiniteElementSpace *pf, double *data) :
      LinearForm(pf, data), pfes(pf) { }

   /** @brief Create a ParLinearForm on the ParFiniteElementSpace @a *pf, using
       the same integrators as the ParLinearForm @a *plf.

       The pointer @a pf is not owned by the newly constructed object.

       The integrators in @a plf are copied as pointers and they are not owned
       by the newly constructed LinearForm. */
   ParLinearForm(ParFiniteElementSpace *pf, ParLinearForm * plf)
      : LinearForm(pf, plf) { pfes = pf; }

   /// Copy assignment. Only the data of the base class Vector is copied.
   /** It is assumed that this object and @a rhs use ParFiniteElementSpace%s
       that have the same size.

       @note Defining this method overwrites the implicitly defined copy
       assignment operator. */
   ParLinearForm &operator=(const ParLinearForm &rhs)
   { return operator=((const Vector &)rhs); }

   ParFiniteElementSpace *ParFESpace() const { return pfes; }

   /// Update the object according to the given new FE space @a *pf.
   /** If the pointer @a pf is NULL (this is the default value), the FE space
       already associated with this object is used.

       This method should be called when the associated FE space #fes has been
       updated, e.g. after its associated Mesh object has been refined.

       @note This method does not perform assembly. */
   void Update(ParFiniteElementSpace *pf = NULL);

   /** @brief Associate a new FE space, @a *pf, with this object and use the
       data of @a v, offset by @a v_offset, to initialize this object's
       Vector::data.

       @note This method does not perform assembly. */
   void Update(ParFiniteElementSpace *pf, Vector &v, int v_offset);


   /** @brief Make the ParLinearForm reference external data on a new
       FiniteElementSpace. */
   /** This method changes the FiniteElementSpace associated with the
       ParLinearForm to @a *f and sets the data of the Vector @a v (plus the @a
       v_offset) as external data in the ParLinearForm.

       @note This version of the method will also perform bounds checks when the
       build option MFEM_DEBUG is enabled. */
   virtual void MakeRef(FiniteElementSpace *f, Vector &v, int v_offset);

   /** @brief Make the ParLinearForm reference external data on a new
       ParFiniteElementSpace. */
   /** This method changes the ParFiniteElementSpace associated with the
       ParLinearForm to @a *pf and sets the data of the Vector @a v (plus the @a
       v_offset) as external data in the ParLinearForm.

       @note This version of the method will also perform bounds checks when the
       build option MFEM_DEBUG is enabled. */
   void MakeRef(ParFiniteElementSpace *pf, Vector &v, int v_offset);

   /// Assembles the ParLinearForm i.e. sums over all domain/bdr integrators.
   /** When @ref LinearForm::UseFastAssembly "UseFastAssembly(true)" has been
       called and the linear form assembly is compatible with device execution,
       the assembly will be executed on the device. */
   void Assemble();

   /// Return true if assembly on device is supported, false otherwise.
   virtual bool SupportsDevice();

   void AssembleSharedFaces();

   /// Assemble the vector on the true dofs, i.e. P^t v.
   void ParallelAssemble(Vector &tv);

   /// Returns the vector assembled on the true dofs, i.e. P^t v.
   HypreParVector *ParallelAssemble();

   /// Return the action of the ParLinearForm as a linear mapping.
   /** Linear forms are linear functionals which map ParGridFunction%s to the
       real numbers. This method performs this mapping which in this case is
       equivalent as an inner product of the ParLinearForm and
       ParGridFunction. */
   double operator()(const ParGridFunction &gf) const
   {
      return InnerProduct(pfes->GetComm(), *this, gf);
   }

   /// Assign constant values to the ParLinearForm data.
   ParLinearForm &operator=(double value)
   { LinearForm::operator=(value); return *this; }

   /// Copy the data from a Vector to the ParLinearForm data.
   ParLinearForm &operator=(const Vector &v)
   { LinearForm::operator=(v); return *this; }
};

}

#endif // MFEM_USE_MPI

#endif
