// SPDX-FileCopyrightText: 2021-2024 The Ikarus Developers mueller@ibb.uni-stuttgart.de
// SPDX-License-Identifier: LGPL-3.0-or-later

/**
 * \file basis.hh
 * \brief Wrapper around Dune-functions global basis

 */

#pragma once

#include <utility>

#include <dune/functions/functionspacebases/defaultglobalbasis.hh>

#include <ikarus/utils/flatprebasis.hh>

namespace Ikarus {

/**
 * @brief Wrapper class for a hierarchical basis constructed from a pre-basis.
 *
 * This class provides a convenient wrapper for a hierarchical basis constructed from a pre-basis.
 * It contains both the original untouched basis and a flattened version of the basis.
 * \ingroup  utils
 * @tparam PreBasis_ The type of the pre-basis used to construct the hierarchical basis.
 */
template <typename PreBasis_>
class Basis
{
public:
  using PreBasis = PreBasis_;                   ///< The type of the untouched pre basis
  using GridView = typename PreBasis::GridView; ///< The type of the grid view
  using UntouchedBasis =
      decltype(Dune::Functions::DefaultGlobalBasis(std::declval<PreBasis>())); ///< The type of the untouched basis
  using FlatBasis = decltype(Dune::Functions::DefaultGlobalBasis(
      Ikarus::flatPreBasis(std::declval<PreBasis>()))); ///< The type of the flattened  basis

  /**
   * @brief Constructs a Basis object from a pre-basis.
   *
   * Constructs a Basis object from a given pre-basis, creating both the untouched and flat versions of the basis.
   *
   * @param pb The pre-basis used to construct the hierarchical basis.
   */
  explicit Basis(const PreBasis& pb)
      : bb{Dune::Functions::DefaultGlobalBasis(pb)},
        fb{Dune::Functions::DefaultGlobalBasis(Ikarus::flatPreBasis(pb))} {}

  /**
   * @brief Returns a reference to the flat version of the basis.
   *
   * @return A reference to the flat version of the basis.
   */
  auto& flat() { return fb; }

  /**
   * @brief Returns a reference to the untouched version of the basis.
   *
   * @return A reference to the untouched version of the basis.
   */
  auto& untouched() { return bb; }

  /**
   * @brief Returns a const reference to the flat version of the basis.
   *
   * @return A const reference to the flat version of the basis.
   */
  const auto& flat() const { return fb; }

  /**
   * @brief Returns a const reference to the untouched version of the basis.
   *
   * @return A const reference to the untouched version of the basis.
   */
  const auto& untouched() const { return bb; }

  /**
   * @brief Returns a const reference to the grid view associated with the untouched basis.
   *
   * @return A const reference to the grid view associated with the untouched basis.
   */
  const auto& gridView() const { return bb.gridView(); }

  /**
   * @brief Returns a reference to the grid view associated with the untouched basis.
   *
   * @return A reference to the grid view associated with the untouched basis.
   */
  auto& gridView() { return bb.gridView(); }

private:
  UntouchedBasis bb;
  FlatBasis fb;
};

/**
 * @brief Factory function to create a Basis object.
 *
 * Factory function that creates a Basis object from a given grid view and pre-basis factory.
 *
 * @tparam GridView The type of the grid view.
 * @tparam PreBasisFactory The type of the pre-basis factory function.
 * @param gv The grid view.
 * @param pb The pre-basis factory function.
 * @return A Basis object.
 */
template <typename GridView, typename PreBasisFactory>
auto makeBasis(const GridView& gv, const PreBasisFactory& pb) {
  auto preBasis = pb(gv);
  return Basis(preBasis);
}

/**
 * @brief Factory function to create a Basis object from a DefaultGlobalBasis.
 *
 *
 * @tparam PreBasis The type of the pre-basis associated with the DefaultGlobalBasis.
 * @param gb The DefaultGlobalBasis.
 * @return A Basis object.
 */
template <typename PreBasis>
auto makeBasis(const Dune::Functions::DefaultGlobalBasis<PreBasis>& gb) {
  return Basis(gb.preBasis());
}
} // namespace Ikarus
