!============================================================================
!
! Routines:
!
! (1) spinor_symmetries     Originally by BAB          Last Modified: 4/01/2014 (BAB)
!
!     Find the 2x2 matrix to rotate spinor components of wavefunctions,
!     given the integer rotation matrix in lattice coordinates.
!     This function must be called in all genwf files for spinor calculations.
!
!============================================================================

#include "f_defs.h"

module spinor_symmetries_m

  use global_m
  use misc_m

  implicit none

  private

  public :: spinor_symmetries, rot_axis_angle

contains

  subroutine spinor_symmetries(bvec, mtrx, umtrx, itqq)

    real(DP), intent(in) :: bvec(:,:) !< crystal%bvec (3, 3)
    integer, intent(in) :: mtrx(:,:)   !< rotation matrices (3, 3)
    complex(DPC), allocatable, intent(out) :: umtrx(:,:)   !< spinor rotation matrix (2, 2)
    integer, intent(in) :: itqq  !< symmetry index

    real(DP), allocatable :: axis(:)   !< rotation axis (3)
    real(DP) :: angle, cosa, sina, bvecinv(3,3)
    real(DP) :: mtrxtemp(3,3),det

    PUSH_SUB(spinor_symmetries)

    ! NOTE: mtrx usually has a third index, indicating symmetry type

    ! Check if identity
    SAFE_ALLOCATE(umtrx,(2,2))

    if (itqq.eq.1) then
      umtrx(1,1) = (1.0d0,0.0d0)
      umtrx(1,2) = (0.0d0,0.0d0)
      umtrx(2,1) = (0.0d0,0.0d0)
      umtrx(2,2) = (1.0d0,0.0d0)
      return
    endif

    ! calculate determinant of matrix mtrx

    ! for umtrx, improper rotations should be turned into proper rotations
    ! mtrxtemp = TRANSPOSE(mtrx)
    mtrxtemp = mtrx
    call compute_det(mtrxtemp,det)
    if (det.lt.TOL_Zero) then
      mtrxtemp = -mtrxtemp
    endif

    ! convert rotation matrix for lattice vectors to cartesian coordinates
    ! R_cart =  B_Transpose * R * (B_Transpose)_Inverse
    ! Note: bvec is already transposed

    call invert_matrix(bvec, bvecinv)

    mtrxtemp = MATMUL(bvec,(MATMUL(mtrxtemp,bvecinv)))

    ! get rotation axis and rotation angle

    SAFE_ALLOCATE(axis,(3))

    call rot_axis_angle(axis,angle,mtrxtemp)

    angle = 0.5*angle
    cosa = COS(angle)
    sina = SIN(angle)

    ! Note: this is actually the transpose of the usual U matrix, for use with genwf
    umtrx(1,1) = CMPLX(cosa,-axis(3)*sina)
    umtrx(2,1) = CMPLX(-axis(2)*sina,-axis(1)*sina)
    umtrx(1,2) = -conjg(umtrx(2,1))
    umtrx(2,2) = conjg(umtrx(1,1))

    POP_SUB(spinor_symmetries)
    return

  end subroutine spinor_symmetries

!===============================================================================

  !> Given a 3x3 orthogonal matrix, this routine gives the rotation axis and angle
  !!
  !! This algorithm is based on the publication
  !! F. Landis Markley, Journal of Guidance, Control, and Dynamics 31 (2008), p. 440

  subroutine rot_axis_angle(axis, angle, mtrx)

    real(DP), intent(in) :: mtrx(:,:)   !< rotation matrices (3, 3)
    real(DP), intent(out) :: axis(:) !< rotation axis (3)
    real(DP), intent(out) :: angle

    integer :: ind, ii, jj, indmax, cyclic(3)
    real(DP) :: tr, vecnorm, maxnorm, quat(4), xquat(4,4), normx(4)

    PUSH_SUB(rot_axis_angle)

    quat(:) = 0.0d0
    ! Ordering for quaternion:
    ! (axis(1) sin(angle/2), axis(2) sin(angle/2), axis(3) sin(angle/2), cos(angle/2))
    tr = 0.0d0
    do ind=1,3
      tr = tr + mtrx(ind,ind)
    enddo

    cyclic(1) = 2
    cyclic(2) = 3
    cyclic(3) = 1

    do ii=1,4
      do jj=1,4

        if (ii.eq.jj .and. jj.lt.4) then
          xquat(ii,jj) = 1 + 2*mtrx(ii,jj) - tr

        else if (ii.ne.jj .and. ii.lt.4 .and. jj.lt.4) then
          xquat(ii,jj) = mtrx(jj,ii) + mtrx(ii,jj)

        else if (ii.eq.jj .and. jj.eq.4) then
          xquat(ii,jj) = 1 + tr

        else if (ii.ne.jj .and. jj.eq.4) then
          xquat(ii,jj) = mtrx(6-cyclic(ii)-ii,cyclic(ii)) - mtrx(cyclic(ii),6-cyclic(ii)-ii)

        endif
      enddo
    enddo 

    do ii=1,4
      do jj=ii+1,4
        xquat(jj,ii) = xquat(ii,jj);
      enddo 
    enddo

    normx(:) = 0.0d0

    do ii=1,4
      do jj=1,4
        normx(ii) = normx(ii) + xquat(ii,jj)**2;
      enddo
      normx(ii) = sqrt(normx(ii));
    enddo

    ! Note that maxnorm is guaranteed to be at least 1.0d0
    maxnorm = 0.0d0
    indmax = 0
    do ii=1,4
      if (normx(ii) .gt. maxnorm) then
        maxnorm = normx(ii)
        indmax = ii
      endif
    enddo

    quat(:) = xquat(:,indmax)/maxnorm

    vecnorm = 0.0d0
    do ii=1,3
      vecnorm = vecnorm + quat(ii)**2
    enddo
    vecnorm = sqrt(vecnorm)

    angle = 2*atan2(vecnorm,quat(4))

    if (vecnorm.eq.0) then
      ! probably does not happen since unity matrix is treated separately
      axis(1) = 0.0d0
      axis(2) = 0.0d0
      axis(3) = 1.0d0
    else
      axis(:) = quat(1:3)/vecnorm
    endif

    POP_SUB(rot_axis_angle)
    return

  end subroutine rot_axis_angle

!===============================================================================

end module spinor_symmetries_m
