!!******************************************************************************
!!
!!  This file is part of the AMUN source code, a program to perform
!!  Newtonian or relativistic magnetohydrodynamical simulations on uniform or
!!  adaptive mesh.
!!
!!  Copyright (C) 2008-2021 Grzegorz Kowal <grzegorz@amuncode.org>
!!
!!  This program is free software: you can redistribute it and/or modify
!!  it under the terms of the GNU General Public License as published by
!!  the Free Software Foundation, either version 3 of the License, or
!!  (at your option) any later version.
!!
!!  This program is distributed in the hope that it will be useful,
!!  but WITHOUT ANY WARRANTY; without even the implied warranty of
!!  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
!!  GNU General Public License for more details.
!!
!!  You should have received a copy of the GNU General Public License
!!  along with this program.  If not, see <http://www.gnu.org/licenses/>.
!!
!!******************************************************************************
!!
!! program: AMUN
!!
!!  AMUN is a code to perform numerical simulations in a fluid approximation on
!!  adaptive mesh for Newtonian and relativistic environments with or without
!!  magnetic field.
!!
!!******************************************************************************
!
program amun

  use helpers        , only : print_welcome, print_section, print_parameter
  use io             , only : initialize_io, finalize_io
  use iso_fortran_env, only : error_unit
  use mpitools       , only : initialize_mpitools, finalize_mpitools
#ifdef MPI
  use mpitools       , only : reduce_sum
#endif /* MPI */
  use mpitools       , only : master, nprocs, nproc, check_status
  use parameters     , only : read_parameters, finalize_parameters
  use problem        , only : initialize_problem, finalize_problem
  use problem        , only : print_problem_info, prepare_problem
  use problem        , only : evolve_problem, store_problem
  use problem        , only : resumed, nrun, name, rngtype
  use problem        , only : tmax, trun, nwork, nmax, quit, nsteps
  use random         , only : initialize_random, finalize_random
  use timers         , only : initialize_timers, finalize_timers
  use timers         , only : start_timer, stop_timer, set_timer, get_timer
  use timers         , only : get_timer_total, timer_enabled, timer_description
  use timers         , only : get_count, ntimers
  use workspace      , only : initialize_workspace, finalize_workspace

  implicit none

! the initialization and execution status flags
!
  logical :: initialization_succeeded
  logical :: verbose = .true.
  integer :: status  = 0

! timer indices, iteration number and other time variables
!
  integer      :: iin, iev, itm
  integer      :: i, ed, eh, em, es, ec
  real(kind=8) :: tm_exec, tm_conv

! the format string
!
  character(len=80) :: sfmt

! an array pointer for timings
!
  real(kind=8), dimension(:), allocatable :: tm

#ifndef __GFORTRAN__
! signal is a subroutine only for the GNU compiler, otherwise it is a function
!
  integer(kind=4) :: signal
#endif /* __GFORTRAN__ */

#ifdef SIGNALS
#ifdef __GFORTRAN__
  intrinsic signal
#endif /* __GFORTRAN__ */

  integer, parameter :: SIGERR = -1
  integer, parameter :: SIGINT =  2, SIGABRT =  6, SIGTERM = 15
#endif /* SIGNALS */

  character(len=*), parameter :: loc = 'AMUN::amun()'

!-------------------------------------------------------------------------------
!
#ifdef SIGNALS
! signals handling
!
#ifdef __GFORTRAN__
  if (signal(SIGINT , signal_handler)     == SIGERR) status = 1
  if (signal(SIGABRT, signal_handler)     == SIGERR) status = 2
  if (signal(SIGTERM, signal_handler)     == SIGERR) status = 3
#else /* __GFORTRAN__ */
  if (signal(SIGINT , signal_handler, -1) == SIGERR) status = 1
  if (signal(SIGABRT, signal_handler, -1) == SIGERR) status = 2
  if (signal(SIGTERM, signal_handler, -1) == SIGERR) status = 3
#endif /* __GFORTRAN__ */

  if (status /= 0) then
    write(error_unit,"('[',a,']: ',a)") trim(loc),                             &
                     "Could not initialize the signal handling!"
    call exit(status)
  end if
#endif /* SIGNALS */

! timers
!
  call initialize_timers()

  call set_timer('INITIALIZATION', iin)
  call set_timer('EVOLUTION'     , iev)
  call set_timer('TERMINATION'   , itm)

! parallelization
!
  call initialize_mpitools(status)
  if (status /= 0) then
    write(error_unit,"('[',a,']: ',a)") trim(loc),                             &
                     "Could not initialize module MPITOOLS!"
  end if
  if (check_status(status /= 0)) call exit(status)
  verbose = master

  call start_timer(iin)

  call print_welcome(verbose)
#ifdef MPI
  call print_section(verbose, "Parallelization")
  call print_parameter(verbose, "MPI processes", nprocs)
#endif /* MPI */

  initialization_succeeded = .false.

! read parameters
!
  call read_parameters(verbose, status)
  if (status /= 0) then
    write(error_unit,"('[',a,']: ',a)") trim(loc),                             &
                     "Could not read parameters file!"
  end if
  if (check_status(status /= 0)) go to 6000

! initialize a few basic modules: IO, PROBLEM, RNG, WORKSPACE
!
  call initialize_io(verbose, status)
  if (status /= 0) then
    write(error_unit,"('[',a,']: ',a)") trim(loc),                             &
                     "Could not initialize module IO!"
  end if
  if (check_status(status /= 0)) go to 5000

  call initialize_problem(verbose, status)
  if (status /= 0) then
    write(error_unit,"('[',a,']: ',a)") trim(loc),                             &
                     "Could not initialize module PROBLEM!"
  end if
  if (check_status(status /= 0)) go to 4000

  call initialize_random(rngtype, 1, 0, nproc, status)
  if (status /= 0) then
    write(error_unit,"('[',a,']: ',a)") trim(loc),                             &
                     "Could not initialize module RANDOM!"
  end if
  if (check_status(status /= 0)) go to 3000

  call initialize_workspace(nwork, status)
  if (status /= 0) then
    write(error_unit,"('[',a,']: ',a)") trim(loc),                             &
                     "Problem initializing WORKSPACE module!"
  end if
  if (check_status(status /= 0)) go to 2000

! show some info before the simulation starts
!
  call print_problem_info(verbose)

! prepare the problem
!
  call prepare_problem(status)
  if (status /= 0) then
    write(error_unit,"('[',a,']: ',a)") trim(loc),                           &
                     "Problem preparation failed!"
  end if
  if (check_status(status /= 0)) go to 1000

  call stop_timer(iin)

  initialization_succeeded = .true.

! time evolution starts here
!
  call start_timer(iev)

  call evolve_problem(verbose, status)
  if (status /= 0) then
    write(error_unit,"('[',a,']: ',a)") trim(loc),                           &
                     "Problem execution failed!"
  end if
  if (check_status(status /= 0)) go to 1000

  call stop_timer(iev)

  1000 continue
  call start_timer(itm)

! finalize all modules
!
  call finalize_workspace(status)
  if (status /= 0) then
    write(error_unit,"('[',a,']: ',a)") trim(loc),                             &
                     "Problem finalizing WORKSPACE module!"
  end if
  2000 continue

  call finalize_random(status)
  if (status /= 0) then
    write(error_unit,"('[',a,']: ',a)") trim(loc),                             &
                     "Could not finalize module RANDOM!"
  end if
  3000 continue

  call finalize_problem(verbose, status)
  if (status /= 0) then
    write(error_unit,"('[',a,']: ',a)") trim(loc),                             &
                     "Could not finalize module PROBLEM!"
  end if
  4000 continue

  call finalize_io(status)
  if (status /= 0) then
    write(error_unit,"('[',a,']: ',a)") trim(loc),                             &
                     "Problem finalizing IO module!"
  end if
  5000 continue

  call finalize_parameters()

  call stop_timer(itm)

! print the execution time summary (only if the initialization was successful)
!
  if (initialization_succeeded) then
    allocate(tm(ntimers), stat=status)
    if (status /= 0) then
      write(error_unit,"('[',a,']: ',a)") trim(loc),                         &
                       "Could not allocate memory for timings!"
      go to 6000
    end if

    tm(1) = get_timer_total()
    do i = 2, ntimers
      tm(i) = get_timer(i)
    end do
#ifdef MPI
    call reduce_sum(tm(1:ntimers))
#endif /* MPI */

    if (verbose) then
      write(*  ,'(a)') ''
      write(sfmt,"(a)") "(2x,a32,1x,':',3x,1f16.3,' secs = ',f6.2,' %')"
      write(*,'(1x,a)') 'EXECUTION TIMINGS'
      tm_conv = 1.0d+02 / tm(1)
      do i = 2, ntimers
        if (timer_enabled(i)) then
          if (get_count(i) > 0) then
            write(*,sfmt) timer_description(i), tm(i), tm_conv * tm(i)
          end if
        end if
      end do

      write(sfmt,"(a)") "(1x,a14,20x,':',3x,1f16.3,' secs = ',f6.2,' %')"
      write(*,sfmt) 'TOTAL CPU TIME', tm(1)         , 1.0d+02
      write(*,sfmt) 'TIME PER STEP ', tm(1) / nsteps, 1.0d+02 / nsteps
#ifdef MPI
      write(*,sfmt) 'TIME PER CPU  ', tm(1) / nprocs, 1.0d+02 / nprocs
#endif /* MPI */

      tm_exec = get_timer_total()

      es = int(tm_exec)
      ec = int(1000 * (tm_exec - es))
      ed = es / 86400
      es = es - 86400 * ed
      eh = es /  3600
      es = es -  3600 * eh
      em = es /    60
      es = es -    60 * em

      write(sfmt,"(a)") "(1x,a14,20x,':',3x,i14,'d'" //                        &
                                      ",i3.2,'h',i3.2,'m',i3.2,'.',i3.3,'s')"
      write(*,sfmt) 'EXECUTION TIME', ed, eh, em, es, ec

    end if
    deallocate(tm, stat=status)
    if (status /= 0) then
      write(error_unit,"('[',a,']: ',a)") trim(loc),                         &
                       "Could not deallocate memory for timings!"
    end if
  end if
  6000 continue

  call finalize_mpitools(status)
  if (status /= 0) then
    write(error_unit,"('[',a,']: ',a)") trim(loc),                             &
                     "Problem finalizing MPITOOLS module!"
  end if

  call finalize_timers()

#ifdef SIGNALS
  contains
#ifdef __INTEL_COMPILER
!
!===============================================================================
!
! function SIGNAL_HANDLER:
! -----------------------
!
!   Function sets the variable quit after receiving a signal.
!
!   Arguments:
!
!     sig_num - the number of the signal to be handled;
!
!===============================================================================
!
  integer(kind=4) function signal_handler(sig_num)

    use iso_fortran_env, only : error_unit

    implicit none

    integer(kind=4), intent(in) :: sig_num

!-------------------------------------------------------------------------------
!
    quit = sig_num
    write(error_unit,*)
    write(error_unit,*) "Received signal:", iterm
    write(error_unit,*) "Closing program..."
    signal_handler = 1

!-------------------------------------------------------------------------------
!
  end function
#else /* __INTEL_COMPILER */
!
!===============================================================================
!
! subroutine SIGNAL_HANDLER:
! -------------------------
!
!   Subroutine sets the variable quit after receiving a signal.
!
!===============================================================================
!
  subroutine signal_handler()

    use iso_fortran_env, only : error_unit

    implicit none

!-------------------------------------------------------------------------------
!
    quit = 15
    write(error_unit,*)
    write(error_unit,*) "Received signal: 2, 9, or 15"
    write(error_unit,*) "Closing program..."
    return

!-------------------------------------------------------------------------------
!
  end subroutine
#endif /* __INTEL_COMPILER */
#endif /* SIGNALS */

!===============================================================================
!
end program