// ---------------------------------------------------------------------
//
// Copyright (C) 2009 - 2021 by the deal.II authors
//
// This file is part of the deal.II library.
//
// The deal.II library is free software; you can use it, redistribute
// it, and/or modify it under the terms of the GNU Lesser General
// Public License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// The full text of the license can be found in the file LICENSE.md at
// the top level directory of deal.II.
//
// ---------------------------------------------------------------------

#ifndef dealii_block_info_h
#define dealii_block_info_h

#include <deal.II/base/config.h>

#include <deal.II/base/memory_consumption.h>
#include <deal.II/base/subscriptor.h>

#include <deal.II/lac/block_indices.h>

#include <iomanip>

DEAL_II_NAMESPACE_OPEN

// Forward declarations
#ifndef DOXYGEN
template <int dim, int spacedim>
class DoFHandler;
#endif


/**
 * @brief A small class collecting the different BlockIndices involved in
 * global, multilevel and local computations.
 *
 * Once a DoFHandler has been initialized with an FESystem, a data object of
 * type BlockInfo (accessed by DoFHandler::block_info() ) is filled, which
 * reflects the block structure of the degrees of freedom.
 *
 * BlockInfo consists of several BlockIndices objects. The member global()
 * reflects the block structure of the system on the active cell level,
 * usually referred to as the global system. As soon as
 * DoFHandler::distribute_dofs() has been called, the function
 * BlockIndices::block_size() in global() will return the correct sizes of
 * each block. After DoFRenumbering::block_wise(), BlockIndices::block_start()
 * will return the start index for each of the blocks.
 *
 * When a DoFHandler with levels is used, the same structure is automatically
 * generated for each level. The level blocks can be accessed through level().
 *
 * Finally, there are local() BlockIndices, which describe the block structure
 * on a single cell. This is used for instance by
 * MeshWorker::Assembler::MatrixLocalBlocksToGlobalBlocks. The local indices
 * are not filled automatically, since they change the behavior of the
 * MeshWorker::Assembler classes relying on BlockInfo. They must be
 * initialized by hand through initialize_local().
 *
 * <h3>Usage</h3>
 *
 * The most common usage for this object is initializing vectors as in the
 * following code:
 *
 * @code
 * DoFHandler<dim> dof_handler(triangulation);
 * dof_handler.distribute_dofs(fe_system);
 * dof_handler.distribute_mg_dofs(fe_system);
 * DoFRenumbering::block_wise(dof_handler);
 *
 * BlockVector<double> solution(dof_handler.block_info().global());
 *
 * MGLevelObject<BlockVector<double> > mg_vector(0, triangulation.n_levels()-1);
 * for (unsigned int i = 0; i < triangulation.n_levels(); ++i)
 *   {
 *     mg_vector[i].reinit(dof_handler.block_info().level(i));
 *   }
 * @endcode
 *
 * In this example, <tt>solution</tt> obtains the block structure needed to
 * represent a finite element function on the DoFHandler. Similarly, all
 * levels of <tt>mg_vector</tt> will have the block structure needed on that
 * level.
 *
 * @todo Extend the functions local() and renumber() to allow for
 * hp-capablilites.
 *
 * @ingroup dofs
 */
class BlockInfo : public Subscriptor
{
public:
  /**
   * @brief Fill the object with values describing block structure of the
   * DoFHandler.
   *
   * By default, this function will attempt to initialize whatever is
   * possible. If active dofs have been assigned int the DoFHandler argument,
   * they BlockIndices for those will be generated. The same for level dofs.
   *
   * This default behavior can be overridden by the two parameters, which can
   * switch off active dofs or level dofs.
   *
   * This function will also clear the local() indices.
   */
  template <int dim, int spacedim>
  void
  initialize(const DoFHandler<dim, spacedim> &,
             bool levels_only = false,
             bool active_only = false);

  /**
   * @brief Initialize block structure on cells and compute renumbering
   * between cell dofs and block cell dofs.
   */
  template <int dim, int spacedim>
  void
  initialize_local(const DoFHandler<dim, spacedim> &);

  /**
   * Access the BlockIndices structure of the global system.
   */
  const BlockIndices &
  global() const;

  /**
   * Access BlockIndices for the local system on a cell.
   */
  const BlockIndices &
  local() const;

  /**
   * Access the BlockIndices structure of a level in the multilevel hierarchy.
   */
  const BlockIndices &
  level(unsigned int level) const;

  /**
   * Return the index after local renumbering.
   *
   * The input of this function is an index between zero and the number of
   * dofs per cell, numbered in local block ordering, that is first all
   * indices of the first system block, then all of the second block and so
   * forth. The function then outputs the index in the standard local
   * numbering of DoFAccessor.
   */
  types::global_dof_index
  renumber(const unsigned int i) const;

  /**
   * The number of base elements.
   */
  unsigned int
  n_base_elements() const;

  /**
   * Return the base element of this index.
   */
  unsigned int
  base_element(const unsigned int i) const;

  /**
   * Write a summary of the block structure to the stream.
   */
  template <class OS>
  void
  print(OS &stream) const;

  /**
   * Determine an estimate for the memory consumption (in bytes) of this
   * object.
   */
  std::size_t
  memory_consumption() const;

  /**
   * Read or write the data of this object to or from a stream for the purpose
   * of serialization using the [BOOST serialization
   * library](https://www.boost.org/doc/libs/1_74_0/libs/serialization/doc/index.html).
   */
  template <class Archive>
  void
  serialize(Archive &ar, const unsigned int /*version*/);

private:
  /**
   * @brief The block structure of the global system.
   */
  BlockIndices bi_global;
  /**
   * @brief The multilevel block structure.
   */
  std::vector<BlockIndices> levels;

  /**
   * @brief The block structure of the cell systems.
   */
  BlockIndices bi_local;

  /**
   * The base element associated with each block.
   */
  std::vector<unsigned int> base_elements;

  /**
   * A vector containing the renumbering from the standard order of degrees of
   * freedom on a cell to a component wise ordering. Filled by initialize().
   */
  std::vector<types::global_dof_index> local_renumbering;
};



//----------------------------------------------------------------------//

inline const BlockIndices &
BlockInfo::global() const
{
  return bi_global;
}


inline const BlockIndices &
BlockInfo::local() const
{
  return bi_local;
}


inline const BlockIndices &
BlockInfo::level(const unsigned int l) const
{
  AssertIndexRange(l, levels.size());
  return levels[l];
}


inline types::global_dof_index
BlockInfo::renumber(const unsigned int i) const
{
  AssertIndexRange(i, static_cast<unsigned int>(local_renumbering.size()));
  return local_renumbering[i];
}


inline unsigned int
BlockInfo::base_element(const unsigned int i) const
{
  AssertIndexRange(i, base_elements.size());

  return base_elements[i];
}


inline unsigned int
BlockInfo::n_base_elements() const
{
  return base_elements.size();
}



template <class OS>
inline void
BlockInfo::print(OS &os) const
{
  os << "global   dofs " << std::setw(5) << global().total_size() << " blocks";
  for (unsigned int i = 0; i < global().size(); ++i)
    os << ' ' << std::setw(5) << global().block_size(i);
  os << std::endl;

  if (local().size() == 0)
    {
      os << "local dofs not initialized" << std::endl;
    }
  else
    {
      os << "local    dofs " << std::setw(5) << local().total_size()
         << " blocks";
      for (unsigned int i = 0; i < local().size(); ++i)
        os << ' ' << std::setw(5) << local().block_size(i);
      os << std::endl;
    }

  for (unsigned int l = 0; l < levels.size(); ++l)
    {
      os << "level " << std::setw(2) << l << " dofs " << std::setw(5)
         << level(l).total_size() << " blocks";
      for (unsigned int i = 0; i < level(l).size(); ++i)
        os << ' ' << std::setw(5) << level(l).block_size(i);
      os << std::endl;
    }
}


inline std::size_t
BlockInfo::memory_consumption() const
{
  return (MemoryConsumption::memory_consumption(bi_global) +
          MemoryConsumption::memory_consumption(levels) +
          MemoryConsumption::memory_consumption(bi_local) +
          MemoryConsumption::memory_consumption(base_elements));
}


template <class Archive>
void
BlockInfo::serialize(Archive &ar, const unsigned int /*version*/)
{
  ar &bi_global;
  ar &levels;
  ar &bi_local;
  ar &base_elements;
  ar &local_renumbering;
}


DEAL_II_NAMESPACE_CLOSE

#endif
