! See copyright notice in the COPYRIGHT file.
! ****************************************************************************** !
!> This module provides a facility to maintain the control over
!! an explicit time stepping solver.
!!
!! It provides the definition of the current time step, and
!! control over the time marching as well as the abort criteria.
module tem_simControl_module
  use env_module, only: rk, labelLen

  use tem_comm_env_module, only: tem_comm_env_type
  use tem_time_module, only: tem_time_type,     &
    &                        tem_time_reset,    &
    &                        tem_time_advance,  &
    &                        tem_time_dump,     &
    &                        tem_time_set_clock,&
    &                        tem_time_out,      &
    &                        tem_time_sim_id,   &
    &                        tem_time_iter_id,  &
    &                        tem_time_clock_id, &
    &                        tem_time_n_ids

  use tem_timeControl_module, only: tem_timeControl_type,         &
    &                               tem_timeControl_load,         &
    &                               tem_timeControl_out,          &
    &                               tem_timeControl_dump,         &
    &                               tem_timeControl_start_at_sim, &
    &                               tem_timeControl_reached_max,  &
    &                               tem_timeControl_triggered,    &
    &                               tem_timeControl_update,       &
    &                               tem_timeControl_reset_trigger

  use tem_status_module, only: tem_status_type,        &
    &                          tem_status_clear,       &
    &                          tem_status_dump,        &
    &                          tem_status_communicate, &
    &                          tem_stat_max_sim,       &
    &                          tem_stat_max_iter,      &
    &                          tem_stat_max_clock,     &
    &                          tem_stat_interval,      &
    &                          tem_stat_stop_file

  use tem_abortCriteria_module, only: tem_abortCriteria_type, &
    &                                 tem_abortCriteria_new,  &
    &                                 tem_abortCriteria_load, &
    &                                 tem_abortCriteria_out,  &
    &                                 tem_abortCriteria_dump, &
    &                                 tem_stop_file_exists

  use tem_convergence_module,       only: tem_convergence_reset

  use aotus_module,     only: flu_State
  use aot_table_module, only: aot_table_open, &
    &                         aot_table_close

  use aot_out_module,   only: aot_out_type,       &
    &                         aot_out_val,        &
    &                         aot_out_open_table, &
    &                         aot_out_close_table

  implicit none

  private

  public :: tem_simControl_type
  public :: tem_simControl_start
  public :: tem_simControl_load
  public :: tem_simControl_out
  public :: tem_simControl_dump
  public :: tem_simControl_dump_now
  public :: tem_simControl_syncUpdate
  public :: tem_simControl_clearStat
  public :: tem_simControl_steadyState_reset


  !> Data structure to describe the overall control of a simulation.
  !!
  !! This comprises the current time in all available definitions.
  type tem_simControl_type
    !> Representation of the current time.
    type(tem_time_type) :: now

    !> Time control, when the simulation should end, and definition of
    !! special interval, at which regular actions should take place.
    !!
    !! The minimum setting has no significance here and is always set to
    !! the time, provided when loading the sim control.
    type(tem_timeControl_type) :: timeControl

    !> Further abort criteria.
    type(tem_abortCriteria_type) :: abortCriteria

    !> Flag collection to describe the status of the simulation.
    type(tem_status_type) :: status
  end type tem_simControl_type


contains


  ! **************************************************************************** !
  !> Start a sim control by resetting its time object.
  !!
  !! Note, that the actual control needs to be filled afterwards with
  !! tem_simControl_load.
  subroutine tem_simControl_start(me)
    ! --------------------------------------------------------------------------!
    !> The simulation control structure to start.
    type(tem_simControl_type), intent(out) :: me
    ! --------------------------------------------------------------------------!

    call tem_time_reset(me%now)

    call tem_status_clear(me = me%status)

  end subroutine tem_simControl_start
  ! **************************************************************************** !


  ! **************************************************************************** !
  !> Load sim control settings from a configuration script.
  !!
  !! The sim control should be started beforhand to ensure a sane setting of
  !! the current time.
  !! The main setting here, is the time_control, which is also attempted to
  !! be read directly, if there is no sim_control table provided.
  subroutine tem_simControl_load(me, conf, parent, key)
    ! --------------------------------------------------------------------------!
    !> Simulation control parameters to set.
    type(tem_simControl_type), intent(inout) :: me

    !> Handle to the configuration script to load the settings from.
    type(flu_state) :: conf

    !> Potential parent table, in which the simulation control table is to be
    !! found.
    integer, intent(in), optional :: parent

    !> Name for the simulation control table. Default is 'sim_control'.
    character(len=*), optional :: key
    ! --------------------------------------------------------------------------!
    character(len=labelLen) :: loc_key
    integer :: thandle
    ! --------------------------------------------------------------------------!

    loc_key = 'sim_control'
    if (present(key)) loc_key = key

    call aot_table_open( L       = conf,    &
      &                  parent  = parent,  &
      &                  thandle = thandle, &
      &                  key     = loc_key  )

    if (thandle /= 0) then
      ! There is a sim control table in the configuration.
      call tem_timeControl_load(me     = me%timeControl, &
        &                       conf   = conf,           &
        &                       parent = thandle         )

      call tem_abortCriteria_load(me     = me%abortCriteria, &
        &                         conf   = conf,             &
        &                         parent = thandle           )
    else
      ! No sim control table found, try to load the time control table itself.
      call tem_timeControl_load(me%timeControl, conf, parent)
      me%abortCriteria = tem_abortCriteria_new()
    end if

    call aot_table_close(L = conf, thandle = thandle)

    ! Overwrite the min of the simulation time range to point to the current
    ! simulation time.
    call tem_timeControl_start_at_sim(me = me%timeControl, now = me%now)

  end subroutine tem_simControl_load
  ! **************************************************************************** !


  ! **************************************************************************** !
  !> Write sim control settings to a configuration script.
  subroutine tem_simControl_out(me, conf, key)
    ! --------------------------------------------------------------------------!
    !> The simulation control settings to write to a Lua table.
    type(tem_simControl_type), intent(inout) :: me

    !> Handle for the Lua script to write to.
    type(aot_out_type) :: conf

    !> Name for the simulation control table. Default is sim_control.
    character(len=*), optional :: key
    ! --------------------------------------------------------------------------!
    character(len=labelLen) :: loc_key
    ! --------------------------------------------------------------------------!

    loc_key = 'sim_control'
    if (present(key)) loc_key = key

    call aot_out_open_table( put_conf = conf,                                  &
      &                      tname    = loc_key                                )

    call tem_time_out( me   = me%now,                                          &
      &                conf = conf                                             )

    call tem_timeControl_out( me   = me%timeControl,                           &
      &                       conf = conf                                      )

    call tem_abortCriteria_out( me   = me%abortCriteria,                       &
      &                         conf = conf                                    )

    call aot_out_close_table( put_conf = conf)

  end subroutine tem_simControl_out
  ! ************************************************************************** !


  ! ************************************************************************** !
  !> Dump the current time (now) of the simControl to outUnit.
  !!
  !! This also updates the clock in now to really show the current time.
  subroutine tem_simControl_dump_now(me, outUnit)
    ! -------------------------------------------------------------------------!
    !> Simulation control settings to write to outUnit.
    type(tem_simControl_type), intent(inout) :: me

    !> File unit to write to.
    integer, intent(in) :: outUnit
    ! -------------------------------------------------------------------------!

    call tem_time_set_clock(me%now)
    call tem_time_dump(me%now, outUnit)

  end subroutine tem_simControl_dump_now
  ! ************************************************************************** !


  ! ************************************************************************** !
  !> Dump simcontrol information to the specified outUnit.
  subroutine tem_simControl_dump(me, outUnit)
    ! -------------------------------------------------------------------------!
    !> Simulation control settings to write to outUnit.
    type(tem_simControl_type), intent(inout) :: me

    !> File unit to write to.
    integer, intent(in) :: outUnit
    ! -------------------------------------------------------------------------!

    write(outUnit,*) '+-------------------------------------------+'
    write(outUnit,*) ' - Now:'
    call tem_simControl_dump_now(me, outUnit)
    write(outUnit,*) ''
    write(outUnit,*) ' - Time Control:'
    call tem_timeControl_dump(me%timeControl, outUnit)
    write(outUnit,*) ''
    write(outUnit,*) ' - Abort Criteria:'
    call tem_abortCriteria_dump(me%abortCriteria, outUnit)
    write(outUnit,*) ''
    write(outUnit,*) ' - Status:'
    call tem_status_dump(me%status, outUnit)
    write(outUnit,*) '+-------------------------------------------+'

  end subroutine tem_simControl_dump
  ! **************************************************************************** !


  ! **************************************************************************** !
  !> Synchronize the status bits across all processes and update the time.
  subroutine tem_simControl_syncUpdate(me, proc, dt, d_iter, outUnit)
    ! --------------------------------------------------------------------------!
    !> Simulation control information.
    type(tem_simControl_type), intent(inout) :: me

    !> Unit to write messages to.
    !!
    !! If this argument is present, the current time will be printed whenever
    !! the interval of the simControl is triggered.
    integer, intent(in), optional :: outUnit

    !> Communicator to use for the communication of status flags.
    type(tem_comm_env_type), intent(in) :: proc

    !> Time step to use for updating the simulation time.
    !!
    !! If this is not given, no advance of the time will be done.
    real(kind=rk), intent(in), optional :: dt

    !> Number of iterations to add to the current number of iterations.
    !! (Default: 1)
    integer, intent(in), optional :: d_iter
    ! --------------------------------------------------------------------------!
    logical :: max_reached(tem_time_n_ids)
    ! --------------------------------------------------------------------------!

    if (present(dt)) then
      call tem_time_advance( me     = me%now, &
        &                    sim_dt = dt,     &
        &                    iter   = d_iter  )
    end if

    max_reached = tem_timeControl_reached_max(me%timeControl, me%now)

    me%status%bits(tem_stat_max_sim)   = max_reached(tem_time_sim_id)
    me%status%bits(tem_stat_max_iter)  = max_reached(tem_time_iter_id)
    if (mod(me%now%iter, me%timeControl%check_iter) == 0) then
      me%status%bits(tem_stat_max_clock) = max_reached(tem_time_clock_id)

      me%status%bits(tem_stat_interval) = tem_timeControl_triggered( &
        &                                   me  = me%timeControl,    &
        &                                   now = me%now,            &
        &                                   comm = proc%comm         )

      me%status%bits(tem_stat_stop_file) &
        &  = tem_stop_file_exists( abortCriteria = me%abortCriteria, &
        &                          rank          = proc%rank         )

      call tem_status_communicate(me = me%status, comm = proc%comm)

      if (present(outUnit)) then
        if (me%status%bits(tem_stat_interval)) then
          call tem_time_set_clock(me%now)
          call tem_time_dump(me%now, outUnit)
        end if
      end if

      call tem_timeControl_update( me           = me%timeControl,            &
        &                          now          = me%now,                    &
        &                          comm         = proc%comm,                 &
        &                          hasTriggered = me%status                  &
        &                                           %bits(tem_stat_interval) )
    end if

  end subroutine tem_simControl_syncUpdate
  ! **************************************************************************** !


  ! **************************************************************************** !
  !> Clear the status flags in the simcontrol.
  subroutine tem_simControl_clearStat(me)
    ! --------------------------------------------------------------------------!
    !> Simulation control information.
    type(tem_simControl_type), intent(inout) :: me
    ! --------------------------------------------------------------------------!

    if (mod(me%now%iter, me%timeControl%check_iter) == 0) then
      call tem_status_clear(me%status)
    end if

  end subroutine tem_simControl_clearStat
  ! **************************************************************************** !

  !****************************************************************************!
  !> Reset trigger, status bit and current time for steady state
  subroutine tem_simControl_steadyState_reset(me)
    ! --------------------------------------------------------------------------!
    type(tem_simControl_type), intent(inout) :: me
    ! --------------------------------------------------------------------------!
    ! clear status bit
    call tem_status_clear(me%status)

    ! reset current time
    call tem_time_reset(me%now)

    ! Run steady state solver untill the solution convergences
    me%timeControl%max%sim = huge(me%timeControl%max%sim)
    me%timeControl%max%iter = huge(me%timeControl%max%iter)

    ! reset simcontrol trigger
    call tem_timeControl_reset_trigger(me%timeControl)

    ! reset convergence to check for new steady state
    call tem_convergence_reset(me%abortCriteria%convergence )

  end subroutine tem_simControl_steadyState_reset
  !****************************************************************************!


end module tem_simControl_module
