// SPDX-FileComment: This file is part of TNL - Template Numerical Library (https://tnl-project.org/)
// SPDX-License-Identifier: MIT

#pragma once

#include <iomanip>
#include <string>

#include <TNL/Solvers/StaticIterativeSolver.h>
#include <TNL/Config/ConfigDescription.h>
#include <TNL/Config/ParameterContainer.h>
#include <TNL/Containers/StaticVector.h>

namespace TNL::Solvers::ODE {

/**
 * \brief Base class for ODE solvers and explicit solvers od PDEs.
 *
 * This is a specialization for static solvers, i.e. solvers which of scalar problem
 * or small system of ODEs solution of which can be expressed by \ref TNL::Containers::StaticVector.
 * The static solvers can be created even in GPU kernels and can be combined with \ref TNL::Algorithms::parallelFor.
 *
 * \tparam Real is type of the floating-point arithmetics or static vector ( \ref TNL::Containers::StaticVector ).
 * \tparam Index is type for indexing.
 */
template< typename Real = double, typename Index = int >
class StaticExplicitSolver : public StaticIterativeSolver< Real, Index >
{
public:
   /**
    * \brief Type of the floating-point arithmetics or static vector.
    */
   using RealType = Real;

   /**
    * \brief Indexing type.
    */
   using IndexType = Index;

   /**
    * \brief Default constructor.
    */
   __cuda_callable__
   StaticExplicitSolver() = default;

   /**
    * \brief This method defines configuration entries for setup of the iterative solver.
    */
   static void
   configSetup( Config::ConfigDescription& config, const std::string& prefix = "" );

   /**
    * \brief Method for setup of the iterative solver based on configuration parameters.
    */
   bool
   setup( const Config::ParameterContainer& parameters, const std::string& prefix = "" );

   /**
    * \brief Setter of the current time of the evolution computed by the solver.
    */
   __cuda_callable__
   void
   setTime( const RealType& t );

   /**
    * \brief Getter of the current time of the evolution computed by the solver.
    */
   [[nodiscard]] __cuda_callable__
   const RealType&
   getTime() const;

   /**
    * \brief Setter of the time where the evolution computation shall by stopped.
    */
   __cuda_callable__
   void
   setStopTime( const RealType& stopTime );

   /**
    * \brief Getter of the time where the evolution computation shall by stopped.
    */
   [[nodiscard]] __cuda_callable__
   const RealType&
   getStopTime() const;

   /**
    * \brief Setter of the time step used for the computation.
    *
    * The time step can be changed by methods using adaptive choice of the time step.
    */
   __cuda_callable__
   void
   setTau( const RealType& tau );

   /**
    * \brief Getter of the time step used for the computation.
    */
   [[nodiscard]] __cuda_callable__
   const RealType&
   getTau() const;

   /**
    * \brief Setter of maximal value of the time step.
    *
    * If methods uses adaptive choice of the time step, this sets the upper limit.
    */
   __cuda_callable__
   void
   setMaxTau( const RealType& maxTau );

   /**
    * \brief Getter of maximal value of the time step.
    */
   [[nodiscard]] __cuda_callable__
   const RealType&
   getMaxTau() const;

   /**
    * \brief Set \e stopOnSteadyState flag.
    *
    * If the flag is on, the solver stops when steady-state solution is reached.
    * Otherwise, the solver stops only when the stop time is reached or criteria
    * defined in \ref TNL::Solvers::IterativeSolver::checkConvergence() are met.
    *
    * \param stopOnSteadyState is the new value of the flag.
    */
   __cuda_callable__
   void
   setStopOnSteadyState( bool stopOnSteadyState );

   //! \brief Getter of \e stopOnSteadyState flag.
   [[nodiscard]] __cuda_callable__
   bool
   getStopOnSteadyState() const;

   /**
    * \brief Proceeds to the next iteration.
    *
    * \return \e true if the solver is allowed to do the next iteration, and \e false otherwise.
    * This may happen because the divergence occurred.
    */
   bool __cuda_callable__
   nextIteration();

   /**
    * \brief Checks if the solver is allowed to do the next iteration.
    *
    * \return \e true if the solver is allowed to do the next iteration, and \e false otherwise.
    * This may happen because the divergence occurred.
    */
   [[nodiscard]] bool __cuda_callable__
   checkNextIteration() const;

   /**
    * \brief Checks whether the convergence occurred already.
    *
    * \return \e true if the convergence already occurred, and \e false otherwise.
    */
   [[nodiscard]] bool __cuda_callable__
   checkConvergence() const;

   __cuda_callable__
   void
   setTestingMode( bool testingMode );

protected:
   /****
    * Current time of the parabolic problem.
    */
   RealType time = 0.0;

   /****
    * The method solve will stop when reaching the stopTime.
    */
   RealType stopTime = 0.0;

   /****
    * Current time step.
    */
   RealType tau = 0.0;

   RealType maxTau = std::numeric_limits< RealType >::max();

   bool stopOnSteadyState = false;

   bool testingMode = false;
};

}  // namespace TNL::Solvers::ODE

#include <TNL/Solvers/ODE/StaticExplicitSolver.hpp>
