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

/**
 * @file svk.hh
 * @brief Implementation of the Saint Venant-Kirchhoff material model.
 * @ingroup  materials
 */

// SPDX-License-Identifier: LGPL-3.0-or-later

#pragma once

#include <ikarus/finiteelements/mechanics/materials/interface.hh>
#include <ikarus/utils/tensorutils.hh>

namespace Ikarus {

/**
 * @brief Implementation of the Saint Venant-Kirchhoff material model.
 * @ingroup materials
 *   The energy is computed as
 *  \f[ \psi(\BE) = \frac{\lambda}{2} (\tr \BE)^2   +\mu \tr (\BE^2) ,\f]
 *  where \f$ \BE \f$ denotes the Green-Lagrangian strain.
 *
 *  The second Piola-Kirchhoff stresses are computed as
 *     \f[ \BS(\BE) =\fracpt{\psi(\BE)}{\BE} = \lambda \tr \BE \BI  +2 \mu \BE,\f]
 *
 * and the material tangent moduli are computed as
 *      \f[ \BBC(\BE) =\fracpt{^2\psi(\BE)}{\BE^2} =  \lambda \tr \BE \CI  +2 \mu \CI^{\mathrm{sym}},\f]
 *      where \f$ \CI_{IJKL} =  \de_{IJ}\de_{KL}\f$ and \f$ \CI_{IJKL}^\mathrm{sym} =  \frac{1}{2}(\de_{IK}\de_{JL}+
 * \de_{IL}\de_{JK})\f$.
 * @tparam ScalarType_ The scalar type used in the material.
 */
template <typename ScalarType_>
struct StVenantKirchhoffT : public Material<StVenantKirchhoffT<ScalarType_>>
{
  [[nodiscard]] constexpr std::string nameImpl() const { return "StVenantKirchhoff"; }

  /**
   * @brief Constructor for StVenantKirchhoffT.
   * @param mpt The material parameters (Lamé's first parameter and shear modulus).
   */
  explicit StVenantKirchhoffT(const LamesFirstParameterAndShearModulus& mpt)
      : materialParameter{mpt} {}

  using ScalarType                    = ScalarType_;
  static constexpr int worldDimension = 3;
  using StrainMatrix                  = Eigen::Matrix<ScalarType, worldDimension, worldDimension>;
  using StressMatrix                  = StrainMatrix;

  static constexpr auto strainTag          = StrainTags::greenLagrangian;
  static constexpr auto stressTag          = StressTags::PK2;
  static constexpr auto tangentModuliTag   = TangentModuliTags::Material;
  static constexpr bool energyAcceptsVoigt = true;
  static constexpr bool stressToVoigt      = true;
  static constexpr bool stressAcceptsVoigt = true;
  static constexpr bool moduliToVoigt      = true;
  static constexpr bool moduliAcceptsVoigt = true;
  // this factor denotes the differences between the returned stresses and moduli and the passed strain
  // for neoHooke the inserted quantity is C the Green-Lagrangian strain tensor,
  // the function relation between the energy and the stresses is S = 1\partial \psi(E)/ \partial E.
  // This factor is pre factor, which is the difference to the actual derivative is written here
  static constexpr double derivativeFactor = 1;

  /**
   * @brief Computes the stored energy in the Saint Venant-Kirchhoff material model.
   * @tparam Derived The derived type of the input matrix.
   * @param E The Green-Lagrangian strain.
   * @return ScalarType The stored energy.
   */
  template <typename Derived>
  ScalarType storedEnergyImpl(const Eigen::MatrixBase<Derived>& E) const {
    static_assert(Concepts::EigenMatrixOrVoigtNotation3<Derived>);
    if constexpr (Concepts::EigenVector<Derived>) {
      const ScalarType traceE = E.template segment<3>(0).sum();
      const ScalarType squaredNorm =
          E.template segment<3>(0).squaredNorm() + E.template segment<3>(3).squaredNorm() / ScalarType(2.0);
      return materialParameter.lambda / ScalarType(2.0) * traceE * traceE + materialParameter.mu * squaredNorm;
    } else {
      const auto traceE = E.trace();
      return materialParameter.lambda / ScalarType(2.0) * traceE * traceE + materialParameter.mu * E.squaredNorm();
    }
  }

  /**
   * @brief Computes the stresses in the Saint Venant-Kirchhoff material model.
   * @tparam voigt A boolean indicating whether to return stresses in Voigt notation.
   * @tparam Derived The derived type of the input matrix.
   * @param E The Green-Lagrangian strain.
   * @return StressMatrix The stresses.
   */
  template <bool voigt, typename Derived>
  auto stressesImpl(const Eigen::MatrixBase<Derived>& E) const {
    static_assert(Concepts::EigenMatrixOrVoigtNotation3<decltype(E.eval())>);
    const auto& Ed = E.derived();
    if constexpr (!voigt) {
      if constexpr (Concepts::EigenVector<Derived>) {
        static_assert(Concepts::EigenVector6<Derived>);
        Eigen::Matrix<ScalarType, 3, 3> S;
        const ScalarType traceE = Ed.template segment<3>(0).sum();
        S.diagonal().array() =
            materialParameter.lambda * traceE + 2 * materialParameter.mu * Ed.template segment<3>(0).array();
        S(1, 2) = S(2, 1) = materialParameter.mu * Ed(3);
        S(0, 2) = S(2, 0) = materialParameter.mu * Ed(4);
        S(0, 1) = S(1, 0) = materialParameter.mu * Ed(5); // no two since E voigt has 2* on off-diagonal terms
        return S;
      } else {
        static_assert(Concepts::EigenMatrix33<Derived>);
        return (materialParameter.lambda * Ed.trace() * StrainMatrix::Identity() + 2 * materialParameter.mu * Ed)
            .eval();
      }
    } else {
      if constexpr (Concepts::EigenVector<Derived>) {
        static_assert(Concepts::EigenVector6<Derived>);
        Eigen::Matrix<ScalarType, 6, 1> S;
        const ScalarType traceE          = Ed.template segment<3>(0).sum();
        S.template segment<3>(0).array() = traceE * materialParameter.lambda;
        S.template segment<3>(0) += materialParameter.mu * 2 * Ed.template segment<3>(0);
        S.template segment<3>(3) =
            materialParameter.mu * Ed.template segment<3>(3); // no two since E voigt has 2* on off-diagonal terms
        return S;
      } else {
        Eigen::Matrix<ScalarType, 6, 1> S;
        S.template segment<3>(0).array() = Ed.trace() * materialParameter.lambda;
        S.template segment<3>(0) += 2 * materialParameter.mu * Ed.diagonal();
        S(3) = 2 * materialParameter.mu * Ed(1, 2);
        S(4) = 2 * materialParameter.mu * Ed(0, 2);
        S(5) = 2 * materialParameter.mu * Ed(0, 1);
        return S;
      }
    }
  }

  /**
   * @brief Computes the tangent moduli in the Saint Venant-Kirchhoff material model.
   * @tparam voigt A boolean indicating whether to return tangent moduli in Voigt notation.
   * @tparam Derived The derived type of the input matrix.
   * @param E The Green-Lagrangian strain.
   * @return Eigen::TensorFixedSize<ScalarType, Eigen::Sizes<3, 3, 3, 3>> The tangent moduli.
   */
  template <bool voigt, typename Derived>
  auto tangentModuliImpl([[maybe_unused]] const Eigen::MatrixBase<Derived>& E) const {
    static_assert(Concepts::EigenMatrixOrVoigtNotation3<Derived>);
    if constexpr (!voigt) {
      Eigen::TensorFixedSize<ScalarType, Eigen::Sizes<3, 3, 3, 3>> moduli;
      moduli =
          materialParameter.lambda * identityFourthOrder() + 2 * materialParameter.mu * symmetricIdentityFourthOrder();
      return moduli;
    } else {
      Eigen::Matrix<ScalarType, 6, 6> moduli;
      moduli.setZero();
      moduli.template block<3, 3>(0, 0).array() = materialParameter.lambda;
      moduli.template block<3, 3>(0, 0).diagonal().array() += 2 * materialParameter.mu;
      moduli.template block<3, 3>(3, 3).diagonal().array() = materialParameter.mu;
      return moduli;
    }
  }

  /**
   * @brief Rebinds the material to a different scalar type.
   * @tparam ScalarTypeOther The target scalar type.
   * @return StVenantKirchhoffT<ScalarTypeOther> The rebound StVenantKirchhoff material.
   */
  template <typename ScalarTypeOther>
  auto rebind() const {
    return StVenantKirchhoffT<ScalarTypeOther>(materialParameter);
  }

  LamesFirstParameterAndShearModulus materialParameter;
};

/**
 * @brief Alias for StVenantKirchhoffT with double as the default scalar type.
 */
typedef StVenantKirchhoffT<double> StVenantKirchhoff;

} // namespace Ikarus
