// Copyright (C) 2005  Davis E. King (davisking@users.sourceforge.net)
// License: Boost Software License   See LICENSE.txt for the full license.
#ifndef DLIB_SORt_
#define DLIB_SORt_

#include "algs.h"
#include <functional>

namespace dlib
{

// ----------------------------------------------------------------------------------------

    template <
        typename T,
        typename compare
        >
    inline void qsort_array (
        T& array,
        unsigned long left,
        unsigned long right,
        const compare& comp 
    );
    /*!
        requires
            - T implements operator[]                                 
            - the items in array must be comparable by comp where comp is a function
              object with the same syntax as std::less<>
            - the items in array must be swappable by a global swap()   
            - left and right are within the bounds of array
              i.e. array[left] and array[right] are valid elements
            - left <= right
        ensures
            - for all elements in #array between and including left and right the 
              ith element is < the i+1 element
            - sorts using a quick sort algorithm
    !*/ 

// ----------------------------------------------------------------------------------------

    template <
        typename T,
        typename compare
        >
    void hsort_array (
        T& array,
        unsigned long left,
        unsigned long right,
        const compare& comp 
    );
    /*!
        requires
            - T implements operator[]                                 
            - the items in array must be comparable by comp where comp is a function
              object with the same syntax as std::less<>
            - the items in array must be swappable by a global swap()   
            - left and right are within the bounds of array
              i.e. array[left] and array[right] are valid elements
            - left <= right
        ensures
            - for all elements in #array between and including left and right the 
              ith element is < the i+1 element
            - sorts using a heapsort algorithm
    !*/ 

// ----------------------------------------------------------------------------------------

    template <
        typename T,
        typename compare
        >
    void isort_array (
        T& array,
        unsigned long left,
        unsigned long right,
        const compare& comp 
    );
    /*!
        requires
            - T implements operator[]                                 
            - the items in array must be comparable by comp where comp is a function
              object with the same syntax as std::less<>
            - the items in array must be swappable by a global swap()   
            - left and right are within the bounds of array
              i.e. array[left] and array[right] are valid elements
            - left <= right
        ensures
            - for all elements in #array between and including left and right the 
              ith element is < the i+1 element
            - sorts using an insertion sort algorithm
    !*/

// ----------------------------------------------------------------------------------------

    template <
        typename T
        >
    inline void qsort_array (
        T& array,
        unsigned long left,
        unsigned long right
    ); 
    /*!
        requires
            - T implements operator[]                                 
            - the items in array must be comparable by std::less         
            - the items in array must be swappable by a global swap()   
            - left and right are within the bounds of array
              i.e. array[left] and array[right] are valid elements
            - left <= right
        ensures
            - for all elements in #array between and including left and right the 
              ith element is < the i+1 element
            - sorts using a quick sort algorithm
    !*/ 

// ----------------------------------------------------------------------------------------

    template <
        typename T
        >
    void hsort_array (
        T& array,
        unsigned long left,
        unsigned long right
    );
    /*!
        requires
            - T implements operator[]                                 
            - the items in array must be comparable by std::less         
            - the items in array must be swappable by a global swap()   
            - left and right are within the bounds of array
              i.e. array[left] and array[right] are valid elements
            - left <= right
        ensures
            - for all elements in #array between and including left and right the 
              ith element is < the i+1 element
            - sorts using a heapsort algorithm
    !*/ 

// ----------------------------------------------------------------------------------------

    template <
        typename T
        >
    void isort_array (
        T& array,
        unsigned long left,
        unsigned long right
    ); 
    /*!
        requires
            - T implements operator[]                                 
            - the items in array must be comparable by std::less      
            - the items in array must be swappable by a global swap()   
            - left and right are within the bounds of array
              i.e. array[left] and array[right] are valid elements
            - left <= right
        ensures
            - for all elements in #array between and including left and right the 
              ith element is < the i+1 element
            - sorts using an insertion sort algorithm
    !*/

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
//                            IMPLEMENTATION DETAILS
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

    namespace sort_helpers
    {
        template <typename T>
        inline const std::less<T> comp (const T&)
        {
            return std::less<T>();
        }

        template <
            typename T,
            typename Y,
            typename compare
            >
        inline unsigned long qsort_partition (
            T& array,
            Y& pivot,
            const unsigned long left,
            const unsigned long right,
            const compare& comp
        )
        /*!
            requires
                - &pivot == &array[right]
                - T implements operator[]                             
                - the items in array must be comparable by comp     
                - left and right are within the bounts of the array
                - left < right
            ensures
                - returns a number called partition_element such that:
                    - left <= partition_element <= right                              
                    - all elements in #array < #array[partition_element] have 
                    indices >= left and < partition_element                         
                    - all elements in #array > #array[partition_element] have 
                    indices > partition_element and <= right
        !*/
        {
            DLIB_ASSERT (&pivot == &array[right] && left < right,
                    "\tunsigned long qsort_partition()"
                    << "\n\t&pivot:        " << &pivot
                    << "\n\t&array[right]: " << &array[right]
                    << "\n\tleft:          " << left
                    << "\n\tright:         " << right );

            exchange(array[(right-left)/2 +left],pivot);

            unsigned long i = left;
            for (unsigned long j = left; j < right; ++j)
            {
                if (comp(array[j] , pivot))
                {
                    swap(array[i],array[j]);
                    ++i;
                }
            }
            exchange(array[i],pivot);
            
            return i;
        }

// ----------------------------------------------------------------------------------------

        template <
            typename T,
            typename compare
            >
        void qsort_array_main (
            T& array,
            const unsigned long left,
            const unsigned long right,
            unsigned long depth_check,
            const compare& comp
        )
        /*!
            requires
                - T implements operator[]                                 
                - the items in array must be comparable by comp         
                - the items in array must be swappable by a global swap()   
                - left and right are within the bounds of array
                  i.e. array[left] and array[right] are valid elements
            ensures
                - for all elements in #array between and including left and right the 
                  ith element is < the i+1 element
                - will only recurse about as deep as log(depth_check) calls
                - sorts using a quick sort algorithm
        !*/ 
        {
            if ( left < right)
            {
                if (right-left < 30 || depth_check == 0)
                {
                    hsort_array(array,left,right,comp);
                }
                else
                {
                    // The idea here is to only let quick sort go about log(N)
                    // calls deep before it kicks into something else.
                    depth_check >>= 1;
                    depth_check += (depth_check>>4);

                    unsigned long partition_element = 
                        qsort_partition(array,array[right],left,right,comp);
                    
                    if (partition_element > 0)
                        qsort_array_main(array,left,partition_element-1,depth_check,comp);
                    qsort_array_main(array,partition_element+1,right,depth_check,comp);
                }
            }
        }

// ----------------------------------------------------------------------------------------

        template <
            typename T,
            typename compare
            >
        void heapify (
            T& array,
            const unsigned long start,
            const unsigned long end,
            unsigned long i,
            const compare& comp
        )
        /*!
            requires
                - T implements operator[]                                 
                - the items in array must be comparable by comp        
                - the items in array must be swappable by a global swap()   
                - start, end, and i are within the bounds of array
                  i.e. array[start], array[end], and array[i] are valid elements
                - start <= i <= end
                - array[i/2] is a max heap
                - array[i/2+1] is a max heap
                - start and end specify the range of the array we are working with.
            ensures
                - array[i] is now a max heap
        !*/
        {
            DLIB_ASSERT (start <= i && i <= end,
                    "\tvoid heapify()"
                    << "\n\tstart:   " << start 
                    << "\n\tend:     " << end 
                    << "\n\ti:       " << i );

            bool keep_going = true;            
            unsigned long left;
            unsigned long right;   
            unsigned long largest; 
            while (keep_going)
            {
                keep_going = false;
                left = (i<<1)+1-start;
                right = left+1;

                if (left <= end && comp(array[i] , array[left]))
                    largest = left;
                else
                    largest = i;

                if (right <= end && comp(array[largest] , array[right]))
                    largest = right;

                if (largest != i)
                {
                    exchange(array[i],array[largest]);
                    i = largest;
                    keep_going = true;
                }
            }
        }

// ----------------------------------------------------------------------------------------
    }
// ----------------------------------------------------------------------------------------


    template <
        typename T
        >
    inline void qsort_array (
        T& array,
        unsigned long left,
        unsigned long right
    ) 
    {
        using namespace sort_helpers;
        qsort_array(array,left,right,comp(array[left]));
    }

// ----------------------------------------------------------------------------------------

    template <
        typename T
        >
    void hsort_array (
        T& array,
        unsigned long left,
        unsigned long right
    )
    {
        using namespace sort_helpers;
        hsort_array(array,left,right,comp(array[left]));
    }

// ----------------------------------------------------------------------------------------

    template <
        typename T
        >
    void isort_array (
        T& array,
        unsigned long left,
        unsigned long right
    ) 
    {
        using namespace sort_helpers;
        isort_array(array,left,right,comp(array[left]));
    }

// ----------------------------------------------------------------------------------------

    template <
        typename T,
        typename compare
        >
    void isort_array (
        T& array,
        const unsigned long left,
        const unsigned long right,
        const compare& comp
    )
    {
        DLIB_ASSERT (left <= right,
                "\tvoid isort_array()"
                << "\n\tleft:          " << left
                << "\n\tright:         " << right );
        using namespace sort_helpers;

        unsigned long pos;
        for (unsigned long i = left+1; i <= right; ++i)
        {
            // everything from left to i-1 is sorted.
            pos = i;
            for (unsigned long j = i-1; comp(array[pos] , array[j]); --j)
            {
                exchange(array[pos],array[j]);
                pos = j;
                
                if (j == left)
                    break;
            }
        }
    }

// ----------------------------------------------------------------------------------------

    template <
        typename T,
        typename compare
        >
    void qsort_array (
        T& array,
        const unsigned long left,
        const unsigned long right,
        const compare& comp
    )
    {      
        DLIB_ASSERT (left <= right,
                "\tvoid qsort_array()"
                << "\n\tleft:          " << left
                << "\n\tright:         " << right );

        sort_helpers::qsort_array_main(array,left,right,right-left,comp);
    }

// ----------------------------------------------------------------------------------------
    
    template <
        typename T,
        typename compare
        >
    void hsort_array (
        T& array,
        const unsigned long left,
        const unsigned long right,
        const compare& comp
    )
    {
        DLIB_ASSERT (left <= right,
                "\tvoid hsort_array()"
                << "\n\tleft:          " << left
                << "\n\tright:         " << right );

        if (right-left < 30)
        {
            isort_array(array,left,right,comp);
            return;
        }

        // turn array into a max heap
        for (unsigned long i = left+((right-left)>>1);; --i)
        {
            sort_helpers::heapify(array,left,right,i,comp);
            if (i == left)
                break;
        }

        // now sort the array
        for (unsigned long i = right; i > left;)
        {
            exchange(array[i],array[left]);
            sort_helpers::heapify(array,left,--i,left,comp);
        }
    }

// ----------------------------------------------------------------------------------------

}

#endif // DLIB_SORt_

