module functions

    use parameters

    implicit none

    public
    contains


    ! Functions below this comment are taken from the functions module of atomsk
    !********************************************************
    !  STRUPCASE
    !  This function reads a string of any length
    !  and capitalizes all letters.
    !********************************************************
    FUNCTION StrUpCase (input_string) RESULT (UC_string)
    !
    IMPLICIT NONE
    CHARACTER(*),PARAMETER:: lower_case = 'abcdefghijklmnopqrstuvwxyz'
    CHARACTER(*),PARAMETER:: upper_case = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    CHARACTER(*),INTENT(IN):: input_string
    CHARACTER(LEN(Input_String)):: UC_string !Upper-Case string that is produced
    INTEGER:: i, n
    !
    IF(LEN(input_string)==0) RETURN
    UC_string = input_string
    ! Loop over string elements
    DO i=1,LEN(UC_string)
      !Find location of letter in lower case constant string
      n = INDEX( lower_case, UC_string(i:i) )
      !If current substring is a lower case letter, make it upper case
      IF(n>0) THEN
        UC_string(i:i) = upper_case(n:n)
      ENDIF
    END DO
    !
    END FUNCTION StrUpCase

    !********************************************************
    !  STRDNCASE
    !  This function reads a string of any length
    !  and transforms all letters to lower case.
    !********************************************************
    FUNCTION StrDnCase (input_string) RESULT (lc_string)
    !
    IMPLICIT NONE
    CHARACTER(*),PARAMETER:: lower_case = 'abcdefghijklmnopqrstuvwxyz'
    CHARACTER(*),PARAMETER:: upper_case = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    CHARACTER(*),INTENT(IN):: input_string
    CHARACTER(LEN(Input_String)):: lc_string !Lower-Case string that is produced
    INTEGER:: i, n
    !
    IF(LEN(input_string)==0) RETURN
    lc_string = input_string
    ! Loop over string elements
    DO i=1,LEN(lc_string)
      !Find location of letter in upper case constant string
      n = INDEX( upper_case, lc_string(i:i) )
      !If current substring is an upper case letter, make it lower case
      IF(n>0) THEN
        lc_string(i:i) = lower_case(n:n)
      ENDIF
    END DO
!
END FUNCTION StrDnCase

    pure function matrix_normal(a, n)

        integer :: i
        integer, intent(in) :: n
        real(kind = dp), dimension(n) :: v
        real(kind = dp), dimension(n, n),intent(in) :: a
        real(kind = dp), dimension(n,n) :: matrix_normal

        matrix_normal(:, :) = a(:, :)

        do i = 1, n

            v(:) = a(i,:)

            matrix_normal(i, :) = v(:) / norm2(v)

        end do

        return
    end function matrix_normal

    pure function cross_product(a, b)
        !Function which finds the cross product of two vectors

        real(kind = dp), dimension(3), intent(in) :: a, b
        real(kind = dp), dimension(3) :: cross_product

        cross_product(1) = a(2) * b(3) - a(3) * b(2)
        cross_product(2) = a(3) * b(1) - a(1) * b(3)
        cross_product(3) = a(1) * b(2) - a(2) * b(1)

        return
    end function cross_product

    pure function identity_mat(n)
        !Returns the nxn identity matrix

        integer :: i
        integer, intent(in) :: n
        real(kind = dp), dimension(n, n) :: identity_mat

        identity_mat(:, :) = 0.0_dp
        do i = 1, n
          identity_mat(i, i) = 1.0_dp
        end do

     return
     end function identity_mat

     pure function triple_product(a, b, c)
        !triple product between three 3*1 vectors

        real(kind = dp) :: triple_product
        real(kind = dp), dimension(3), intent(in) :: a, b, c
        triple_product = dot_product(a, cross_product(b, c))

     return
     end function triple_product

    function in_bd_lat(v, box_faces, box_norms)
        !This function returns whether the point is within the transformed box boundaries. The transformed 
        !box being the transformed simulation cell in the lattice basis
        
        !Input/output variables
        real(kind=dp), dimension(3), intent(in) :: v !integer lattice position
        real(kind=dp), dimension(3,6), intent(in) :: box_faces !Centroid of all the box faces
        real(kind=dp), dimension(3,6), intent(in) :: box_norms !Box face normals
        logical :: in_bd_lat

        !Other variables
        integer :: i
        real(kind=dp) :: pt_to_face(3)

        in_bd_lat = .true.

        !Check if point is in box bounds, this works by comparing the dot product of the face normal and the 
        !vector from the point to the face. If the dot product is greater than 0 then the point is behind the face
        !if it is equal to zero then the point is on the face, if is less than 0 the the point is in front of the face.
        do i = 1, 6
            pt_to_face(:) = box_faces(:, i) - v 
            if(dot_product(pt_to_face, box_norms(:,i)) <= 0) then 
                in_bd_lat = .false.
                exit
            end if
        end do

        return
    end function in_bd_lat

    function in_block_bd(v, box_bd)
        !This function determines whether a point is within a block in 3d
        !Input/output
        real(kind=dp), dimension(3), intent(in) :: v
        real(kind=dp), dimension(6), intent(in) :: box_bd
        logical :: in_block_bd

        !Other variables
        integer :: i

        in_block_bd = .true.

        do i =1 ,3
            !Check upper bound
            if(v(i) > (box_bd(2*i))) then 
                in_block_bd =.false.
                exit
            !Check lower bound
            else if (v(i) < (box_bd(2*i-1))+1d-6) then 
                in_block_bd = .false.
                exit
            end if
        end do
    end function in_block_bd

    function in_wedge_bd(r,vertex)
        !This code determines whether the 2dimensional projection of a point lies within the 2 dimensional projection of a wedge.
        real(kind=dp), intent(in) :: r(3) !This is the point position
        real(kind=dp), intent(in) :: vertex(3,3) !These are the relevant vertex positions for the wedge
        real(kind=dp) :: v1(3), v2(3), v3(3), c1(3), c2(3) !Vertex vector to point and cross products
        integer :: i
        logical :: in_wedge_bd

        in_wedge_bd = .true.
        do i = 1, 3
            v1 = vertex(:,mod(i,3)+1) - vertex(:,i)
            v2 = r - vertex(:,i)
            v3 = vertex(:,mod(i+1,3)+1) - vertex(:,i)
            c1 = cross_product(v1,v2)  
            c2 = cross_product(v1,v3)  
            if(dot_product(c1,c2) < 0) then 
               in_wedge_bd=.false.
               exit
            end if
        end do
    end function in_wedge_bd

    function lcm(a,b)
        !This function returns the smallest least common multiple of two numbers 

        real(kind=dp), intent(in) :: a, b
        real(kind=dp) :: lcm

        integer :: aint, bint, gcd, remainder, placeholder

        !Cast the vector positions to ints. There will be some error associated with this calculation
        aint = a*10**2
        bint = b*10**2

        !Calculate greated common divisor
        gcd = aint
        placeholder = bint
        do while(placeholder /= 0)
            remainder = modulo(gcd, placeholder)
            gcd = placeholder
            placeholder=remainder
        end do
        lcm = real((aint*bint),dp)/(real(gcd,dp))* 10.0_dp**(-2.0_dp)
    end function lcm

    function is_neighbor(rl, rk, r_in, r_out)
        !This function checks to see if two atoms are within a shell with an inner radius r_in and outer radius 
        !r_out
        real(kind=dp), intent(in) :: r_in, r_out
        real(kind=dp), dimension(3), intent(in) :: rl, rk
        logical :: is_neighbor

        !Internal variable
        real(kind=dp) :: rlk

        rlk = norm2(rk - rl)
        is_neighbor=.true.
        if((rlk>r_out).or.(rlk < r_in)) is_neighbor = .false.

        return
    end function is_neighbor
    
    function is_equal(A, B)
        !This function checks if too numbers are equal within a tolerance
        real(kind=dp), intent(in) :: A, B
        logical :: is_equal

        if((A>(B - 10.0_dp**(-10))).and.(A < (B+10.0_dp**(-10)))) then 
            is_equal = .true.
        else
            is_equal = .false.
        end if
    return
    end function is_equal

    function mass_is_equal(A, B)
        !This function checks if too numbers are equal within a tolerance
        real(kind=dp), intent(in) :: A, B
        logical :: mass_is_equal

        if((A>(B - 10.0_dp**(-2))).and.(A < (B+10.0_dp**(-2)))) then 
            mass_is_equal = .true.
        else
            mass_is_equal = .false.
        end if
    return
    end function mass_is_equal

    pure function unitvec(n,vec)
        integer, intent(in) :: n
        real(kind=dp), intent(in) :: vec(n)
        real(kind=dp) :: unitvec(n)

        unitvec = vec/norm2(vec)
        return
    end function unitvec

    pure function norm_dis(rl,rk)
        !This just returns the magnitude of the vector between two points
        real(kind=dp), dimension(3), intent(in) :: rl, rk
        real(kind=dp) :: norm_dis(4)

        norm_dis(1:3) = (rk - rl)
        norm_dis(4) = norm2(rk-rl)
    end function

    pure function matinv3(A) result(B)
        !! Performs a direct calculation of the inverse of a 3×3 matrix.
        real(kind=dp), intent(in) :: A(3,3)   !! Matrix
        real(kind=dp)             :: B(3,3)   !! Inverse matrix
        real(kind=dp)             :: detinv

        if(abs(A(1,1)*A(2,2)*A(3,3) - A(1,1)*A(2,3)*A(3,2)&
                  - A(1,2)*A(2,1)*A(3,3) + A(1,2)*A(2,3)*A(3,1)&
                  + A(1,3)*A(2,1)*A(3,2) - A(1,3)*A(2,2)*A(3,1)) < lim_zero) then 
            B(:,:) = 0
            return
        else
            ! Calculate the inverse determinant of the matrix
            
            detinv = 1/(A(1,1)*A(2,2)*A(3,3) - A(1,1)*A(2,3)*A(3,2)&
                      - A(1,2)*A(2,1)*A(3,3) + A(1,2)*A(2,3)*A(3,1)&
                      + A(1,3)*A(2,1)*A(3,2) - A(1,3)*A(2,2)*A(3,1))

            ! Calculate the inverse of the matrix
            B(1,1) = +detinv * (A(2,2)*A(3,3) - A(2,3)*A(3,2))
            B(2,1) = -detinv * (A(2,1)*A(3,3) - A(2,3)*A(3,1))
            B(3,1) = +detinv * (A(2,1)*A(3,2) - A(2,2)*A(3,1))
            B(1,2) = -detinv * (A(1,2)*A(3,3) - A(1,3)*A(3,2))
            B(2,2) = +detinv * (A(1,1)*A(3,3) - A(1,3)*A(3,1))
            B(3,2) = -detinv * (A(1,1)*A(3,2) - A(1,2)*A(3,1))
            B(1,3) = +detinv * (A(1,2)*A(2,3) - A(1,3)*A(2,2))
            B(2,3) = -detinv * (A(1,1)*A(2,3) - A(1,3)*A(2,1))
            B(3,3) = +detinv * (A(1,1)*A(2,2) - A(1,2)*A(2,1))
        end if
    end function

     pure function transpose3(A) result(B)
         !!Transposes matrix A
         real(kind=dp), intent(in) :: A(3,3)
         real(kind=dp)             :: B(3,3)  

         integer :: i, j
         forall(i =1:3,j=1:3) B(i,j) = A(j,i)

    end function transpose3

    pure function sqrt3(A) result(B)
        !This calculates the square of matrix A fulfilling the equation B*B = A according to 
        !the algorithm by Franca1989

        real(kind=dp), intent(in) :: A(3,3)
        real(kind=dp)             :: B(3,3)

        real(kind=dp) :: Ione, Itwo, Ithree, l, k, phi, Asq(3,3), lambda, Bone, Btwo, Bthree, p
    
        !Step 1 is calculating the invariants of C
        Ione = A(1,1) + A(2,2)  + A(3,3)
        Asq = matmul(A,A)
        Itwo = 0.5_dp *(Ione*Ione - (Asq(1,1) + Asq(2,2) + Asq(3,3)))
        Ithree = (A(1,1)*A(2,2)*A(3,3) - A(1,1)*A(2,3)*A(3,2)& 
                - A(1,2)*A(2,1)*A(3,3) + A(1,2)*A(2,3)*A(3,1)& 
                + A(1,3)*A(2,1)*A(3,2) - A(1,3)*A(2,2)*A(3,1)) 

        if (Ithree < 0) then 
            B(:,:)=0.0_dp
            return
        end if
        !Check for an isotropic matrix
        k = Ione*Ione - 3*Itwo
        if (k < lim_zero) then 
            lambda = sqrt(Ione/3.0_dp)
            B = lambda*identity_mat(3)
        else
            l = Ione*(Ione*Ione - 9.0_dp/2.0_dp * Itwo) + 27.0_dp/2.0_dp * Ithree
            p = l/(k**(1.5_dp))

            if (p > 1.0 ) then 
                B(:,:) = 0.0_dp
                return
            end if

            if ((p< -1).or.(p>1)) then 
                B(:,:) = 0.0_dp
                return
            end if
            phi = acos(p)
            lambda = sqrt(1.0_dp/3.0_dp * (Ione + 2*sqrt(k)*cos(phi/3)))
            
            !Now calculate invariantes of B
            Bthree = sqrt(Ithree)
            if((-lambda*lambda + Ione + 2*Ithree/lambda) < 0) then 
                B(:,:) = 0.0_dp
                return
            end if
            Bone = lambda + sqrt(- lambda*lambda + Ione + 2*Ithree/lambda)
            Btwo = (Bone*Bone - Ione)/2.0_dp
            
            !Now calculate B
            if(abs(Bone*Btwo -Bthree) < lim_zero) then 
                B(:,:) = 0.0_dp
                return
            end if
            B = 1/(Bone*Btwo - Bthree) *(Bone*Bthree*identity_mat(3) + (Bone*Bone - Btwo)*A - matmul(A,A))
        end if
    end function sqrt3

    pure function permutation(i,j,k) result(e)
        !Calculates the permutation tensor
        integer, intent(in) :: i,j,k
        integer             :: e
        
        if ( ((i==1).and.(j==2).and.(k==3)).or. &
             ((i==2).and.(j==3).and.(k==1)).or. &
             ((i==3).and.(j==1).and.(k==2))) then
            e=1
        else if( ((i==2).and.(j==1).and.(k==3)).or. &
                 ((i==1).and.(j==3).and.(k==2)).or. &
                 ((i==3).and.(j==2).and.(k==1))) then
            e=-1
        else
            e=0
        end if
    end function permutation

    pure function evtogp(virial)
        real(kind=dp), dimension(6), intent(in) :: virial
        real(kind=dp), dimension(6) :: evtogp

        evtogp = virial * 1e21_dp * 1.602176565e-19_dp

    end function

end module functions
