!! Three-dimensional reflection calculations for acoustic boundaries
MODULE Reflect3DMod
  !! 3D ray reflection computations at boundaries with complex geometry and loss calculations

  USE bellhopMod
  USE RayNormals
  USE cross_products
  USE, INTRINSIC :: ISO_FORTRAN_ENV, ONLY: ERROR_UNIT

  IMPLICIT NONE
  PUBLIC

CONTAINS

  SUBROUTINE Reflect3D( is, HS, BotTop, nBdry, z_xx, z_xy, z_yy, kappa_xx, kappa_xy, kappa_yy, RefC, Npts )

    ! *** If reflecting off surface need jump conditions ***

    USE RefCoef
    USE sspMod

    INTEGER,           INTENT( INOUT ) :: is                          ! index of the ray step
    TYPE( HSInfo ),    INTENT( IN    ) :: HS                          ! halfspace parameters
    CHARACTER (LEN=3), INTENT( IN    ) :: BotTop                      ! bottom or top flag
    REAL    (KIND=8),  INTENT( INOUT ) :: nBdry( 3 )                  ! normal to the boundary (changes if cone reflection)
    REAL    (KIND=8),  INTENT( IN    ) :: z_xx, z_xy, z_yy, kappa_xx, kappa_xy, kappa_yy
    TYPE(ReflectionCoef), INTENT( IN ) :: RefC( NPts )                ! reflection coefficient
    INTEGER,           INTENT( IN    ) :: Npts                        ! Number of points in the reflection coefficient
    INTEGER          :: is1
    REAL    (KIND=8) :: rayt( 3 ), rayn1( 3 ), rayn2( 3 )             ! unit ray tangent and normals
    REAL    (KIND=8) :: rayt_tilde( 3 ), rayn1_tilde( 3 ), rayn2_tilde( 3 ), cn1jump, cn2jump, csjump
    REAL    (KIND=8) :: c, cimag, gradc( 3 ), cxx, cyy, czz, cxy, cxz, cyz, rho
        ! derivatives of sound speed in cartesian coordinates
    REAL    (KIND=8) :: RM, R1, R2, R3, Tg, Th                        ! curvature corrections on reflection
    COMPLEX (KIND=8) :: gamma1, gamma2, gamma1Sq, gamma2Sq, GK, Refl
    TYPE(ReflectionCoef) :: RInt
    REAL    (KIND=8) :: tBdry( 3 )                                    ! tangent to the boundary
    REAL    (KIND=8) :: e1( 3 ), e2( 3 )                              ! ray normals for ray-centered coordinates
    REAL    (KIND=8) :: p_tilde_in(  2 ), p_hat_in(  2 ), q_tilde_in(  2 ), q_hat_in(  2 ), p_tilde_out( 2 ), p_hat_out( 2 )
    REAL    (KIND=8) :: t_rot( 2 ), n_rot( 2 )
    REAL    (KIND=8) :: RotMat( 2, 2 ), kappaMat( 2, 2 ), DMat( 2, 2 ), DMatTemp( 2, 2 )

    is  = is + 1
    is1 = is + 1

    ! analytic formulas for the curvature of the seamount:
    !CALL ConeFormulas(    z_xx, z_xy, z_yy, nBdry, xs, ray3D( is )%x, BotTop )
    ! analytic formulas for the curvature of the parabolic bottom:
    !CALL ParabotFormulas( z_xx, z_xy, z_yy, nBdry )
    !write( *, * ) 'z_xx, z_xy, z_yy', z_xx, z_xy, z_yy, 'nBdry', nBdry
    kappaMat( 1, 1 ) = z_xx / 2
    kappaMat( 1, 2 ) = z_xy / 2
    kappaMat( 2, 1 ) = z_xy / 2
    kappaMat( 2, 2 ) = z_yy / 2

    ! get normal and tangential components of ray in the reflecting plane

    Th = DOT_PRODUCT( ray3D( is )%t, nBdry )  ! component of ray tangent normal to boundary

    tBdry = ray3D( is )%t - Th * nBdry        ! component of ray tangent along the boundary, in the reflection plane
    tBdry = tBdry / NORM2( tBdry )

    Tg = DOT_PRODUCT( ray3D( is )%t, tBdry )  ! component of ray tangent along the boundary

    ray3D( is1 )%NumTopBnc = ray3D( is )%NumTopBnc
    ray3D( is1 )%NumBotBnc = ray3D( is )%NumBotBnc
    ray3D( is1 )%x         = ray3D( is )%x
    ray3D( is1 )%t         = ray3D( is )%t - 2.0 * Th * nBdry   ! reflect the ray

    CALL EvaluateSSP3D( ray3D( is1 )%x, ray3D( is1 )%t, c, cimag, gradc, cxx, cyy, czz, cxy, cxz, cyz, rho, freq, 'TAB' )
    ray3D( is1 )%c         = c
    ray3D( is1 )%tau       = ray3D( is )%tau

    ! Calculate the ray normals, rayn1, rayn2, and a unit tangent

    CALL CalcTangent_Normals( ray3D( is  )%t, nBdry, rayt,       rayn1,       rayn2       ) ! incident
    CALL CalcTangent_Normals( ray3D( is1 )%t, nBdry, rayt_tilde, rayn1_tilde, rayn2_tilde ) ! reflected

    ! WRITE( PRTFile, * ) 'point0', rayt, rayn1, rayn2
    ! WRITE( PRTFile, * ) 'point1', rayt_tilde, rayn1_tilde, rayn2_tilde

    ! rotation matrix to get surface curvature in and perpendicular to the reflection plane
    ! we use only the first two elements of the vectors because we want the projection in the x-y plane
    t_rot = rayt(  1 : 2 ) / NORM2( rayt(  1 : 2 ) )
    n_rot = rayn2( 1 : 2 ) / NORM2( rayn2( 1 : 2 ) )

    RotMat( 1 : 2, 1 ) = t_rot
    RotMat( 1 : 2, 2 ) = n_rot

    ! apply the rotation to get the matrix D of curvatures (see Popov 1977 for definition of DMat)
    ! DMat = RotMat^T * kappaMat * RotMat, with RotMat anti-symmetric
    DMatTemp = MATMUL( 2 * kappaMat, RotMat )
    DMat     = MATMUL( TRANSPOSE( RotMat ), DMatTemp )
    ! WRITE( PRTFile, * ) 'DMat', DMat

    ! normal and tangential derivatives of the sound speed
    cn1jump =  DOT_PRODUCT( gradc, -rayn1_tilde - rayn1 )
    cn2jump =  DOT_PRODUCT( gradc, -rayn2_tilde - rayn2 )
    csjump  = -DOT_PRODUCT( gradc,  rayt_tilde  - rayt  )
    ! WRITE( PRTFile, * ) 'cn1jump cn2jump csjump', cn1jump, cn2jump, csjump

!!! not sure if cn2 needs a sign flip also
!!$  IF ( BotTop == 'TOP' ) THEN
!!$     cn1jump = -cn1jump    ! flip sign for top reflection
!!$     cn2jump = -cn2jump    ! flip sign for top reflection
!!$  END IF

    RM = Tg / Th   ! this is tan( alpha ) where alpha is the angle of incidence
    CALL CurvatureCorrection2( ray3D( is ), ray3D( is1 ) )

    ! account for phase change

    SELECT CASE ( HS%BC )
    CASE ( 'R' )                 ! rigid
       ray3D( is1 )%Amp   = ray3D( is )%Amp
       ray3D( is1 )%Phase = ray3D( is )%Phase
    CASE ( 'V' )                 ! vacuum
       ray3D( is1 )%Amp   = ray3D( is )%Amp
       ray3D( is1 )%Phase = ray3D( is )%Phase + pi
    CASE ( 'F' )                 ! file
       RInt%theta = RadDeg * ABS( ATAN2( Th, Tg ) )           ! angle of incidence (relative to normal to bathymetry)
       IF ( RInt%theta > 90 ) RInt%theta = 180. - RInt%theta  ! reflection coefficient is symmetric about 90 degrees
       CALL InterpolateReflectionCoefficient( RInt, RefC, Npts, PRTFile )
       ray3D( is1 )%Amp   = ray3D( is )%Amp * RInt%R
       ray3D( is1 )%Phase = ray3D( is )%Phase + RInt%phi
    CASE ( 'A', 'G' )            ! half-space
       GK       = omega * Tg     ! wavenumber in direction parallel to bathymetry
       gamma1Sq = ( omega / c     ) ** 2 - GK ** 2 - i * tiny( omega )   ! tiny prevents g95 giving -zero, and wrong branch cut
       gamma2Sq = ( omega / HS%cP ) ** 2 - GK ** 2 - i * tiny( omega )
       gamma1   = SQRT( -gamma1Sq )
       gamma2   = SQRT( -gamma2Sq )

       Refl = ( HS%rho * gamma1 - gamma2 ) / ( HS%rho * gamma1 + gamma2 )
       !write( *, * ) abs( Refl ), c, HS%cp, rho, HS%rho
       ! LP: Removed this code as it was obviously modified to do nothing
       ! ( ABS( anything ) < 0 is always false ), and users would likely be
       ! unpleasantly surprised if it did do what it was intended to do.
       ! Hack to make a wall (where the bottom slope is more than 80 degrees) be a perfect reflector
       !!!!!!!!!!!
       !IF ( ABS( RadDeg * ATAN2( nBdry( 3 ), NORM2( nBdry( 1 : 2 ) ) ) ) < 0 ) THEN   ! was 60 degrees
       !   Refl = 1
       !END IF

       IF ( ABS( Refl ) < 1.0E-5 ) THEN   ! kill a ray that has lost its energy in reflection
          ray3D( is1 )%Amp   = 0.0
          ray3D( is1 )%Phase = ray3D( is )%Phase
       ELSE
          ray3D( is1 )%Amp   = ABS( Refl ) * ray3D(  is )%Amp
          ray3D( is1 )%Phase = ray3D( is )%Phase + ATAN2( AIMAG( Refl ), REAL( Refl ) )
       END IF
    CASE DEFAULT
       WRITE( ERROR_UNIT, * ) 'Reflect3D: Unknown boundary condition type: ', HS%BC
       CALL ERROUT( 'Reflect3D', 'Unknown boundary condition type' )
    END SELECT

  CONTAINS
    SUBROUTINE CurvatureCorrection2( ray, rayOut )

      TYPE( ray3DPt ), INTENT( IN  ) :: ray
      TYPE( ray3DPt ), INTENT( OUT ) :: rayOut

      ! Note that Tg, Th need to be multiplied by c to normalize tangent; hence, c^2 below
      ! added the SIGN in R2 to make ati and bty have a symmetric effect on the beam
      ! not clear why that's needed

      R1 = 2 / c ** 2 * DMat( 1, 1 ) / Th + RM * ( 2 * cn1jump - RM * csjump ) / c ** 2
      R2 = 2 / c *      DMat( 1, 2 ) * SIGN( 1.0D0, -Th )      + RM * cn2jump  / c ** 2
      R3 = 2 *          DMat( 2, 2 ) * Th
      ! WRITE( PRTFile, * ) 'rmat', R1, R2, R2, R3

      ! z-component of unit tangent is sin( theta ); we want cos( theta )
      !R1 = R1 * ( 1 - ( ray%c * ray%t( 3 ) ) ** 2 )
      ! write( *, * )  1 - ( ray%c * ray%t( 3 ) ) ** 2

      ! *** curvature correction ***

      CALL RayNormal( ray%t, ray%phi, ray%c, e1, e2 )  ! Compute ray normals e1 and e2

      IF ( R1 == 0.0D0 .AND. R2 == 0.0D0 .AND. R3 == 0.0D0 ) THEN

         ! LP: There is no curvature change, but rotating p forward and back
         ! can change it slightly due to floating-point imprecision, leading to
         ! long-term divergence.
         rayOut%p_tilde = ray%p_tilde
         rayOut%p_hat   = ray%p_hat

      ELSE

         ! rotate p-q from e1, e2 system, onto rayn1, rayn2 system

         RotMat( 1, 1 ) = DOT_PRODUCT( rayn1, e1 )
         RotMat( 1, 2 ) = DOT_PRODUCT( rayn1, e2 )
         RotMat( 2, 1 ) = -RotMat( 1, 2 )             ! same as DOT_PRODUCT( rayn2, e1 )
         RotMat( 2, 2 ) = DOT_PRODUCT( rayn2, e2 )

         p_tilde_in = RotMat( 1, 1 ) * ray%p_tilde + RotMat( 1, 2 ) * ray%p_hat
         p_hat_in   = RotMat( 2, 1 ) * ray%p_tilde + RotMat( 2, 2 ) * ray%p_hat

         q_tilde_in = RotMat( 1, 1 ) * ray%q_tilde + RotMat( 1, 2 ) * ray%q_hat
         q_hat_in   = RotMat( 2, 1 ) * ray%q_tilde + RotMat( 2, 2 ) * ray%q_hat

         ! here's the actual curvature change

         p_tilde_out = p_tilde_in + q_tilde_in * R1 - q_hat_in * R2
         p_hat_out   = p_hat_in   + q_tilde_in * R2 + q_hat_in * R3
         !p_hat_out   = p_hat_in   + q_tilde_in * R2 - q_hat_in * R3 ! this one good

         ! rotate p-q back to e1, e2 system (RotMat^(-1) = RotMat^T)

         rayOut%p_tilde = RotMat( 1, 1 ) * p_tilde_out + RotMat( 2, 1 ) * p_hat_out
         rayOut%p_hat   = RotMat( 1, 2 ) * p_tilde_out + RotMat( 2, 2 ) * p_hat_out

      END IF

      rayOut%q_tilde = ray%q_tilde
      rayOut%q_hat   = ray%q_hat

      !write( *, * ) 'p', ray%p_tilde, ray%p_hat, rayOut%p_tilde, rayOut%p_hat
      !write( *, * ) 'q', ray%q_tilde, ray%q_hat, rayOut%q_tilde, rayOut%q_hat

      ! Logic below fixes a bug when the |dot product| is infinitesimally greater than 1 (then ACos is complex)
      rayOut%phi = ray%phi + 2 * ACOS( MAX( MIN( DOT_PRODUCT( rayn1, e1 ), 1.0D0 ), -1.0D0 ) ) !!!What happens to torsion?
      ! WRITE( PRTFile, * ) 'dot phi', DOT_PRODUCT( rayn1, e1 ), rayOut%phi
      !write( *, * ) rayn1, e1
      ! write( *, * ) DOT_PRODUCT( rayn1, e1 ), ACOS( MAX( MIN( DOT_PRODUCT( rayn1, e1 ), 1.0D0 ), -1.0D0 ) )
      ! f, g, h continuation; needs curvature corrections
      ! ray3D( is1 )%f    = ray3D( is )%f ! + ray3D( is )%DetQ * R1
      ! ray3D( is1 )%g    = ray3D( is )%g ! + ray3D( is )%DetQ * R1
      ! ray3D( is1 )%h    = ray3D( is )%h ! + ray3D( is )%DetQ * R2
      ! ray3D( is1 )%DetP = ray3D( is )%DetP
      ! ray3D( is1 )%DetQ = ray3D( is )%DetQ

    END SUBROUTINE CurvatureCorrection2

    ! ********************************************************************** !

    SUBROUTINE CalcTangent_Normals( ray3Dt, nBdry, rayt, rayn1, rayn2 )

      ! given the ray tangent vector (not normalized) and the unit normal to the bdry
      ! calculate a unit tangent and the two ray normals

      REAL (KIND=8), INTENT( IN )  :: ray3Dt( 3 ), nBdry( 3 )
      REAL (KIND=8), INTENT( OUT ) :: rayt( 3 ), rayn1( 3 ), rayn2( 3 )

      ! incident unit ray tangent and normal
      rayt  = c * ray3Dt                      ! unit tangent to ray
      rayn2 = -cross_product( rayt, nBdry )   ! ray tangent x boundary normal gives refl. plane normal
      rayn2 = rayn2 / NORM2( rayn2 )          ! unit normal to refl. plane
      rayn1 = -cross_product( rayt, rayn2 )   ! ray tangent x refl. plane normal is first ray normal

    END SUBROUTINE CalcTangent_Normals

    ! ********************************************************************** !

    SUBROUTINE ParabotFormulas( z_xx, z_xy, z_yy, nBdry )

      ! analytic formula for the parabolic bottom

      REAL (KIND=8), INTENT( OUT ) :: z_xx, z_xy, z_yy, nBdry( 3 )
      REAL (KIND=8) :: Radius, x, y, z, z_r, z_rr, z_x, z_y, a, b, c, RLen

      IF ( BotTop == 'BOT' ) THEN
         a = 1 / 1000.
         b = 0
         c = 250

         x = ray3D( is )%x( 1 )
         y = ray3D( is )%x( 2 )

         Radius = SQRT( x ** 2 + y ** 2 )   ! radius of seamount at the bounce point

         z = 500.0 * SQRT( 1 + Radius / c )

         z_r = 1 / ( 2 * a * z + b )
         z_x = z_r * x / Radius
         z_y = z_r * y / Radius

         nBdry = [ -z_x, -z_y, 1.D0 ]       !     normal to bdry (outward pointing)
         RLen = NORM2( nBdry )
         nBdry = nBdry / Rlen   ! unit normal to bdry
         ! curvatures

         ! z = z_xx / 2 * x^2 + z_xy * xy + z_yy * y^2 ! coefs are the kappa matrix
         ! RLen is an extra factor based on Eq. 4.4.18 in Cerveny's book
         ! It represents a length along the tangent for a delta x step

         z_rr = -1 / ( 4 * a * a * z * z * z )
         z_xx = ( z_rr * x * x / Radius ** 2 + z_r * y * y / Radius ** 3 ) / RLen
         z_xy = ( z_rr * x * y / Radius ** 2 - z_r * x * y / Radius ** 3 ) / RLen
         z_yy = ( z_rr * y * y / Radius ** 2 + z_r * x * x / Radius ** 3 ) / RLen

         ! write( *, * ) 'x=', x, 'y=', y, 'z_x', z_x, 'z_y', z_y, 'z_r', z_r, 'zrr', z_rr, 'Radius', Radius, 'RLen', RLen
      END IF
    END SUBROUTINE ParabotFormulas
  END SUBROUTINE Reflect3D
END MODULE Reflect3DMod
