module mode_da
    !This mode is used to calculate the dislocation analysis algorithm for both CG and atomistic regions

    use parameters
    use io
    use elements
    use neighbors
    
    implicit none 

    integer :: num_d_points
    integer, allocatable :: is_slipped(:,:,:) 
    real(kind=dp), allocatable :: disl_line(:,:), disl_data(:)

    private :: write_xyz
    public 
    contains

    subroutine da(arg_pos)
        !This is the main calling subroutine for the dislocation analysis code
        integer, intent(out) :: arg_pos
        real(kind=dp), dimension(6) :: temp_box_bd

        integer :: ppos
        character(len=100) :: outfile

        !Now call the dislocation analysis code for the coarse-grained elements
        call parse_command(arg_pos)

        call read_in(1, (/0.0_dp,0.0_dp,0.0_dp/), temp_box_bd)

        !Now allocate the necessary variables
        if(allocated(is_slipped)) deallocate(is_slipped)
        allocate(is_slipped(max_basisnum, max_ng_node, ele_num))

        if(allocated(disl_line)) deallocate(disl_line)
        num_d_points=0
        allocate(disl_line(3,1024))
        allocate(disl_data(1024))

        call cga_da

        !Now create the output file num and write out to xyz format
        ppos = scan(trim(infiles(1)),".", BACK= .true.)
        if ( ppos > 0 ) then 
            outfile = 'da_'//infiles(1)(1:ppos)//'vtk'
        else 
            outfile = 'da_'//infiles(1)//'.vtk'
        end if
        call write_da_vtk(outfile)        

        ppos = scan(trim(infiles(1)),".", BACK= .true.)
        if ( ppos > 0 ) then 
            outfile = 'da_'//infiles(1)(1:ppos)//'xyz'
        else 
            outfile = 'da_'//infiles(1)//'.xyz'
        end if
        call write_da_xyz(outfile)
        
        return
    end subroutine da


    subroutine parse_command(arg_pos)
        !This subroutine parses the arguments for mode command
        integer, intent(out)  :: arg_pos
        integer :: i, arglen
        character(len = 100) :: textholder
        logical :: file_exists

        !Read the input file 
        call get_command_argument(2,textholder, arglen)
        if (arglen == 0) stop "Missing file for dislocation analysis"
        call get_in_file(textholder)

        arg_pos = 3
        return
    end subroutine parse_command

    subroutine cga_da

        integer :: i, j, k, l, ibasis, nei, close_neighbors(2,4,6), far_neighbor(4,6), minindex, minindices(2), linevec(3)

        integer :: face_types(ele_num*6), face_ele(6*ele_num), diff_pair(2,6)
        real(kind = dp) :: face_centroids(3, ele_num*6), r(3), rc, vnode(3, max_basisnum, 4), vnorm, &
                           max_node_dist, ndiff, rmax(3), rmin(3), rnorm, v1(3), v2(3), v1norm, v2norm, theta1, theta2, &
                            diff_mag(6), diffvec(3)
        real(kind=dp), allocatable :: disl_line_temp(:,:), disl_data_temp(:)
        logical :: is_edge

        !Initialize variables
        l = 0
        max_node_dist = 0

        !Now save the close and far neighbors of the nodes. This is done to attempt to figure out the character of the dislocation
        !by comparing how the burgers vector is distributed over the face.
        do j = 1, 6
            close_neighbors(1, 1, j) = cubic_faces(4, j)
            close_neighbors(2, 1, j) = cubic_faces(2, j)
            far_neighbor(1,j) = cubic_faces(3,j)

            close_neighbors(1, 2, j) = cubic_faces(1, j)
            close_neighbors(2, 2, j) = cubic_faces(3, j)
            far_neighbor(2,j) = cubic_faces(4,j)

            close_neighbors(1, 3, j) = cubic_faces(2, j)
            close_neighbors(2, 3, j) = cubic_faces(4, j)
            far_neighbor(3,j) = cubic_faces(1,j)

            close_neighbors(1, 4, j) = cubic_faces(3, j)
            close_neighbors(2, 4, j) = cubic_faces(1, j)
            far_neighbor(4,j) = cubic_faces(2,j)
        end do

        !First calculate all of the face centroids
        do i = 1, ele_num
            do j = 1, 6
                r(:) = 0.0_dp
                rmax= -Huge(1.0)
                rmin= Huge(1.0)
                do k = 1, 4
                    do ibasis = 1, basisnum(lat_ele(i))
                        r = r + r_node(:, ibasis, cubic_faces(k,j), i) 
                        rmax= max(rmax, r_node(:, ibasis, cubic_faces(k,j), i))
                        rmin= min(rmin, r_node(:, ibasis, cubic_faces(k,j), i))
                    end do
                end do  
                
                rnorm=norm2(rmax-rmin)
                max_node_dist = max(max_node_dist, rnorm)
                r = r/(basisnum(lat_ele(i))*4)

                !add the face centroids, the type, and map the elements faces to the face arrays
                l = l + 1
                face_centroids(:, l) = r 
                face_types(l) = j
                face_ele(l) = i


            end do
        end do

        !Now set the cut off distance as half the distance from opposite nodes in the largest element
        rc = 0.5*max_node_dist
        call calc_NN(l, face_centroids(:,1:l), rc)

        !Now loop overall the faces and make sure the nearest neighbor is the opposite type. If it isn't than we dscard it 
        is_slipped = 0
        do i = 1, l
            nei = nn(i)             
            
            !Skip if it's 0 or the closest face belongs to the same element
            if ((nei == 0).or.(face_ele(i) == face_ele(nei))) cycle
            

            !Check the face types, the way that the faces are laid out in the cubic_faces array face 1's opposite is 6 and 
            ! face 2's opposite is 5 and etc
            vnode = 0
            if(face_types(i) == (7-face_types(nei))) then 
                vnorm = 0
                do j = 1, 4
                    do ibasis = 1, basisnum(lat_ele(face_ele(i)))
                        !Compute the vectors between all nodes at the face.
                        vnode(:,ibasis,j) = r_node(:,ibasis, cubic_faces(j,face_types(i)), face_ele(i)) - &
                                       r_node(:,ibasis, cubic_faces(j,face_types(nei)), face_ele(nei))
                    end do
                end do

                !Now calculate the maximum difference between nodes
                vnorm = 0
                do j = 1, 4
                    do k=j,4
                        v1=vnode(:,1,j) - vnode(:,1,k)
                        v1norm=norm2(v1)
                        if (vnorm < v1norm) then
                            vnorm = v1norm
                            diffvec= v1
                        end if 
                    end do
                end do
                !Now calculate the difference between the largest norm and the smallest norm, if it's larger than 0.5 then mark it
                !as slipped. This value probably can be converted to a variable value that depends on the current lattice parameter
                !I think 0.5 works ok though.
                if (vnorm>0.5_dp) then 
                    print *, "Element number ", face_ele(i), " is dislocated along face ", face_types(i), &
                             " with neighbor ", face_ele(nei), " with max displacement of ", vnorm
                    l=0
                    do j = 1, 4
                        is_slipped(:, cubic_faces(j,face_types(i)), face_ele(i)) = 1

                        !This portion of the code is used to determine what the character of the dislocation most likely is
                        !effectively how this works is we look for the two nodes with the most similar burgers vector.
                        !The vector between these two nodes is close to the line direction and as a result we can probably estimate
                        !if it's close to edge, screw, or if it's pretty fairly mixed.
                        do k = j+1, 4
                            l=l+1
                            diff_mag(l) = norm2(vnode(:, 1, j)-vnode(:,1,k))
                            diff_pair(1,l)=cubic_faces(j, face_types(i))
                            diff_pair(2,l)=cubic_faces(k, face_types(i))
                        end do
                    end do
                    minindex = minloc(diff_mag,1)
                    !Now figure out if the min difference between nodes is associated with a 112 direction    
                    is_edge = .false.
                    do j = 1,6
                        if((diff_pair(1,minindex) == oneonetwopairs(1,j)).and.(diff_pair(2,minindex)==oneonetwopairs(2,j))) then
                            is_edge=.true.
                            exit
                        else if((diff_pair(2,minindex)==oneonetwopairs(1,j)).and.(diff_pair(1,minindex)==oneonetwopairs(2,j)))then
                            is_edge=.true.
                            exit
                        end if
                    end do

                    if(is_edge) then 
                        print *, 'Dislocation has primarily edge character'
                    else
                        print *, 'Dislocation has primarily screw character'
                    end if

                    if( i < nei) then 
                        num_d_points=num_d_points+2
                        if(num_d_points > size(disl_line,2)) then 
                            allocate(disl_line_temp(3,size(disl_line,2)+1024))
                            disl_line_temp=0.0_dp
                            disl_line_temp(:, 1:size(disl_line,2))=disl_line
                            call move_alloc(disl_line_temp, disl_line)
                        end if
                        if(num_d_points/2 > size(disl_data)) then 
                            allocate(disl_data_temp(size(disl_data)+1024))
                            disl_data_temp=0
                            disl_data_temp(1:size(disl_data)) = disl_data
                            call move_alloc(disl_data_temp, disl_data)
                        end if
                        print *, num_d_points/2, vnorm
                        disl_data(num_d_points/2)=vnorm
                        disl_line(:,num_d_points-1) =r_node(:, 1, diff_pair(1,minindex), face_ele(i))
                        disl_line(:,num_d_points) =r_node(:, 1, diff_pair(2,minindex), face_ele(i))
                    end if
                end if
            end if
        end do

    end subroutine cga_da

    subroutine write_da_vtk(outfile)
        character (len=*), intent(in) :: outfile

        integer :: i 
        open(unit=11, file=outfile, action='write', status='replace', position='rewind')

        write(11, '(a)') '# vtk DataFile Version 4.0.1'
        write(11, '(a)') 'Dislocation line analyzed via cacmb dislocation analysis'
        write(11, '(a)') 'ASCII'
        write(11, '(a)') 'DATASET UNSTRUCTURED_GRID'
        write(11, *) 'POINTS', num_d_points, 'float'
       
        !Write the points to the file
        do i = 1, num_d_points
            write(11, '(3f23.15)') disl_line(:, i)
        end do

        write(11,*) 'CELLS', num_d_points/2, num_d_points/2*3
        do i=1, num_d_points, 2
            write(11,'(3i16)') 2, i-1, i
        end do
        
        write(11,*) 'CELL_TYPES', num_d_points/2
        do i=1, num_d_points, 2
            write(11,'(i16)') 3
        end do

        write(11,*) 'CELL_DATA', num_d_points/2
        write(11, '(a)') 'SCALARS burgers_vec, float'
        write(11, '(a)') 'LOOKUP_TABLE default'
        do i=1, num_d_points, 2
            write(11, '(f23.15)') disl_data((i+1)/2)
        end do

        close(11)

    end subroutine write_da_vtk

    subroutine write_da_xyz(outfile)
        !This subroutine write the element positions to a .xyz file and marks whether they are slipped or not 
        character(len=*), intent(in) :: outfile
        integer :: i, ibasis, inod, outn
        
        open(unit=11, file=outfile, action='write', status='replace', position='rewind')

        !Write number of node_atoms
        write(11, '(i16)') node_atoms
        !Write comment
        write(11, '(a)') "is_slipped x y z"
        !Write nodal positions
        outn = 0
        do i = 1, ele_num
            do inod = 1, ng_node(lat_ele(i))
                do ibasis = 1, basisnum(lat_ele(i))
                        write(11, '(1i16, 3f23.15)') is_slipped(ibasis,inod,i), r_node(:,ibasis,inod,i)
                        outn = outn + 1
                end do 
            end do 
        end do 

        if(outn /= node_atoms) then 
            print *, "outn", outn, " doesn't equal node_atoms ", node_atoms
        end if
        
        close(11)
    end subroutine write_da_xyz
end module mode_da
