!  ┏┓┏┓┏┓  Licensed under the MIT License
!  ┃ ┗┓┣   Copyright (c) 2025 Rodrigo Castro 
!  ┗┛┗┛┻   https://github.com/rodpcastro/colspecf

module csf_exponential_integral
!* # Exponential integral
! Exponential integrals.
!
! Procedures:
!
! - `ei`: Exponential integral \(\mathrm{Ei}(x)\)
! - `e1`: Exponential integral \(\mathrm{E}_1(x)\) or \(\mathrm{E}_1(z)\)
!
! Untested procedures:  
!
! - `enz`: Exponential integral \(\mathrm{E}_n(z)\), untested for \(n \neq 1\)
!
! ## References
! 1. Kathleen A. Paciorek. 1970. Algorithm 385: Exponential integral Ei(x). Commun.
!    ACM 13, 7 (July 1970), 446–447. <https://doi.org/10.1145/362686.362696>
! 2. Donald E. Amos. 1990. Algorithms 683: a portable FORTRAN subroutine for
!    exponential integrals of a complex argument. ACM Trans. Math. Softw. 16,
!*   2 (June 1990), 178–182. <https://doi.org/10.1145/78928.78934>

  use, intrinsic :: iso_fortran_env, only: stderr => error_unit
  use csf_kinds, only: i4, wp
  use csf_constants, only: pi, ninf, pinf
  use csf_numerror, only: eps_wp
  use calgo_385, only: dei
  use calgo_683, only: cexint

  implicit none
  private
  public :: ei, e1, enz

  interface e1
    !! Exponential integral \(\mathrm{E}_1(x)\) or \(\mathrm{E}_1(z)\).
    module procedure e1x, e1z
  end interface e1

contains

  real(wp) function ei(x)
    !! Exponential integral \(\mathrm{Ei}(x)\).
    !
    !! \(\lbrace x \in \mathbb{R} \mid x \neq 0 \rbrace\)

    real(wp), intent(in) :: x

    if (x == 0.0_wp) then
      ei = ninf()
    else if (x < -738.0_wp) then
      ei = 0.0_wp
    else if (x <= 709.0_wp) then
      ei = dei(x)
    else
      ei = pinf()
    end if
  end function ei


  real(wp) function e1x(x)
    !! Exponential integral \(\mathrm{E}_1(x)\).
    !
    !! \(\lbrace x \in \mathbb{R} \mid x \neq 0 \rbrace\)

    real(wp), intent(in) :: x
    complex(wp) :: z, e1z

    if (x == 0.0_wp) then
      e1x = pinf()
    else if (x <= 738.0_wp) then
      z = cmplx(x, 0.0_wp, kind=wp)
      e1z = enz(1, z, .false.)
      e1x = e1z%re
    else
      e1x = 0.0_wp
    end if
  end function e1x


  complex(wp) function e1z(z)
    !! Exponential integral \(\mathrm{E}_1(z)\).
    !
    !! \(z \in \mathbb{C} \setminus \left( \lbrace z \in \mathbb{C} \mid \Re(z) \lt 0,
    !! \thinspace 0 \lt |\Im(z)| \lt 10^{-6} \rbrace \cup \lbrace 0 \rbrace \right)\)

    complex(wp), intent(in) :: z
    real(wp) :: zabs

    zabs = abs(z)

    if (zabs == 0.0_wp) then
      e1z = cmplx(pinf(), -pi, kind=wp)
      return
    else if (z%re < -700.0_wp .and. z%im == 0.0_wp) then
      e1z = cmplx(ninf(), -pi, kind=wp)
      return
    else if (z%re > 738.0_wp .or. (z%re >= 0 .and. abs(z%im) > 1.0e15_wp)) then
      ! For Re(z) ≥ 0 and Im(z) > 1e15, CALGO 683 throws IERR = 5.
      e1z = (0.0_wp, 0.0_wp)
      return
    else
      e1z = enz(1, z, .false.)
    end if

    if (z%re < 0.0_wp .and. z%im == 0.0_wp) then
      e1z = cmplx(e1z%re, -pi, kind=wp)
    end if
  end function e1z


  complex(wp) function enz(n, z, show_warning)
    !* Exponential integral \(\mathrm{E}_n(z)\).
    ! 
    ! \(n \geq 1,\thinspace z \in \mathbb{C},\thinspace -\pi \lt \arg(z) \leq \pi \)
    !
    ! If `show_warning = .true.`, a warning message is displayed when `cexint` returns
    !* `IERR = 2` or `IERR = 4`.

    integer(i4), intent(in) :: n
    complex(wp), intent(in) :: z
    logical, intent(in), optional :: show_warning  !! Default=`.true.`
    real(wp), parameter :: tol = eps_wp
    integer(i4), parameter :: m = 1
    complex(wp) :: cy(m)
    integer(i4) :: ierr
    logical :: show_warning_

    show_warning_ = .true.
    
    if (present(show_warning)) then
      show_warning_ = show_warning
    end if

    ! Computing En(z) with no scaling (KODE = 1).
    call cexint(z, n, 1, tol, m, cy, ierr)
    enz = cy(1)

    select case (ierr)
      case (1)
        error stop 'CALGO 683 IERR = 1: An input error. No computation.'
      case (2)
        ! Computing E1(z) with scaling (KODE = 2).
        call cexint(z, n, 1, tol, m, cy, ierr)
        enz = exp(-z) * cy(1)

        if (show_warning_) then
          write(stderr, '(a)') 'CALGO 683 IERR = 2: Underflow. ' // &
                               'En(z) = (0.0, 0.0). Real(z) > 0.0 ' // &
                               'too large on KODE = 1. Recomputed with KODE = 2.'
        end if
      case (3)
        error stop 'CALGO 683 IERR = 3: Overflow. No computation. ' // &
                   'Real(z) < 0.0 too small on KODE = 1.'
      case (4)
        if (show_warning_) then
          write(stderr, '(a)') 'CALGO 683 IERR = 4: |z| or n large. '  // &
                               'Computation done but losses of significance by ' // &
                               'argument reduction may exceed half precision.'
        end if
      case (5)
        error stop 'CALGO 683 IERR = 5: |z| or n large. No computation. ' // &
                   'All loss of significance by argument reduction has occurred.'
      case (6)
        error stop 'CALGO 683 IERR = 6: Convergence error. No computation. ' // &
                   'Algorithm termination condition not met.'
      case (7)
        error stop 'CALGO 683 IERR = 7: Discrimination error. No computation. ' // &
                   'This condition should never occur.'
    end select
  end function enz

end module csf_exponential_integral
