! $Id$
!
! MODULE_DOC: Runge-Kutta time advance, accurate to order itorder.
! MODULE_DOC: At the moment, itorder can be 1, 2, or 3.
!
!** AUTOMATIC CPARAM.INC GENERATION ****************************
! CPARAM logical, parameter :: lcourant_dt = .true.
!***************************************************************
!
module Timestep
!
  use Cdata
!
  implicit none
!
  private
!
  include 'timestep.h'
!
  contains
!***********************************************************************
    subroutine initialize_timestep
!
!  Coefficients for up to order 3.
!  26-oct-21/wlyra: added 2N-RK4 coefficients. The coefficients are for
!                   for 4th order, but use 5 stages. Since itorder is used
!                   in the code for the number of stages, it should be 5
!                   to use it.
!
      use Messages, only: not_implemented
      use General, only: itoa
!
      if (itorder==1) then
        alpha_ts=(/ 0.0, 0.0, 0.0, 0.0, 0.0 /)
        beta_ts =(/ 1.0, 0.0, 0.0, 0.0, 0.0 /)
      elseif (itorder==2) then
        alpha_ts=(/   0.0, -1/2.0, 0.0, 0.0, 0.0 /)
        beta_ts =(/ 1/2.0,    1.0, 0.0, 0.0, 0.0 /)
      elseif (itorder==3) then
        !alpha_ts=(/   0.0, -2/3.0, -1.0  , 0.0, 0.0 /)
        !beta_ts =(/ 1/3.0,    1.0,  1/2.0, 0.0, 0.0 /)
        !  use coefficients of Williamson (1980)
        alpha_ts=(/   0.0, -5/9.0 , -153/128.0, 0.0, 0.0 /)
        beta_ts =(/ 1/3.0, 15/16.0,    8/15.0 , 0.0, 0.0 /)
     elseif (itorder==5) then
        ! coefficients of Carpenter & Kennedy (1994)
        ! https://ntrs.nasa.gov/api/citations/19940028444/downloads/19940028444.pdf
!
        alpha_ts=(/0.0,-567301805773./1357537059087.,&
             -2404267990393./2016746695238.,&
             -3550918686646./2091501179385.,&
             -1275806237668./842570457699./)
        beta_ts=(/1432997174477./9575080441755.,&
             5161836677717./13612068292357.,&
             1720146321549./2090206949498.,&
             3134564353537./4481467310338.,&
             2277821191437./14882151754819./)
!
        !alpha_ts=(/0.0,-0.4812317431372,-1.049562606709,-1.602529574275,-1.778267193916/)
        !beta_ts =(/9.7618354692056e-2,0.4122532929155,0.4402169639311,1.426311463224,0.1978760537318/)
      else
        call not_implemented('initialize_timestep','itorder= '//trim(itoa(itorder)))
      endif
!
!  Invoke automatic timestep calulation when dt=0 (initially).
!
      ldt = (dt==0.)
!
!  If timestep is given, we reset dtmin to dt (which could be shorter than the default)
!
      if (dt/=0.) dtmin=dt
!
      num_substeps = itorder
!
    endsubroutine initialize_timestep
!***********************************************************************
    subroutine time_step(f,df,p)
!
!   2-apr-01/axel: coded
!  14-sep-01/axel: moved itorder to cdata
!
      use Boundcond, only: update_ghosts
      use BorderProfiles, only: border_quenching
      use Equ, only: pde, impose_floors_ceilings
      use Particles_main, only: particles_timestep_first, &
          particles_timestep_second
      use PointMasses, only: pointmasses_timestep_first, &
          pointmasses_timestep_second
      use Solid_Cells, only: solid_cells_timestep_first, &
          solid_cells_timestep_second
      use Shear, only: advance_shear
      use Sub, only: set_dt, shift_dt
      use GPU, only: update_after_substep_gpu, split_update_gpu
      use Mpicomm, only: mpiwtime
!
      real, dimension (mx,my,mz,mfarray) :: f
      real, dimension (mx,my,mz,mvar) :: df
      real, dimension (mx,my,mz,mvar) :: df_tmp
      real :: start_time
      type (pencil_case) :: p
      real :: ds, dtsub
!
!  dt_beta_ts may be needed in other modules (like Dustdensity) for fixed dt.
!
! ==============  the following should be omitted at some point =============
!  There is also an option to advance the time in progressively smaller
!  fractions of the current time. This is used to model the end of inflation,
!  when lfractional_tstep_negative should be used.
!  If dt=.5 and tstart=20, then one has -20, -10, -5, -2.5, etc.
!  From radiation era onward, lfractional_tstep_positive should be used
!  to make sure the dt used in run.in is positive.
!
      if (.not. ldt) then
  !     if (lfractional_tstep_advance) then
  !       if (lfractional_tstep_negative) then
  !         dt_beta_ts=-dt*t
  !       else
  !         dt_beta_ts=dt*t
  !       endif
  !     else
          dt_beta_ts=dt*beta_ts
  !     endif
      endif
! ==================  until here =========================================
!
!  Set up df and ds for each time sub.
!
      do itsub=1,itorder

        lfirst=(itsub==1)
        llast=(itsub==itorder)

        headtt = headt .and. lfirst .and. lroot

          if (lfirst) then
            if (.not. lgpu) df=0.0
            ds=0.0
          else
            if (.not. lgpu) df=alpha_ts(itsub)*df !(could be subsumed into pde, but is dangerous!)
            ds=alpha_ts(itsub)*ds
            if (it_rmv>0) lrmv=.false.
          endif
!
!  Set up particle derivative array (including df because of insert_nucleii in particles_dust.f90).
!
        if (lparticles .and. .not. lgpu) call particles_timestep_first(f,df)
!
!  Set up point masses derivative array
!
        if (lpointmasses .and. .not. lgpu) call pointmasses_timestep_first(f)
!
!  Set up ODE derivatives array
!
        if (lode) call ode_timestep_first
!
!  Set up solid_cells time advance
!
        if (lsolid_cells .and. .not. lgpu) call solid_cells_timestep_first(f)
!
!  Change df according to the chosen physics modules.
!
        call pde(f,df,p)

        if (lode) call ode

        ds=ds+1.0
!
!  Calculate dt_beta_ts.
!
        if (ldt) dt_beta_ts=dt*beta_ts
        if (ip<=6) print*, 'time_step: iproc, dt=', iproc_world, dt  !(all have same dt?)
        dtsub = ds * dt_beta_ts(itsub)
!
!  Apply border quenching.
!
        if (lborder_profiles .and. .not. lgpu) call border_quenching(f,df,dt_beta_ts(itsub))
!
!  Time evolution of grid variables.
!
        if (.not. lgpu) f(l1:l2,m1:m2,n1:n2,1:mvar) =  f(l1:l2,m1:m2,n1:n2,1:mvar) &
                                                     + dt_beta_ts(itsub)*df(l1:l2,m1:m2,n1:n2,1:mvar)
!
!  Time evolution of point masses.
!
        if (lpointmasses .and. .not. lgpu) call pointmasses_timestep_second(f)
!
!  Time evolution of particle variables.
!
        if (lparticles .and. .not. lgpu) call particles_timestep_second(f)
!
! Time evolution of ODE variables.
!
        if (lode) call ode_timestep_second
!
!  Time evolution of solid_cells.
!
        if (lsolid_cells .and. .not. lgpu) call solid_cells_timestep_second(f,dt_beta_ts(itsub),ds)
!
!  Advance deltay of the shear (and, optionally, perform shear advection
!  by shifting all variables and their derivatives).
!
        if (lshear) then
          if (.not. lgpu) call impose_floors_ceilings(f)
          if (.not. lgpu) call update_ghosts(f)  ! Necessary for non-FFT advection but unnecessarily overloading FFT advection
          call advance_shear(f, df, dtsub)
        endif
!
        start_time = mpiwtime()
        if (lgpu) then
          call update_after_substep_gpu
        else
          call update_after_substep(f,df,dtsub,llast)
        endif
        after_substep_sum_time = after_substep_sum_time + mpiwtime()-start_time
!
        ! [PAB] according to MR this breaks the autotest.
        ! @Piyali: there must be a reason to add an additional global communication,
        ! could this be solved differently, and, if not, why? Can you enclose this in an if-clause, like above?
        !call update_ghosts(f)  ! Necessary for boundary driving in special module
!
!  Increase time.
!
        t = t + dtsub
!
      enddo   ! substep loop
!
!  Integrate operator split terms.
!
    if (.not. lgpu)  call split_update(f)
    if (lgpu)  call split_update_gpu(f)
!
    endsubroutine time_step
!***********************************************************************
    subroutine split_update(f)
!
!  Integrate operator split terms.
!
!  14-dec-14/ccyang: coded
!
      use Density, only: split_update_density
      use Energy, only: split_update_energy
      use Magnetic, only: split_update_magnetic
      use Viscosity, only: split_update_viscosity
      use Particles_main, only: split_update_particles
!
      real, dimension(mx,my,mz,mfarray), intent(inout) :: f
!
!  Dispatch to respective modules.
!
      if (ldensity) call split_update_density(f)
      if (lenergy) call split_update_energy(f)
      if (lmagnetic) call split_update_magnetic(f)
      if (lviscosity) call split_update_viscosity(f)
!
      if (lparticles) call split_update_particles(f, dt)
!
    endsubroutine split_update
!***********************************************************************
    subroutine update_after_substep(f,df,dtsub,llast)
!
!   Hooks for modifying f and df after the timestep is performed.
!
!  12-03-17/wlyra: coded
!  28-03-17/MR: removed update_ghosts; checks for already communicated variables enabled.
!
      use Hydro,    only: hydro_after_timestep
      use Energy,   only: energy_after_timestep
      use Magnetic, only: magnetic_after_timestep
      use Special,  only: special_after_timestep
      use Particles_main, only: particles_special_after_dtsub, particles_write_rmv
!
      logical, intent(in) :: llast
      real, dimension (mx,my,mz,mfarray) :: f
      real, dimension (mx,my,mz,mvar) :: df
      real :: dtsub
!
!  Enables checks to avoid unnecessary communication
!
      ighosts_updated=0
!
!  Dispatch to respective modules. The module which communicates
!  the biggest number of variables should come first here.
!
      if (lhydro)    call hydro_after_timestep   (f,df,dtsub)
      if (lmagnetic) call magnetic_after_timestep(f,df,dtsub)
      if (lenergy)   call energy_after_timestep  (f,df,dtsub)
      if (lspecial) then
        call special_after_timestep(f, df, dtsub, llast)
        if (lparticles) call particles_special_after_dtsub(f, dtsub)
      endif
!
!  Flush the list of removed particles to the log file.
!
      if (lparticles) call particles_write_rmv
!
!  Disables checks.
!
      ighosts_updated=-1
!
    endsubroutine update_after_substep
!***********************************************************************
    subroutine ode_timestep_first

      if (itsub==1) then
        df_ode = 0.0
      else
        df_ode=alpha_ts(itsub)*df_ode
      endif

    endsubroutine ode_timestep_first
!***********************************************************************
    subroutine ode

      use Special, only: dspecial_dt_ode

      call dspecial_dt_ode

    endsubroutine ode
!***********************************************************************
    subroutine ode_timestep_second

      f_ode(1:n_odevars) = f_ode(1:n_odevars) + dt_beta_ts(itsub)*df_ode

    endsubroutine ode_timestep_second
!***********************************************************************
    subroutine pushpars2c(p_par)

    use Syscalls, only: copy_addr

    integer, parameter :: n_pars=0
    integer(KIND=ikind8), dimension(n_pars) :: p_par

    !!call copy_addr(alpha_ts,p_par(1))  ! (3)
    !!call copy_addr(beta_ts ,p_par(2))  ! (3)

    endsubroutine pushpars2c
!***********************************************************************
endmodule Timestep
