!! A module that provides access to a quicksort form of sorting
!! routine. 
!!
!! Not clear that it is very efficient (e.g. compared to C++ stdlib
!! sort). It is based on the ideas behind NR indexx, but has been
!! rewritten (without looking at the NR f90 code) to avoid copyright
!! issues. Considerable tests have been performed [but no guarantees
!! are provided...!]
!!
module sort
  use types
  use warnings_and_errors
  use assertions
  implicit none
  private

  

  interface indexx
     module procedure indexx_dp, indexx_int
  end interface
  public :: indexx

  interface swap
     module procedure swap_int, swap_dp
  end interface
  public :: swap
  
  interface sort_array
     module procedure sort_array_dp
  end interface sort_array
  public :: sort_array
  
contains

  subroutine sort_array_dp(array)
    real(dp), intent(inout) :: array(:)
    !--------------------------------
    integer :: idx(size(array))
    real(dp) :: tmp(size(array))
    integer :: i
    call indexx(array, idx)
    tmp = array
    array = tmp(idx)
  end subroutine sort_array_dp
  
  
  !-------------------------------------------------------------------------
  !! A non-recursive version of the quick sort algorithm. It returns
  !! an array idx such that array(idx) is sorted.
  !!
  !! Following NR outline, it uses insertion for for sorting small
  !! sub-segments, sentinels to reduce the number of checks when
  !! pivoting and median of first, middle and last elements in order
  !! to select the pivot, to reduce the chances of the worst case
  !! (N^2) occurring.
  !!
  !! Note it uses a fixed-size stack, which is not so great an idea...
  !! (recursion would have had the advantage that this would not have
  !! been a problem).
  subroutine indexx_dp(array,idx)
    real(dp), intent(in)  :: array(:)
    integer,  intent(out) :: idx(size(array))
    !----------------------------------
    integer, parameter :: insertion_threshold = 7, stack_size = 50
    type lrpair
       integer :: l, r
    end type lrpair
    type(lrpair) :: stack(stack_size)
    integer  :: nmid, i, j, l, r, ixstack, medidx, n
    real(dp) :: medval
    
    !n = size(array)
    !if (n/=size(idx)) stop 'array and idx are different sizes'
    n = assert_eq(size(idx),size(array),'indexx_dp')

    forall(i=1:n) idx(i) = i
    
    ! push the whole array section onto the stack
    ixstack = 1
    stack(1) = lrpair(1,n); ixstack = 1
    do 
       ! pull an array section off the stack
       l = stack(ixstack)%l
       r = stack(ixstack)%r
       ixstack = ixstack - 1
       ! get to work on it
       if (r-l < insertion_threshold) then
          ! run an inlined insertion sort.
          do i = l+1, r
             medidx = idx(i)
             medval = array(medidx)
             ! look for the place where we should insert the element "medval", 
             ! and as we go about it, shift the other elements up by one
             do j = i-1, l, -1
                if (medval < array(idx(j))) then
                   idx(j+1) = idx(j)
                else
                   exit
                end if
             end do
             ! insert medval in its correct location
             idx(j+1) = medidx
          end do
       else
          ! NR idea is to take the median value of first, middle and
          ! last elements, so do a little insertion sort to get these
          ! three elements into order
          nmid = (l+r)/2
          ! first place the middle element in position l+1
          call swap(idx(l+1),idx(nmid))
          if (array(idx(l+1)) < array(idx(l))  ) call swap(idx(l+1),idx(l))
          if (array(idx(r))   < array(idx(l+1))) then
             call swap(idx(r),idx(l+1))
             if (array(idx(l+1)) < array(idx(l))) call swap(idx(l+1),idx(l))
          end if
          medidx = idx(l+1)
          medval = array(medidx)

          ! Then run up from first (non-trivial) element, and down from last
          ! element, swapping things as need be until i and j cross
          i = l+1; j = r
          do
             do 
                i = i + 1
                if (array(idx(i)) >= medval) exit
             end do
             do 
                j = j - 1
                if (array(idx(j)) <= medval) exit
             end do
             if (i > j) exit
             call swap(idx(i), idx(j))
          end do
          ! now insert medval (the partitioning element) into the
          ! correct position
          idx(l+1) = idx(j)
          idx(j)   = medidx

          ! now put things onto the stack
          if (ixstack+2 > stack_size) error stop 'stack too small'
          stack(ixstack+1) = lrpair(l,j-1)
          stack(ixstack+2) = lrpair(j,r)
          ixstack = ixstack + 2
       end if
       
       if (ixstack == 0) exit
    end do
    
  end subroutine indexx_dp



  !-------------------------------------------------------------------------
  !! A non-recursive version of the quick sort algorithm. It returns
  !! an array idx such that array(idx) is sorted.
  !!
  !! Following NR outline, it uses insertion for for sorting small
  !! sub-segments, sentinels to reduce the number of checks when
  !! pivoting and median of first, middle and last elements in order
  !! to select the pivot, to reduce the chances of the worst case
  !! (N^2) occurring.
  !!
  !! Note it uses a fixed-size stack, which is not so great an idea...
  !! (recursion would have had the advantage that this would not have
  !! been a problem).
  subroutine indexx_int(array,idx)
    integer,  intent(in)  :: array(:)
    integer,  intent(out) :: idx(size(array))
    !----------------------------------
    integer, parameter :: insertion_threshold = 7, stack_size = 50
    type lrpair
       integer :: l, r
    end type lrpair
    type(lrpair) :: stack(stack_size)
    integer :: nmid, i, j, l, r, ixstack, medidx, n
    real    :: medval
    
    !n = size(array)
    !if (n/=size(idx)) stop 'array and idx are different sizes'
    n = assert_eq(size(idx),size(array),'indexx_dp')

    forall(i=1:n) idx(i) = i
    
    ! push the whole array section onto the stack
    ixstack = 1
    stack(1) = lrpair(1,n); ixstack = 1
    do 
       ! pull an array section off the stack
       l = stack(ixstack)%l
       r = stack(ixstack)%r
       ixstack = ixstack - 1
       ! get to work on it
       if (r-l < insertion_threshold) then
          ! run an inlined insertion sort.
          do i = l+1, r
             medidx = idx(i)
             medval = array(medidx)
             ! look for the place where we should insert the element "medval", 
             ! and as we go about it, shift the other elements up by one
             do j = i-1, l, -1
                if (medval < array(idx(j))) then
                   idx(j+1) = idx(j)
                else
                   exit
                end if
             end do
             ! insert medval in its correct location
             idx(j+1) = medidx
          end do
       else
          ! NR idea is to take the median value of first, middle and
          ! last elements, so do a little insertion sort to get these
          ! three elements into order
          nmid = (l+r)/2
          ! first place the middle element in position l+1
          call swap(idx(l+1),idx(nmid))
          if (array(idx(l+1)) < array(idx(l))  ) call swap(idx(l+1),idx(l))
          if (array(idx(r))   < array(idx(l+1))) then
             call swap(idx(r),idx(l+1))
             if (array(idx(l+1)) < array(idx(l))) call swap(idx(l+1),idx(l))
          end if
          medidx = idx(l+1)
          medval = array(medidx)

          ! Then run up from first (non-trivial) element, and down from last
          ! element, swapping things as need be until i and j cross
          i = l+1; j = r
          do
             do 
                i = i + 1
                if (array(idx(i)) >= medval) exit
             end do
             do 
                j = j - 1
                if (array(idx(j)) <= medval) exit
             end do
             if (i > j) exit
             call swap(idx(i), idx(j))
          end do
          ! now insert medval (the partitioning element) into the
          ! correct position
          idx(l+1) = idx(j)
          idx(j)   = medidx

          ! now put things onto the stack
          if (ixstack+2 > stack_size) error stop 'stack too small'
          stack(ixstack+1) = lrpair(l,j-1)
          stack(ixstack+2) = lrpair(j,r)
          ixstack = ixstack + 2
       end if
       
       if (ixstack == 0) exit
    end do
    
  end subroutine indexx_int


  subroutine swap_dp(a,b)
    real(dp), intent(inout) :: a,b
    real(dp)                :: dummy
    dummy=a
    a=b
    b=dummy
  end subroutine swap_dp

  subroutine swap_int(a,b)
    integer, intent(inout) :: a,b
    integer                :: dummy
    dummy=a
    a=b
    b=dummy
  end subroutine swap_int

end module sort
