! These various sort routines are provided as
! examples, rather than being fully optimised
! for the best possible performance. MJR 9/2019
!
! So far the collection is:
!
!  bubble_sort           (order N**2)
!  selection_sort        (order N**2)
!  inline_selection_sort (order N**2)
!  insertion_sort        (order N**2)
!  binary_insertion_sort (order N**2)
!  pair_insertion_sort   (order N**2)
!  double_insertion_sort (order N**2)
!  shell_sort            (order N**(4/3)) (possibly)
!  merge_sort            (order N log N)
!  alt_merge_sort        (order N log N)
!  heap_sort             (order N log N)
!  heap_sort_nr          (order N log N)
!  smoothsort            (order N log N)
!  quicksort             (order N log N) (usually)
!  quicksort_nr          (order N log N) (usually)
!  dpquicksort           (order N log N) (usually)

! 14/10/19 and 18/10/19 indexing errors in heap sorts corrected

! 4/4/20 smooth sort and non-recursive quicksort added, and some
!        minor code tidying


module sorts
  use parameters
  implicit none
  integer, parameter :: prec=dp, i64=selected_int_kind(15)

  ! array L used by smoothsort

  integer, parameter :: L(0:43) = (/ 1, 1, 3, 5, 9, 15, 25, 41, 67, 109, &
       177, 287, 465, 753, 1219, 1973, 3193, 5167, 8361, &
       13529, 21891, 35421, 57313, 92735, 150049, &
       242785, 392835, 635621, 1028457, 1664079, 2692537, &
       4356617, 7049155, 11405773, 18454929, 29860703, &
       48315633, 78176337, 126491971, 204668309, 331160281, &
       535828591, 866988873, 1402817465 /)


  private :: binary_search, merge_arrays, heapify, heapify_nr, &
       smooth_heapify, smooth_extract, sift_in, interheap_sift, L

contains

  recursive subroutine quicksort(array)
    real(prec), intent(inout)::array(:)
    real(prec) :: temp,pivot
    integer :: i,j,last,left,right

    last=size(array)

    if (last.lt.50) then ! use insertion sort on small arrays
       do i=2,last
          temp=array(i)
          do j=i-1,1,-1
             if (array(j).le.temp) exit
             array(j+1)=array(j)
          enddo
          array(j+1)=temp
       enddo
       return
    endif
    ! find median of three pivot
    ! and place sentinels at first and last elements
    temp=array(last/2)
    array(last/2)=array(2)
    if (temp.gt.array(last)) then
       array(2)=array(last)
       array(last)=temp
    else
       array(2)=temp
    endif
    if (array(1).gt.array(last)) then
       temp=array(1)
       array(1)=array(last)
       array(last)=temp
    endif
    if (array(1).gt.array(2)) then
       temp=array(1)
       array(1)=array(2)
       array(2)=temp
    endif
    pivot=array(2)

    left=3
    right=last-1
    do
       do while(array(left).lt.pivot)
          left=left+1
       enddo
       do while(array(right).gt.pivot)
          right=right-1
       enddo
       if (left.ge.right) exit
       temp=array(left)
       array(left)=array(right)
       array(right)=temp
       left=left+1
       right=right-1
    enddo
    if (left.eq.right) left=left+1
    call quicksort(array(1:left-1))
    call quicksort(array(left:))

  end subroutine quicksort

  ! This version maintains its own stack, to avoid needing to call
  ! itself recursively. By always pushing the larger "half" to the
  ! stack, and moving directly to calculate the smaller "half",
  ! it can guarantee that the stack needs no more than log_2(N)
  ! entries
  subroutine quicksort_nr(array)
    real(prec), intent(inout)::array(:)
    real(prec) :: temp,pivot
    integer :: i,j,left,right,low,high
    ! If your compiler lacks storage_size(), replace
    ! storage_size(i) by 64
    integer :: stack(2,storage_size(i)),stack_ptr

    low=1
    high=size(array)
    stack_ptr=1

    do

       if (high-low.lt.50) then ! use insertion sort on small arrays
          do i=low+1,high
             temp=array(i)
             do j=i-1,low,-1
                if (array(j).le.temp) exit
                array(j+1)=array(j)
             enddo
             array(j+1)=temp
          enddo
          ! now pop from stack
          if (stack_ptr.eq.1) return
          stack_ptr=stack_ptr-1
          low=stack(1,stack_ptr)
          high=stack(2,stack_ptr)
          cycle
       endif

       ! find median of three pivot
       ! and place sentinels at first and last elements
       temp=array((low+high)/2)
       array((low+high)/2)=array(low+1)
       if (temp.gt.array(high)) then
          array(low+1)=array(high)
          array(high)=temp
       else
          array(low+1)=temp
       endif
       if (array(low).gt.array(high)) then
          temp=array(low)
          array(low)=array(high)
          array(high)=temp
       endif
       if (array(low).gt.array(low+1)) then
          temp=array(low)
          array(low)=array(low+1)
          array(low+1)=temp
       endif
       pivot=array(low+1)

       left=low+2
       right=high-1
       do
          do while(array(left).lt.pivot)
             left=left+1
          enddo
          do while(array(right).gt.pivot)
             right=right-1
          enddo
          if (left.ge.right) exit
          temp=array(left)
          array(left)=array(right)
          array(right)=temp
          left=left+1
          right=right-1
       enddo
       if (left.eq.right) left=left+1
       !          call quicksort(array(1:left-1))
       !          call quicksort(array(left:))
       if (left.lt.(low+high)/2) then
          stack(1,stack_ptr)=left
          stack(2,stack_ptr)=high
          stack_ptr=stack_ptr+1
          high=left-1
       else
          stack(1,stack_ptr)=low
          stack(2,stack_ptr)=left-1
          stack_ptr=stack_ptr+1
          low=left
       endif

    enddo
  end subroutine quicksort_nr


  ! dual pivot quicksort
  recursive subroutine dpquicksort(array)
    real(prec), intent(inout)::array(:)
    real(prec) :: temp,p1,p2
    integer :: i,j,last,l,k,g

    last=size(array)

    if (last.lt.40) then ! use insertion sort on small arrays
       do i=2,last
          temp=array(i)
          do j=i-1,1,-1
             if (array(j).le.temp) exit
             array(j+1)=array(j)
          enddo
          array(j+1)=temp
       enddo
       return
    endif
    p1=array(last/3)
    p2=array(2*last/3)
    if (p2.lt.p1) then
       temp=p1
       p1=p2
       p2=temp
    endif
    array(last/3)=array(1)
    array(1)=p1
    array(2*last/3)=array(last)
    array(last)=p2

    g=last
    l=2
    do while (array(l).lt.p1)
       l=l+1
    enddo
    k=l

    do while(k.lt.g)
       temp=array(k)
       if (temp.lt.p1) then
          array(k)=array(l)
          array(l)=temp
          l=l+1
       else if (temp.gt.p2) then
          do while(array(g-1).gt.p2)
             g=g-1
          enddo
          if (k.ge.g) exit
          g=g-1
          if (array(g).lt.p1) then
             array(k)=array(l)
             array(l)=array(g)
             array(g)=temp
             l=l+1
          else
             array(k)=array(g)
             array(g)=temp
          endif
       endif
       k=k+1
    enddo
    if (l.gt.2) then
       array(1)=array(l-1)
       array(l-1)=p1
       call dpquicksort(array(1:l-2))
    endif
    call dpquicksort(array(l:g-1))
    if (g.lt.last) then
       array(last)=array(g)
       array(g)=p2
       call dpquicksort(array(g+1:last))
    endif

  end subroutine dpquicksort

  function binary_search(array, x, start, end)
    integer binary_search
    integer, intent(in) :: start,end
    real(prec), intent(in) :: x,array(:)
    integer :: a,b,mid

    a=start
    b=end

    do
       if (a.eq.b) then
          if (array(a).gt.x) then
             binary_search=a
             return 
          else
             binary_search=a+1
             return
          endif
       endif
       if (a.gt.b) then
          binary_search=a
          return
       end if


       mid=(a+b)/2
       if (array(mid).lt.x) then
          a=mid+1
       else if (array(mid).gt.x) then
          b=mid-1
       else
          binary_search=mid
          return
       endif

    end do
  end function binary_search

  subroutine binary_insertion_sort(array)
    real(prec), intent(inout) :: array(:)
    integer :: i,j,pos
    real(prec) :: x

    do i=2,size(array)
       x=array(i)
       pos=binary_search(array,x,1,i-1)
       do j=i,pos+1,-1
          array(j)=array(j-1)
       enddo
       array(pos)=x
    enddo
  end subroutine binary_insertion_sort

  subroutine insertion_sort(array)
    real(prec), intent(inout) :: array(:)
    integer :: i,j
    real(prec) :: temp

    do i=2,size(array)
       temp=array(i)
       do j=i-1,1,-1
          if (array(j).le.temp) exit
          array(j+1)=array(j)
       enddo
       array(j+1)=temp
    enddo
  end subroutine insertion_sort

  subroutine double_insertion_sort(array)
    real(prec), intent(inout) :: array(:)
    integer :: i,j,left,right,last
    real(prec) :: temp,p,next

    last=size(array)

    p=0.5*(array(1)+array(last))
    if (array(1).gt.array(last)) then
       temp=array(last)
       array(last)=array(1)
       array(1)=temp
    endif

    left=1
    right=last
    temp=array(2)

    do i=2,last-1
       if (temp.lt.p) then
          do j=left,1,-1
             if (array(j).le.temp) exit
             array(j+1)=array(j)
          enddo
          array(j+1)=temp
          temp=array(left+2)
          left=left+1
       else
          next=array(right-1)
          do j=right,last
             if (array(j).ge.temp) exit
             array(j-1)=array(j)
          enddo
          array(j-1)=temp
          temp=next
          right=right-1
       endif
    enddo

  end subroutine double_insertion_sort


  subroutine pair_insertion_sort(array)
    real(prec), intent(inout) :: array(:)
    integer :: i,j,last
    real(prec) :: t1,t2

    last=size(array)
    do i=2,last-1,2
       t1=min(array(i),array(i+1))
       t2=max(array(i),array(i+1))
       j=i-1
       do while((j.ge.1).and.(array(j).gt.t2))
          array(j+2)=array(j)
          j=j-1
       enddo
       array(j+2)=t2
       do while((j.ge.1).and.(array(j).gt.t1))
          array(j+1)=array(j)
          j=j-1
       enddo
       array(j+1)=t1
    end do

    if(mod(last,2).eq.0)then
       t1=array(last)
       do j=last-1,1,-1
          if (array(j).le.t1) exit
          array(j+1)=array(j)
       end do
       array(j+1)=t1
    endif

  end subroutine pair_insertion_sort


  subroutine merge_arrays(a,b,out)
    ! routine used by merge_sort()
    real(prec), intent(in) :: a(:),b(:)
    real(prec), intent(out) :: out(:)
    integer :: ai,bi,oi,i

    ai=1
    bi=1
    oi=1

    do
       if (ai.gt.size(a)) then
          do i=bi,size(b)
             out(oi)=b(i)
             oi=oi+1
          enddo
          return
       endif
       if (bi.gt.size(b)) then
          do i=ai,size(a)
             out(oi)=a(i)
             oi=oi+1
          enddo
          return
       endif

       if (a(ai).lt.b(bi)) then
          out(oi)=a(ai)
          ai=ai+1
       else
          out(oi)=b(bi)
          bi=bi+1
       endif
       oi=oi+1
    enddo
  end subroutine merge_arrays

  recursive subroutine merge_sort(array)
    ! With a little skill, the extra copy of "array=temp"
    ! can be avoided
    real(prec), intent(inout) :: array(:)
    real(prec), allocatable :: temp(:)
    integer :: last

    last=size(array)

    if (last.lt.40) then
       call insertion_sort(array)
       return
    endif

    call merge_sort(array(1:last/2))
    call merge_sort(array(last/2+1:last))
    allocate(temp(last))
    call merge_arrays(array(1:last/2),array(last/2+1:last),temp)
    array=temp
    deallocate(temp)
  end subroutine merge_sort

  recursive subroutine alt_merge_sort(array)
    ! Alternate merges between array and temp
    ! to avoid copy
    real(prec), intent(inout) :: array(:)
    real(prec), allocatable :: temp(:)
    integer :: last

    last=size(array)

    if (last.lt.40) then
       call insertion_sort(array)
       return
    endif

    call alt_merge_sort(array(1:last/4))
    call alt_merge_sort(array(last/4+1:last/2))
    call alt_merge_sort(array(last/2+1:last/2+last/4))
    call alt_merge_sort(array(last/2+last/4+1:last))

    allocate(temp(last))
    call merge_arrays(array(1:last/4),array(last/4+1:last/2),temp(1:last/2))
    call merge_arrays(array(last/2+1:last/2+last/4),  &
         array(last/2+last/4+1:last),temp(last/2+1:last))
    call merge_arrays(temp(1:last/2),temp(last/2+1:last),array)
    deallocate(temp)
  end subroutine alt_merge_sort

  subroutine selection_sort(array)
    ! This version uses Fortran's minloc intrinsic
    real(prec), intent(inout) :: array(:)
    real(prec) :: temp
    integer :: i,j

    do i=1,size(array)-1
       j=minloc(array(i:),1)+i-1
       temp=array(i)
       array(i)=array(j)
       array(j)=temp
    enddo

  end subroutine selection_sort

  subroutine inline_selection_sort(array)
    ! This version does not use Fortran's minloc intrinsic
    real(prec), intent(inout) :: array(:)
    real(prec) :: temp
    integer :: i,j,k

    do i=1,size(array)-1
       temp=array(i)
       j=i
       do k=i+1,size(array)
          if (array(k).lt.temp) then
             temp=array(k)
             j=k
          endif
       enddo
       array(j)=array(i)
       array(i)=temp
    enddo

  end subroutine inline_selection_sort

  subroutine shell_sort(array)
    ! There are many choices of intervals
    ! The sequence (3**k-1)/2 used below is given by Knuth
    ! The sort relies on the insertion sort of nearly sorted
    ! data being fast (i.e. not order N**2)
    real(prec), intent(inout) :: array(:)
    integer :: i,interval,last

    interval=1
    last=size(array)
    do while(interval<last/3)
       interval=3*interval+1
    enddo

    do while(interval>0)
       do i=1,interval
          ! The following syntax passes a subarray with elements
          ! array(i),array(i+interval),array(i+2*interval),...
          call insertion_sort(array(i::interval))
       enddo
       interval=(interval-1)/3
    enddo

  end subroutine shell_sort

  subroutine bubble_sort(array)

    real(prec), intent(inout) :: array(:)
    real(prec) :: temp
    integer :: i,j,last

    last=size(array)
    do i=last-1,1,-1
       do j=1,i
          if (array(j+1).lt.array(j)) then
             temp=array(j+1)
             array(j+1)=array(j)
             array(j)=temp
          endif
       enddo
    enddo

  end subroutine bubble_sort

  subroutine heap_sort(array)
    real(prec), intent(inout) :: array(:)
    real(prec) :: temp
    integer :: i,last

    last=size(array)

    ! Build heap
    do i=last/2,1,-1
       call heapify(array,i)
    enddo

    ! Unpick heap
    do i=last,2,-1
       temp=array(1)
       array(1)=array(i)
       array(i)=temp
       call heapify(array(1:i-1),1)
    end do

  end subroutine heap_sort

  recursive subroutine heapify(array,root)
    real(prec), intent(inout) :: array(:)
    real(prec) :: temp
    integer :: left,right,root,last,largest

    last=size(array)
    left=2*root
    right=left+1
    largest=root

    if (left.le.last) then
       if(array(left).gt.array(largest)) largest=left
    endif

    if (right.le.last) then
       if(array(right).gt.array(largest)) largest=right
    endif

    if (largest.ne.root) then
       temp=array(root)
       array(root)=array(largest)
       array(largest)=temp
       call heapify(array,largest)
    end if

  end subroutine heapify


  subroutine heap_sort_nr(array)
    ! The same but with a non-recursive heapify
    real(prec), intent(inout) :: array(:)
    real(prec) :: temp
    integer :: i,last

    last=size(array)

    ! Build heap
    do i=last/2,1,-1
       call heapify_nr(array,i)
    enddo

    ! Unpick heap
    do i=last,2,-1
       temp=array(1)
       array(1)=array(i)
       array(i)=temp
       call heapify_nr(array(1:i-1),1)
    end do

  end subroutine heap_sort_nr

  subroutine heapify_nr(array,i)
    ! A non-recursive heapify
    real(prec), intent(inout) :: array(:)
    real(prec) :: root_val
    integer :: i,left,right,root,last,largest

    last=size(array)
    root=i
    left=2*root
    root_val=array(root)
    largest=root

    do while(left.le.last)
       right=left+1

       if (left.le.last) then
          if(array(left).gt.array(largest)) largest=left
       endif

       if (right.le.last) then
          if(array(right).gt.array(largest)) largest=right
       endif

       if (largest.eq.root) exit

       array(root)=array(largest)
       array(largest)=root_val
       root=largest
       left=2*root
    enddo

    !    array(largest)=root_val

  end subroutine heapify_nr

  ! Smoothsort is introduced by Edsger Dijkstra
  ! https://doi.org/10.1016/0167-6423(82)90016-8
  !
  ! This Fortran version owes much to https://www.keithschwarz.com/smoothsort/
  ! and the C version given by Martin Knoblauch Revuelta at
  ! http://code.google.com/archive/p/combsortcs2p-and-other-sorting-algorithms

  subroutine smoothsort(array)
    real(prec), intent(inout) :: array(:)
    integer(i64) :: mask
    integer :: offset

    if (size(array).le.1) return

    call smooth_heapify(array,mask,offset)

    call smooth_extract(array,mask,offset)
  end subroutine smoothsort

  subroutine smooth_heapify(array,mask,offset)
    real(prec), intent(inout) :: array(0:)
    integer(i64), intent(out) :: mask
    integer, intent(out) :: offset
    integer :: i,last
    logical :: wbf

    mask=1
    offset=1
    last=size(array)

    do i=1,last-1
       if (iand(mask,2_i64).eq.2) then
          mask=ior(ishft(mask,-2),1_i64)
          offset=offset+2
       else if (offset.eq.1) then
          mask=ior(ishft(mask,1),1_i64)
          offset=0
       else
          mask=ior(ishft(mask,offset-1),1_i64)
          offset=1
       endif
       wbf=(((iand(mask,2_i64).eq.2).and.(i+1.lt.last)).or. &
            ((offset.gt.0).and.(1+i+L(offset-1).lt.last)))
       if (wbf) then
          call sift_in(array,i,offset)
       else
          call interheap_sift(array,i,mask,offset)
       endif
    enddo

  end subroutine smooth_heapify
  subroutine smooth_extract(array,mask,offset)
    real(prec), intent(inout) :: array(0:)
    integer :: i,j,last,ch(2)
    integer(i64),value :: mask
    integer, value :: offset

    last=size(array)

    do i=last-1,2,-1
       if (offset.lt.2) then
          do
             mask=ishft(mask,-1)
             offset=offset+1
             if (iand(mask,1_i64).eq.1) exit
          enddo
       else
          ch(2)=i-1
          ch(1)=ch(2)-L(offset-2)
          mask=iand(mask,not(1_i64))
          do j=1,2
             mask=ior(ishft(mask,1),1_i64)
             offset=offset-1
             call interheap_sift(array,ch(j),mask,offset)
          end do
       end if
    end do


  end subroutine smooth_extract


  subroutine sift_in(array,root,sz)
    real(prec), intent(inout) :: array(0:)
    real(prec) :: tmp
    integer :: left,right,next
    integer,value :: root,sz

    if (sz.lt.2) return

    tmp=array(root)

    do
       right=root-1
       left=right-L(sz-2)

       if (array(right).lt.array(left)) then
          next=left
          sz=sz-1
       else
          next=right
          sz=sz-2
       end if

       if (array(next).le.tmp) exit

       array(root)=array(next)

       root=next
       if (sz.le.1) exit
    end do

    array(root)=tmp

  end subroutine sift_in

  subroutine interheap_sift(array,root,mask,offset)
    real(prec), intent(inout) :: array(0:)
    real(prec) :: tmp,mx
    integer :: left,right,next
    integer(i64),value :: mask
    integer, value :: root,offset

    tmp=array(root)

    do while (mask.ne.1)
       mx=tmp

       if (offset.gt.1) then
          right=root-1
          left=right-L(offset-2)
          mx=max(mx,array(left),array(right))
       end if

       next=root-L(offset)

       if (array(next).le.mx) exit

       array(root)=array(next)
       root=next

       do
          mask=ishft(mask,-1)
          offset=offset+1
          if (iand(mask,1_i64).eq.1) exit
       end do
    end do


    array(root)=tmp
    call sift_in(array,root,offset)
  end subroutine interheap_sift


end module sorts
