! Manage timing routine
module MW_timers

   implicit none
   private

   ! subroutine
   public :: MW_timers_initialize
   public :: MW_timers_start
   public :: MW_timers_pause
   public :: MW_timers_resume
   public :: MW_timers_stop
   public :: MW_timers_print
   public :: MW_timers_lastElapsedTime

   integer, parameter, public :: TIMER_TOTAL = 1

   ! Main
   integer, parameter :: NUM_TIMER_MAIN = 3
   integer, parameter :: OFFSET_TIMER_MAIN = 1
   integer, parameter, public :: TIMER_INITIALIZATION = OFFSET_TIMER_MAIN + 1
   integer, parameter, public :: TIMER_SETUP = OFFSET_TIMER_MAIN + 2
   integer, parameter, public :: TIMER_MAIN_LOOP = OFFSET_TIMER_MAIN + 3

   character(32), parameter :: TIMERS_DESCRIPTIONS_MAIN(NUM_TIMER_MAIN) = [ &
         "initialization:                 ", &
         "setup:                          ", &
         "main loop:                      "]

   ! Coulomb Melt-Elec Potential
   integer, parameter :: NUM_TIMER_COULOMB_MELTELEC = 3
   integer, parameter :: OFFSET_TIMER_COULOMB_MELTELEC = 4
   integer, parameter, public :: TIMER_COULOMB_MELTELEC_LR = OFFSET_TIMER_COULOMB_MELTELEC + 1
   integer, parameter, public :: TIMER_COULOMB_MELTELEC_KEQ0 = OFFSET_TIMER_COULOMB_MELTELEC + 2
   integer, parameter, public :: TIMER_COULOMB_MELTELEC_SR = OFFSET_TIMER_COULOMB_MELTELEC + 3

   character(32), parameter :: TIMERS_DESCRIPTIONS_COULOMB_MELTELEC(NUM_TIMER_COULOMB_MELTELEC) = [ &
         "long range                      ", &
         "k==0                            ", &
         "short range                     "]

   ! Coulomb Elec-Elec Potential
   integer, parameter :: NUM_TIMER_COULOMB_ELECELEC = 4
   integer, parameter :: OFFSET_TIMER_COULOMB_ELECELEC = 7
   integer, parameter, public :: TIMER_COULOMB_ELECELEC_LR = OFFSET_TIMER_COULOMB_ELECELEC + 1
   integer, parameter, public :: TIMER_COULOMB_ELECELEC_KEQ0 = OFFSET_TIMER_COULOMB_ELECELEC + 2
   integer, parameter, public :: TIMER_COULOMB_ELECELEC_SR = OFFSET_TIMER_COULOMB_ELECELEC + 3
   integer, parameter, public :: TIMER_COULOMB_ELECELEC_SELF = OFFSET_TIMER_COULOMB_ELECELEC + 4

   character(32), parameter :: TIMERS_DESCRIPTIONS_COULOMB_ELECELEC(NUM_TIMER_COULOMB_ELECELEC) = [ &
         "long range                      ", &
         "k==0                            ", &
         "short range                     ", &
         "self                            "]

   ! Coulomb GradQ of the Elec-Elec Potential
   integer, parameter :: NUM_TIMER_COULOMB_GRADQ_ELECELEC = 4
   integer, parameter :: OFFSET_TIMER_COULOMB_GRADQ_ELECELEC = 11
   integer, parameter, public :: TIMER_COULOMB_GRADQ_ELECELEC_LR = OFFSET_TIMER_COULOMB_GRADQ_ELECELEC + 1
   integer, parameter, public :: TIMER_COULOMB_GRADQ_ELECELEC_KEQ0 = OFFSET_TIMER_COULOMB_GRADQ_ELECELEC + 2
   integer, parameter, public :: TIMER_COULOMB_GRADQ_ELECELEC_SR = OFFSET_TIMER_COULOMB_GRADQ_ELECELEC + 3
   integer, parameter, public :: TIMER_COULOMB_GRADQ_ELECELEC_SELF = OFFSET_TIMER_COULOMB_GRADQ_ELECELEC + 4

   character(32), parameter :: TIMERS_DESCRIPTIONS_COULOMB_GRADQ_ELECELEC(NUM_TIMER_COULOMB_GRADQ_ELECELEC) = [ &
         "long range                      ", &
         "k==0                            ", &
         "short range                     ", &
         "self                            "]

   ! Coulomb Forces on melt particules
   integer, parameter :: NUM_TIMER_COULOMB_MELT_FORCES = 4
   integer, parameter :: OFFSET_TIMER_COULOMB_MELT_FORCES = 15
   integer, parameter, public :: TIMER_COULOMB_MELT_FORCES_LR = OFFSET_TIMER_COULOMB_MELT_FORCES + 1
   integer, parameter, public :: TIMER_COULOMB_MELT_FORCES_KEQ0 = OFFSET_TIMER_COULOMB_MELT_FORCES + 2
   integer, parameter, public :: TIMER_COULOMB_MELT_FORCES_SR = OFFSET_TIMER_COULOMB_MELT_FORCES + 3
   integer, parameter, public :: TIMER_COULOMB_MELT_FORCES_MOL = OFFSET_TIMER_COULOMB_MELT_FORCES + 4

   character(32), parameter :: TIMERS_DESCRIPTIONS_COULOMB_MELT_FORCES(NUM_TIMER_COULOMB_MELT_FORCES) = [ &
         "long range                      ", &
         "k==0                            ", &
         "short range                     ", &
         "intramolecular                  "]

   ! Coulomb Potential on Melt particules
   integer, parameter :: NUM_TIMER_COULOMB_MELT_POTENTIAL = 5
   integer, parameter :: OFFSET_TIMER_COULOMB_MELT_POTENTIAL = 19
   integer, parameter, public :: TIMER_COULOMB_MELT_POTENTIAL_LR = OFFSET_TIMER_COULOMB_MELT_POTENTIAL + 1
   integer, parameter, public :: TIMER_COULOMB_MELT_POTENTIAL_KEQ0 = OFFSET_TIMER_COULOMB_MELT_POTENTIAL + 2
   integer, parameter, public :: TIMER_COULOMB_MELT_POTENTIAL_SR = OFFSET_TIMER_COULOMB_MELT_POTENTIAL + 3
   integer, parameter, public :: TIMER_COULOMB_MELT_POTENTIAL_MOL = OFFSET_TIMER_COULOMB_MELT_POTENTIAL + 4
   integer, parameter, public :: TIMER_COULOMB_MELT_POTENTIAL_SELF = OFFSET_TIMER_COULOMB_MELT_POTENTIAL + 5

   character(32), parameter :: TIMERS_DESCRIPTIONS_COULOMB_MELT_POTENTIAL(NUM_TIMER_COULOMB_MELT_POTENTIAL) = [ &
         "long range                      ", &
         "k==0                            ", &
         "short range                     ", &
         "intramolecular                  ", &
         "self                            "]

   ! Electric field due to charges on melt particules
   integer, parameter :: NUM_TIMER_COULOMB_MELT_ELECQ = 4
   integer, parameter :: OFFSET_TIMER_COULOMB_MELT_ELECQ = 24
   integer, parameter, public :: TIMER_COULOMB_MELT_ELECQ_LR = OFFSET_TIMER_COULOMB_MELT_ELECQ + 1
   integer, parameter, public :: TIMER_COULOMB_MELT_ELECQ_KEQ0 = OFFSET_TIMER_COULOMB_MELT_ELECQ + 2
   integer, parameter, public :: TIMER_COULOMB_MELT_ELECQ_SR = OFFSET_TIMER_COULOMB_MELT_ELECQ + 3
   integer, parameter, public :: TIMER_COULOMB_MELT_ELECQ_MOL = OFFSET_TIMER_COULOMB_MELT_ELECQ + 4

   character(32), parameter :: TIMERS_DESCRIPTIONS_COULOMB_MELT_ELECQ(NUM_TIMER_COULOMB_MELT_ELECQ) = [ &
         "long range                      ", &
         "k==0                            ", &
         "short range                     ", &
         "intramolecular                  "] 

   ! Electric field due to dipoles on Melt particules
   integer, parameter :: NUM_TIMER_COULOMB_MELT_ELECMU = 5
   integer, parameter :: OFFSET_TIMER_COULOMB_MELT_ELECMU = 28
   integer, parameter, public :: TIMER_COULOMB_MELT_ELECMU_LR = OFFSET_TIMER_COULOMB_MELT_ELECMU + 1
   integer, parameter, public :: TIMER_COULOMB_MELT_ELECMU_KEQ0 = OFFSET_TIMER_COULOMB_MELT_ELECMU + 2
   integer, parameter, public :: TIMER_COULOMB_MELT_ELECMU_SR = OFFSET_TIMER_COULOMB_MELT_ELECMU + 3
   integer, parameter, public :: TIMER_COULOMB_MELT_ELECMU_MOL = OFFSET_TIMER_COULOMB_MELT_ELECMU + 4
   integer, parameter, public :: TIMER_COULOMB_MELT_ELECMU_SELF = OFFSET_TIMER_COULOMB_MELT_ELECMU + 5

   character(32), parameter :: TIMERS_DESCRIPTIONS_COULOMB_MELT_ELECMU(NUM_TIMER_COULOMB_MELT_ELECMU) = [ &
         "long range                      ", &
         "k==0                            ", &
         "short range                     ", &
         "intramolecular                  ", &
         "self                            "]

   ! Gradmu of the electric field on Melt particules
   integer, parameter :: NUM_TIMER_COULOMB_MELT_GRADMU_ELEC = 4
   integer, parameter :: OFFSET_TIMER_COULOMB_MELT_GRADMU_ELEC = 33
   integer, parameter, public :: TIMER_COULOMB_MELT_GRADMU_ELEC_LR = OFFSET_TIMER_COULOMB_MELT_GRADMU_ELEC + 1
   integer, parameter, public :: TIMER_COULOMB_MELT_GRADMU_ELEC_KEQ0 = OFFSET_TIMER_COULOMB_MELT_GRADMU_ELEC + 2
   integer, parameter, public :: TIMER_COULOMB_MELT_GRADMU_ELEC_SR = OFFSET_TIMER_COULOMB_MELT_GRADMU_ELEC + 3
   integer, parameter, public :: TIMER_COULOMB_MELT_GRADMU_ELEC_SELF = OFFSET_TIMER_COULOMB_MELT_GRADMU_ELEC + 4

   character(32), parameter :: TIMERS_DESCRIPTIONS_COULOMB_MELT_GRADMU_ELEC(NUM_TIMER_COULOMB_MELT_GRADMU_ELEC) = [ &
         "long range                      ", &
         "k==0                            ", &
         "short range                     ", &
         "self                            "]

   ! Rattle
   integer, parameter :: NUM_TIMER_RATTLE = 2
   integer, parameter :: OFFSET_TIMER_RATTLE = 37
   integer, parameter, public :: TIMER_RATTLE_POSITIONS = OFFSET_TIMER_RATTLE + 1
   integer, parameter, public :: TIMER_RATTLE_VELOCITIES = OFFSET_TIMER_RATTLE + 2

   character(32), parameter :: TIMERS_DESCRIPTIONS_RATTLE(NUM_TIMER_RATTLE) = [ &
         "positions                       ", &
         "velocities                      "]

   ! Van der Waals (=LJ, FT, XFT, DAIM, Steele)
   integer, parameter :: NUM_TIMER_VAN_DER_WAALS = 2
   integer, parameter :: OFFSET_TIMER_VAN_DER_WAALS = 39
   integer, parameter, public :: TIMER_VAN_DER_WAALS_MELT_FORCES = OFFSET_TIMER_VAN_DER_WAALS + 1
   integer, parameter, public :: TIMER_VAN_DER_WAALS_MELT_POTENTIAL = OFFSET_TIMER_VAN_DER_WAALS + 2

   character(32), parameter :: TIMERS_DESCRIPTIONS_VAN_DER_WAALS(NUM_TIMER_VAN_DER_WAALS) = [ &
         "vdW  forces                     ", &
         "vdW  potential                  "]

   ! INTRAMOLECULAR
   integer, parameter :: NUM_TIMER_INTRA = 2
   integer, parameter :: OFFSET_TIMER_INTRA = 41
   integer, parameter, public :: TIMER_INTRA_MELT_FORCES = OFFSET_TIMER_INTRA + 1
   integer, parameter, public :: TIMER_INTRA_MELT_POTENTIAL = OFFSET_TIMER_INTRA + 2

   character(32), parameter :: TIMERS_DESCRIPTIONS_INTRA(NUM_TIMER_INTRA) = [ &
         "Intramolecular  forces         ", &
         "Intramolecular potential       "]

   ! Minimizer (charges)
   integer, parameter :: NUM_TIMER_MINQ = 3
   integer, parameter :: OFFSET_TIMER_MINQ = 43
   integer, parameter, public :: TIMER_MINQ = OFFSET_TIMER_MINQ + 1
   integer, parameter, public :: TIMER_MINQ_INVMAT = OFFSET_TIMER_MINQ + 2
   integer, parameter, public :: TIMER_MINQ_MATVEC = OFFSET_TIMER_MINQ + 3

   character(32), parameter :: TIMERS_DESCRIPTIONS_MINQ(NUM_TIMER_MINQ) = [ &
         "Electrode charge computation   ", &
         "Inversion of the matrix        ", &
         "One matrix-vector product      "]

   ! Minimizer (dipoles)
   integer, parameter :: NUM_TIMER_MINMU = 3
   integer, parameter :: OFFSET_TIMER_MINMU = 46
   integer, parameter, public :: TIMER_MINMU = OFFSET_TIMER_MINMU + 1
   integer, parameter, public :: TIMER_MINMU_INVMAT = OFFSET_TIMER_MINMU + 2
   integer, parameter, public :: TIMER_MINMU_MATVEC = OFFSET_TIMER_MINMU + 3

   character(32), parameter :: TIMERS_DESCRIPTIONS_MINMU(NUM_TIMER_MINMU) = [ &
         "Melt dipoles computation       ", &
         "Inversion of the matrix        ", &
         "One matrix-vector product      "]

   ! Minimizer (AIM radius and dipoles)
   integer, parameter :: NUM_TIMER_MINAIM = 1
   integer, parameter :: OFFSET_TIMER_MINAIM = 49 
   integer, parameter, public :: TIMER_MINAIM = OFFSET_TIMER_MINAIM + 1

   character(32), parameter :: TIMERS_DESCRIPTIONS_MINAIM(NUM_TIMER_MINAIM) = [ &
         "AIM DOFs computation            "]

   ! Diagnostics
   integer, parameter :: NUM_TIMER_DIAG = 2
   integer, parameter :: OFFSET_TIMER_DIAG = 50
   integer, parameter, public :: TIMER_DIAG_COMPUTE = OFFSET_TIMER_DIAG + 1
   integer, parameter, public :: TIMER_DIAG_IO = OFFSET_TIMER_DIAG + 2

   character(32), parameter :: TIMERS_DESCRIPTIONS_DIAG(NUM_TIMER_DIAG) = [ &
         "diagnostics computations        ", &
         "IO                              "]

   ! Measured but not printed timers
   integer, parameter :: NUM_TIMER_MISC = 1
   integer, parameter :: OFFSET_TIMER_MISC = 52
   integer, parameter, public :: TIMER_STEP = OFFSET_TIMER_MISC + 1

   ! Electric field gradient on melt particles
   integer, parameter :: NUM_TIMER_COULOMB_EFG_MELT = 3
   integer, parameter :: OFFSET_TIMER_COULOMB_EFG_MELT = 53
   integer, parameter, public :: TIMER_COULOMB_EFG_MELT_LR = OFFSET_TIMER_COULOMB_EFG_MELT + 1
   integer, parameter, public :: TIMER_COULOMB_EFG_MELT_SR = OFFSET_TIMER_COULOMB_EFG_MELT + 2
   integer, parameter, public :: TIMER_COULOMB_EFG_MELT_SELF = OFFSET_TIMER_COULOMB_EFG_MELT + 3

   character(32), parameter :: TIMERS_DESCRIPTIONS_COULOMB_TIMER_COULOMB_EFG_MELT(NUM_TIMER_COULOMB_EFG_MELT) = [ &
         "long range                      ", &
         "short range                     ", &
         "self                            "]

   integer, parameter :: MAX_TIMER_TYPES = 56
   real,    save :: TIMERS_START(MAX_TIMER_TYPES)
   real,    save :: TIMERS_STOP(MAX_TIMER_TYPES)
   real,    save :: TIMERS_LAST(MAX_TIMER_TYPES)
   real,    save :: TIMERS_CUMUL(MAX_TIMER_TYPES)
   integer, save :: TIMERS_COUNT(MAX_TIMER_TYPES)

contains

   !================================================================================
   ! Initialize the timers
   subroutine MW_timers_initialize()
      TIMERS_START(:) = 0.0
      TIMERS_STOP(:) = 0.0
      TIMERS_LAST(:) = 0.0
      TIMERS_CUMUL(:) = 0.0
      TIMERS_COUNT(:) = 0
   end subroutine MW_timers_initialize

   !================================================================================
   ! Start a specific timer
   subroutine MW_timers_start(timer)
      integer, intent(in) :: timer

      call cpu_time(TIMERS_START(timer))
      TIMERS_STOP(timer) = 0.0
      TIMERS_COUNT(timer) = TIMERS_COUNT(timer) + 1
   end subroutine MW_timers_start

   !================================================================================
   ! Resume a specific timer
   subroutine MW_timers_resume(timer)
      integer, intent(in) :: timer

      call cpu_time(TIMERS_START(timer))
      TIMERS_STOP(timer) = 0.0
   end subroutine MW_timers_resume

   !================================================================================
   ! Stop a specific timer
   subroutine MW_timers_stop(timer)
      integer, intent(in) :: timer
      real :: time

      call cpu_time(TIMERS_STOP(timer))

      time = TIMERS_STOP(timer) - TIMERS_START(timer)
      TIMERS_LAST(timer) = time
      TIMERS_CUMUL(timer) = TIMERS_CUMUL(timer) + time

   end subroutine MW_timers_stop

   !================================================================================
   ! Pause a specific timer
   subroutine MW_timers_pause(timer)
      integer, intent(in) :: timer
      real :: time

      call cpu_time(TIMERS_STOP(timer))

      time = TIMERS_STOP(timer) - TIMERS_START(timer)
      TIMERS_LAST(timer) = time
      TIMERS_CUMUL(timer) = TIMERS_CUMUL(timer) + time

   end subroutine MW_timers_pause

   !================================================================================
   ! Print timer results to ounit
   subroutine MW_timers_print(ounit)
      integer, intent(in) :: ounit ! output unit

      integer :: i, timer
      real :: avg, cumul, total


      write(ounit, *)

      total = MW_timers_cumulatedElapsedTime(TIMER_TOTAL)
      write(ounit,'("Total elapsed time: ",es12.5," seconds.")') total
      write(ounit,*)
 
      ! Breakdown header
      write(ounit,'("Elapsed Times breakdown:")')
      write(ounit,'("========================")')
      write(ounit,'(2x,32x,a12,1x,a12,1x,a12)') &
            " average (s)","  cumul. (s)","fraction (%)"

      ! Categories break down
      do i = 1, NUM_TIMER_MAIN
         timer = i + OFFSET_TIMER_MAIN
         avg = MW_timers_averagedElapsedTime(timer)
         cumul = MW_timers_cumulatedElapsedTime(timer)
         write(ounit,'(2x,a32,es12.5,1x,es12.5,1x,f12.2)') &
               TIMERS_DESCRIPTIONS_MAIN(i), avg, cumul, 100.0*(cumul/total)
      end do

      ! Computing the non covered part of the code by the timers
      cumul = 0.0
      do i = 5, 52
         if((i.ne.44).and.(i.ne.47))then ! ugly but avoids double counting
            cumul = cumul + MW_timers_cumulatedElapsedTime(i)
         endif
      end do
         write(ounit,'(2x,a44,1x,es12.5,1x,f12.2)') &
               "Time breakdown                     ----     ", cumul, 100.0*(cumul/total)
         write(ounit,'(2x,a44,1x,es12.5,1x,f12.2)') &
               "=> Time NOT in breakdown           ----     ", total - cumul, 100.0*(1.0 - cumul/total)

      write(ounit,*)
      write(ounit,'("Ions->Atoms Coulomb potential")')
      write(ounit,'("-----------------------------")')
      ! Categories break down
      do i = 1,  NUM_TIMER_COULOMB_MELTELEC
         timer = i + OFFSET_TIMER_COULOMB_MELTELEC
         avg = MW_timers_averagedElapsedTime(timer)
         cumul = MW_timers_cumulatedElapsedTime(timer)
         write(ounit,'(2x,a32,es12.5,1x,es12.5,1x,f12.2)') &
               TIMERS_DESCRIPTIONS_COULOMB_MELTELEC(i), avg, cumul, 100.0*(cumul/total)
      end do

      write(ounit,*)
      write(ounit,'("Atoms->Atoms Coulomb potential")')
      write(ounit,'("------------------------------")')
      ! Categories break down
      do i = 1,  NUM_TIMER_COULOMB_ELECELEC
         timer = i + OFFSET_TIMER_COULOMB_ELECELEC
         avg = MW_timers_averagedElapsedTime(timer)
         cumul = MW_timers_cumulatedElapsedTime(timer)
         write(ounit,'(2x,a32,es12.5,1x,es12.5,1x,f12.2)') &
               TIMERS_DESCRIPTIONS_COULOMB_ELECELEC(i), avg, cumul, 100.0*(cumul/total)
      end do

      write(ounit,*)
      write(ounit,'("Atoms->Atoms Coulomb grad Q of the potential")')
      write(ounit,'("--------------------------------------------")')
      ! Categories break down
      do i = 1,  NUM_TIMER_COULOMB_GRADQ_ELECELEC
         timer = i + OFFSET_TIMER_COULOMB_GRADQ_ELECELEC
         avg = MW_timers_averagedElapsedTime(timer)
         cumul = MW_timers_cumulatedElapsedTime(timer)
         write(ounit,'(2x,a32,es12.5,1x,es12.5,1x,f12.2)') &
               TIMERS_DESCRIPTIONS_COULOMB_GRADQ_ELECELEC(i), avg, cumul, 100.0*(cumul/total)
      end do

      write(ounit,*)
      write(ounit,'("Ions Coulomb forces")')
      write(ounit,'("-------------------")')
      ! Categories break down
      do i = 1,  NUM_TIMER_COULOMB_MELT_FORCES
         timer = i + OFFSET_TIMER_COULOMB_MELT_FORCES
         avg = MW_timers_averagedElapsedTime(timer)
         cumul = MW_timers_cumulatedElapsedTime(timer)
         write(ounit,'(2x,a32,es12.5,1x,es12.5,1x,f12.2)') &
               TIMERS_DESCRIPTIONS_COULOMB_MELT_FORCES(i), avg, cumul, 100.0*(cumul/total)
      end do

      write(ounit,*)
      write(ounit,'("Ions Coulomb potential")')
      write(ounit,'("----------------------")')
      ! Categories break down
      do i = 1,  NUM_TIMER_COULOMB_MELT_POTENTIAL
         timer = i + OFFSET_TIMER_COULOMB_MELT_POTENTIAL
         avg = MW_timers_averagedElapsedTime(timer)
         cumul = MW_timers_cumulatedElapsedTime(timer)
         write(ounit,'(2x,a32,es12.5,1x,es12.5,1x,f12.2)') &
               TIMERS_DESCRIPTIONS_COULOMB_MELT_POTENTIAL(i), avg, cumul, 100.0*(cumul/total)
      end do

      write(ounit,*)
      write(ounit,'("Ions Coulomb electric field (due to charges)")')
      write(ounit,'("--------------------------------------------")')
      ! Categories break down
      do i = 1,  NUM_TIMER_COULOMB_MELT_ELECQ
         timer = i + OFFSET_TIMER_COULOMB_MELT_ELECQ
         avg = MW_timers_averagedElapsedTime(timer)
         cumul = MW_timers_cumulatedElapsedTime(timer)
         write(ounit,'(2x,a32,es12.5,1x,es12.5,1x,f12.2)') &
               TIMERS_DESCRIPTIONS_COULOMB_MELT_ELECQ(i), avg, cumul, 100.0*(cumul/total)
      end do

      write(ounit,*)
      write(ounit,'("Ions Coulomb electric field (due to dipoles)")')
      write(ounit,'("---------------------------------------- ---")')
      ! Categories break down
      do i = 1,  NUM_TIMER_COULOMB_MELT_ELECMU
         timer = i + OFFSET_TIMER_COULOMB_MELT_ELECMU
         avg = MW_timers_averagedElapsedTime(timer)
         cumul = MW_timers_cumulatedElapsedTime(timer)
         write(ounit,'(2x,a32,es12.5,1x,es12.5,1x,f12.2)') &
               TIMERS_DESCRIPTIONS_COULOMB_MELT_ELECMU(i), avg, cumul, 100.0*(cumul/total)
      end do

      write(ounit,*)
      write(ounit,'("Ions Coulomb electric field gradient (due to charges and dipoles)")')
      write(ounit,'("---------------------------------------- ---")')
      ! Categories break down
      do i = 1,  NUM_TIMER_COULOMB_EFG_MELT
         timer = i + OFFSET_TIMER_COULOMB_EFG_MELT
         avg = MW_timers_averagedElapsedTime(timer)
         cumul = MW_timers_cumulatedElapsedTime(timer)
         write(ounit,'(2x,a32,es12.5,1x,es12.5,1x,f12.2)') &
               TIMERS_DESCRIPTIONS_COULOMB_TIMER_COULOMB_EFG_MELT(i), avg, cumul, 100.0*(cumul/total)
      end do

      write(ounit,*)
      write(ounit,'("Ions Coulomb gradient mu of the electric field")')
      write(ounit,'("----------------------------------------------")')
      ! Categories break down
      do i = 1,  NUM_TIMER_COULOMB_MELT_GRADMU_ELEC
         timer = i + OFFSET_TIMER_COULOMB_MELT_GRADMU_ELEC
         avg = MW_timers_averagedElapsedTime(timer)
         cumul = MW_timers_cumulatedElapsedTime(timer)
         write(ounit,'(2x,a32,es12.5,1x,es12.5,1x,f12.2)') &
               TIMERS_DESCRIPTIONS_COULOMB_MELT_GRADMU_ELEC(i), avg, cumul, 100.0*(cumul/total)
      end do

      write(ounit,*)
      write(ounit,'("Rattle")')
      write(ounit,'("------")')
      ! Categories break down
      do i = 1,  NUM_TIMER_RATTLE
         timer = i + OFFSET_TIMER_RATTLE
         avg = MW_timers_averagedElapsedTime(timer)
         cumul = MW_timers_cumulatedElapsedTime(timer)
         write(ounit,'(2x,a32,es12.5,1x,es12.5,1x,f12.2)') &
               TIMERS_DESCRIPTIONS_RATTLE(i), avg, cumul, 100.0*(cumul/total)
      end do

      write(ounit,*)
      write(ounit,'("van der Waals")')
      write(ounit,'("-------------")')
      ! Categories break down
      do i = 1,  NUM_TIMER_VAN_DER_WAALS
         timer = i + OFFSET_TIMER_VAN_DER_WAALS
         avg = MW_timers_averagedElapsedTime(timer)
         cumul = MW_timers_cumulatedElapsedTime(timer)
         write(ounit,'(2x,a32,es12.5,1x,es12.5,1x,f12.2)') &
               TIMERS_DESCRIPTIONS_VAN_DER_WAALS(i), avg, cumul, 100.0*(cumul/total)
      end do

      write(ounit,*)
      write(ounit,'("Intramolecular")')
      write(ounit,'("--------------")')
      ! Categories break down
      do i = 1,  NUM_TIMER_INTRA
         timer = i + OFFSET_TIMER_INTRA
         avg = MW_timers_averagedElapsedTime(timer)
         cumul = MW_timers_cumulatedElapsedTime(timer)
         write(ounit,'(2x,a32,es12.5,1x,es12.5,1x,f12.2)') &
               TIMERS_DESCRIPTIONS_INTRA(i), avg, cumul, 100.0*(cumul/total)
      end do

      write(ounit,*)
      write(ounit,'("Additional degrees of freedom computation")')
      write(ounit,'("-----------------------------------------")')
      ! Categories break down
      do i = 1,  NUM_TIMER_MINQ
         timer = i + OFFSET_TIMER_MINQ
         avg = MW_timers_averagedElapsedTime(timer)
         cumul = MW_timers_cumulatedElapsedTime(timer)
         write(ounit,'(2x,a32,es12.5,1x,es12.5,1x,f12.2)') &
               TIMERS_DESCRIPTIONS_MINQ(i), avg, cumul, 100.0*(cumul/total)
      end do

!     write(ounit,*)
!     write(ounit,'("Dipoles computation")')
!     write(ounit,'("-------------------")')
      ! Categories break down
      do i = 1,  NUM_TIMER_MINMU
         timer = i + OFFSET_TIMER_MINMU
         avg = MW_timers_averagedElapsedTime(timer)
         cumul = MW_timers_cumulatedElapsedTime(timer)
         write(ounit,'(2x,a32,es12.5,1x,es12.5,1x,f12.2)') &
               TIMERS_DESCRIPTIONS_MINMU(i), avg, cumul, 100.0*(cumul/total)
      end do

!     write(ounit,*)
!     write(ounit,'("AIM DOFs computation")')
!     write(ounit,'("--------------------")')
      ! Categories break down
      do i = 1,  NUM_TIMER_MINAIM
         timer = i + OFFSET_TIMER_MINAIM
         avg = MW_timers_averagedElapsedTime(timer)
         cumul = MW_timers_cumulatedElapsedTime(timer)
         write(ounit,'(2x,a32,es12.5,1x,es12.5,1x,f12.2)') &
               TIMERS_DESCRIPTIONS_MINAIM(i), avg, cumul, 100.0*(cumul/total)
      end do

      write(ounit,*)
      write(ounit,'("Diagnostics")')
      write(ounit,'("-------------")')
      ! Categories break down
      do i = 1,  NUM_TIMER_DIAG
         timer = i + OFFSET_TIMER_DIAG
         avg = MW_timers_averagedElapsedTime(timer)
         cumul = MW_timers_cumulatedElapsedTime(timer)
         write(ounit,'(2x,a32,es12.5,1x,es12.5,1x,f12.2)') &
               TIMERS_DESCRIPTIONS_DIAG(i), avg, cumul, 100.0*(cumul/total)
      end do

      ! blank line below
      write(ounit,*)
   end subroutine MW_timers_print

   !================================================================================
   ! Get last elapsed time for a given timer
   real function MW_timers_lastElapsedTime(timer)
      integer, intent(in) :: timer
      MW_timers_lastElapsedTime = TIMERS_LAST(timer)
   end function MW_timers_lastElapsedTime


   !================================================================================
   ! Get cumulated elapsed time for a given timer
   real function MW_timers_cumulatedElapsedTime(timer)
      integer, intent(in) :: timer
      MW_timers_CumulatedElapsedTime = TIMERS_CUMUL(timer)
   end function MW_timers_cumulatedElapsedTime

   !================================================================================
   ! Get averaged elapsed time for a given timer
   real function MW_timers_averagedElapsedTime(timer)
      integer, intent(in) :: timer
      if (TIMERS_COUNT(timer) > 0) then
         MW_timers_AveragedElapsedTime = TIMERS_CUMUL(timer) / real(TIMERS_COUNT(timer))
      else
         MW_timers_AveragedElapsedTime = 0.0
      end if
   end function MW_timers_averagedElapsedTime

end module MW_timers
