module m_common_error

#ifndef DUMMYLIB
  use fox_m_fsys_abort_flush, only: pxfabort, pxfflush
  use fox_m_fsys_array_str, only: vs_str_alloc

  implicit none
  private

  integer, parameter :: ERR_NULL = 0
  integer, parameter :: ERR_WARNING = 1
  integer, parameter :: ERR_ERROR = 2
  integer, parameter :: ERR_FATAL = 3
#endif  
  logical, save :: errors_are_fatal = .false.
  logical, save :: warnings_are_fatal = .false.

#ifndef DUMMYLIB
  type error_t
    integer :: severity = ERR_NULL
    integer :: error_code = 0
    character, dimension(:), pointer :: msg => null()
  end type error_t

  type error_stack
    type(error_t), dimension(:), pointer :: stack => null()
  end type error_stack

  interface FoX_warning
    module procedure FoX_warning_base
  end interface

  interface FoX_error
    module procedure FoX_error_base
  end interface

  interface FoX_fatal
    module procedure FoX_fatal_base
  end interface

  public :: ERR_NULL
  public :: ERR_WARNING
  public :: ERR_ERROR
  public :: ERR_FATAL

  public :: error_t
  public :: error_stack

  public :: init_error_stack
  public :: destroy_error_stack

  public :: FoX_warning
  public :: FoX_error
  public :: FoX_fatal

  public :: FoX_warning_base
  public :: FoX_error_base
  public :: FoX_fatal_base

  public :: add_error
  public :: in_error

#endif
  public :: FoX_set_fatal_errors
  public :: FoX_get_fatal_errors
  public :: FoX_set_fatal_warnings
  public :: FoX_get_fatal_warnings

contains
#ifndef DUMMYLIB

  subroutine FoX_warning_base(msg)
    ! Emit warning, but carry on.
    character(len=*), intent(in) :: msg

    if (warnings_are_fatal) then
        write(0,'(a)') 'FoX warning  made fatal'
        call FoX_fatal_base(msg)
    endif
#if !defined(SUPPRESS_WARNINGS)
    write(0,'(a)') 'WARNING(FoX)'
    write(0,'(a)')  msg
    call pxfflush(0)
#endif

  end subroutine FoX_warning_base


  subroutine FoX_error_base(msg)
    ! Emit error message and stop.
    ! No clean up is done here, but this can
    ! be overridden to include clean-up routines
    character(len=*), intent(in) :: msg

    if (errors_are_fatal) then
        write(0,'(a)') 'FoX error made fatal'
        call FoX_fatal_base(msg)
    endif

    write(0,'(a)') 'ERROR(FoX)'
    write(0,'(a)')  msg
    call pxfflush(0)

    stop

  end subroutine FoX_error_base

  subroutine FoX_fatal_base(msg)
    !Emit error message and abort with coredump.
    !No clean-up occurs

    character(len=*), intent(in) :: msg

    write(0,'(a)') 'ABORT(FOX)'
    write(0,'(a)')  msg
    call pxfflush(0)

    call pxfabort()

  end subroutine FoX_fatal_base


  subroutine init_error_stack(stack)
    type(error_stack), intent(inout) :: stack
    
    allocate(stack%stack(0))
  end subroutine init_error_stack


  subroutine destroy_error_stack(stack)
    type(error_stack), intent(inout) :: stack
    
    integer :: i

    do i = 1, size(stack%stack)
      deallocate(stack%stack(i)%msg)
    enddo
    deallocate(stack%stack)
  end subroutine destroy_error_stack


  subroutine add_error(stack, msg, severity, error_code)
    type(error_stack), intent(inout) :: stack
    character(len=*), intent(in) :: msg
    integer, intent(in), optional :: severity
    integer, intent(in), optional :: error_code

    integer :: i, n
    type(error_t), dimension(:), pointer :: temp_stack 

    if (.not.associated(stack%stack)) &
      call init_error_stack(stack)

    n = size(stack%stack)
    
    temp_stack => stack%stack
    allocate(stack%stack(n+1))
    do i = 1, size(temp_stack)
      stack%stack(i)%msg => temp_stack(i)%msg
      stack%stack(i)%severity = temp_stack(i)%severity
      stack%stack(i)%error_code = temp_stack(i)%error_code
    enddo
    deallocate(temp_stack)

    stack%stack(n+1)%msg => vs_str_alloc(msg)
    if (present(severity)) then
      stack%stack(n+1)%severity = severity
    else
      stack%stack(n+1)%severity = ERR_ERROR
    endif
    if (present(error_code)) then
      stack%stack(n+1)%error_code = error_code
    else
      stack%stack(n+1)%error_code = -1
    endif

  end subroutine add_error


  function in_error(stack) result(p)
    type(error_stack), intent(in) :: stack
    logical :: p

    if (associated(stack%stack)) then
      p = (size(stack%stack) > 0)
    else
      p = .false.
    endif
  end function in_error

#endif
  subroutine FoX_set_fatal_errors(newvalue)
    logical, intent(in) :: newvalue
    errors_are_fatal = newvalue
  end subroutine FoX_set_fatal_errors

  function  FoX_get_fatal_errors()
      logical :: FoX_get_fatal_errors
      FoX_get_fatal_errors = errors_are_fatal
  end function FoX_get_fatal_errors

  subroutine  FoX_set_fatal_warnings(newvalue)
    logical, intent(in) :: newvalue
    warnings_are_fatal = newvalue
  end subroutine FoX_set_fatal_warnings

  function FoX_get_fatal_warnings()
    logical :: FoX_get_fatal_warnings
    FoX_get_fatal_warnings = warnings_are_fatal
  end function FoX_get_fatal_warnings

end module m_common_error
    