! =============================================================================
!> Module to explicitly handle exceptions. Depending on the application at hand
!! we override what happens when an exception is raised, which is useful for
!! testing purposes (no error stop if we _expect_ something to fail).
!! Loosely based on an example given in
!! https://github.com/Goddard-Fortran-Ecosystem/pFUnit_demos/blob/main/Basic/src/throw.F90
module mod_exceptions
  implicit none

  private

  abstract interface
    subroutine raise(message)
       character(len=*), intent(in) :: message
    end subroutine raise
  end interface

  !> pointer to method that is used to raise exceptions
  procedure (raise), pointer :: raise_method => null()
  !> logical to check if <tt>raise_method</tt> pointer is assigned
  logical, save :: initialised = .false.

  public :: raise_exception
  public :: set_raise_method

contains


  !> Private subroutine, sets the pointer to the default
  !! method to be used when raising exceptions.
  subroutine initialise_exceptions()
    raise_method => on_exception_raised
    initialised = .true.
  end subroutine initialise_exceptions


  !> Subroutine meant to be publicly called, sets the
  !! routine to be used when raising exceptions.
  !! Calls the initialisation routine if not already done.
  subroutine set_raise_method(method)
    !> subroutine to be used when an exception is raised
    procedure (raise) :: method

    if (.not. initialised) then
       call initialise_exceptions()
    end if
    raise_method => method
  end subroutine set_raise_method


  !> Raises an exception with a given message.
  !! By default, exceptions terminate program execution.
  !! Calls the initialisation routine if not already done.
  subroutine raise_exception(msg)
    !> message to be used when an exception is raised
    character(len=*), intent(in) :: msg

    if (.not. initialised) then
       call initialise_exceptions()
    end if

    call raise_method(msg)
  end subroutine raise_exception


  ! LCOV_EXCL_START <this will never run during testing due to the error stop>
  !> Workflow that is executed by default when
  !! an exception is raised. The argument <tt>message</tt>
  !! is printed to the console and program execution
  !! is terminated.
  subroutine on_exception_raised(msg)
    use mod_painting, only: paint_string
    !> message to print to the console when exception is raised
    character(len=*), intent(in) :: msg

    write(*, *) paint_string(" ERROR   | " // msg, "red")
    error stop
  end subroutine on_exception_raised
  ! LCOV_EXCL_STOP
end module mod_exceptions
