/* $NoKeywords: $ */
/*
//
// Copyright (c) 1993-2011 Robert McNeel & Associates. All rights reserved.
// OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert
// McNeel & Assoicates.
//
// THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
// ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF
// MERCHANTABILITY ARE HEREBY DISCLAIMED.
//				
// For complete openNURBS copyright information see <http://www.opennurbs.org>.
//
////////////////////////////////////////////////////////////////
*/

/*
See opennurbs_sort.cpp for examples showing how to use
opennurbs_quacksort_template.h to define type specific
sorting functions.
*/

#if !defined(ON_QSORT_FNAME)
#error Define ON_QSORT_FNAME macro before including opennurbs_quacksort_template.h
#endif

// ON_SORT_TEMPLATE_TYPE -> double, int, ....
#if !defined(ON_SORT_TEMPLATE_TYPE)

#define BASETYPE void *
#define DATATYPE unsigned char
#define DATAWIDTH m_width

#define Swap(a,b) m_swapfunc(a,b,m_width)

#if defined(ON_SORT_TEMPLATE_USE_CONTEXT)
// use a compare function with context parameter
#define GreaterThan(A,B) m_compare(m_context,A,B) > 0
#else
// use a compare function without context parameter
#define GreaterThan(A,B) m_compare(A,B) > 0
#endif

#else

#define BASETYPE ON_SORT_TEMPLATE_TYPE *
#define DATATYPE ON_SORT_TEMPLATE_TYPE
#define DATAWIDTH 1

#if defined(ON_SORT_TEMPLATE_USE_SWAP)
#define Swap(a,b) m_swapfunc(a,b,m_width)
#else
// use intrinsic assigment
#define Swap(a,b) ON_SORT_TEMPLATE_TYPE tmp = *a; *a = *b; *b = tmp
#endif

#if defined(ON_SORT_TEMPLATE_COMPARE)
// use a compare function like strcmp for char* strings
#define GreaterThan(A,B) ON_SORT_TEMPLATE_COMPARE(A,B) > 0
#else
// use intrinsic type compares
#define GreaterThan(A,B) *A > *B
#endif

#endif

#if !defined(ON_QUACKSORT_SWAP_FUNCS_DEFINED)
#if !defined(ON_SORT_TEMPLATE_TYPE) || defined(ON_SORT_TEMPLATE_USE_SWAP)

// In some files this template is used multiple times.
// The ON_QUACKSORT_SWAP_FUNCS_DEFINED define prevents
// multiple definitions of the static Swap*() functions.
#define ON_QUACKSORT_SWAP_FUNCS_DEFINED

static void SwapChars( unsigned char* a, unsigned char* b, size_t width)
{
  do
  {
    unsigned char x = *a;
    *a++ = *b;
    *b++ = x;
  }
  while( --width);
}

static void SwapInts( unsigned char* a, unsigned char* b, size_t width)
{
  ON__UINT32* ai = (ON__UINT32*)a;
  ON__UINT32* bi = (ON__UINT32*)b;
  do
  {
    ON__UINT32 x = *ai;
    *ai++ = *bi;
    *bi++ = x;
    width -= sizeof(x);
  }
  while( width);
}

static void SwapBigInts( unsigned char* a, unsigned char* b, size_t width)
{
  ON__UINT64* ai = (ON__UINT64*)a;
  ON__UINT64* bi = (ON__UINT64*)b;
  do
  {
    ON__UINT64 x = *ai;
    *ai++ = *bi;
    *bi++ = x;
    width -= sizeof(x);
  }
  while( width);
}

#endif
#endif

// implementation of quick sort with minimum swaps for partition sizes 4 and less
void ON_quacksort( 
  BASETYPE *base
  ,size_t nel
#if !defined(ON_SORT_TEMPLATE_TYPE)
  ,size_t width
#if defined(ON_SORT_TEMPLATE_USE_CONTEXT)
  ,int (*compar)(void*, const void *, const void *)
  ,void* context
#else
  ,int (*compar)(const void *, const void *)
#endif
#endif
  )
{
  class CSorter
  {
  public:
    DATATYPE *m_base;
    size_t m_nel;
    const size_t m_width;
    int (*m_compar)(const void *, const void *);
    void (*m_swapfunc)(unsigned char *, unsigned char *, size_t width);
    unsigned int m_rnd;
//#if defined(ON_SORT_TEMPLATE_TYPE) && !defined(ON_SORT_TEMPLATE_USE_SWAP)
//    ON_SORT_TEMPLATE_TYPE m_tmp;
//#endif

    CSorter( 
      DATATYPE *base
      , size_t nel
      , size_t width
      , int (*compar)(const void *, const void *)
      )
      : m_base((DATATYPE*)base)
      , m_nel(nel)
      , m_width(width)
      , m_compar(compar)
      , m_rnd(62538161)
    {
      // When width is a multiple of 8 or 4 (with most arrays it probably is),
      // use faster integer swappers instead of byte swapper
      if ( 0 == width%sizeof(ON__UINT64))
        m_swapfunc = SwapBigInts;
      else if ( 0 == width%sizeof(ON__UINT32))
        m_swapfunc = SwapInts;
      else
        m_swapfunc = SwapChars;
    };

    ~CSorter() {};

    DATATYPE* Pivot( DATATYPE* base, size_t count)
    {
      // Uses local quick and dirty pseudorandom number generator to 
      // give a fuzzy answer to avoid having the data be arranged in 
      // a way that mechanically always picking the pivot the same way 
      // affects the speed. Mostly affects chevron etc. patterns.
      // 
      // Totally random pivot would guarantee O(nlogn) worst case, but 
      // does not perform as well on sorted or nearly sorted sets.

      m_rnd *= 1664525;
      m_rnd += 1013904223;
      unsigned int dice = (m_rnd>>16)&7;

      size_t p=count>>1; // 1/2

      if ( dice&4)
        p += count>>3;  // +1/8
      if ( dice&2)
        p -= count>>4;  // -1/16
      if ( dice&1)
        p -= count>>5;  // -1/32

      return base + p*DATAWIDTH;
    }

    void SortSmallRange( DATATYPE* p0, size_t count)
    {
      // use minimum compares and swaps for 2 to 4 items
      switch (count)
      {
      case 2:
        {
          DATATYPE* p1 = p0 + DATAWIDTH;
          if ( GreaterThan( p0, p1)) { Swap( p0, p1);}
          return;
        }
      case 3:
        {
          DATATYPE* p1 = p0 + DATAWIDTH;
          DATATYPE* p2 = p1 + DATAWIDTH;
          bool b = false;
          if ( GreaterThan( p0, p1)) { Swap( p0, p1); b = true;}
          if ( GreaterThan( p1, p2)) { Swap( p1, p2); b = true;}
          if ( b && GreaterThan( p0, p1)) { Swap( p0, p1);}
          return;
        }
      case 4:
        {
          DATATYPE* p1 = p0 + DATAWIDTH;
          DATATYPE* p2 = p1 + DATAWIDTH;
          DATATYPE* p3 = p2 + DATAWIDTH;
          if ( GreaterThan( p0, p3)) { Swap( p0, p3);}
          if ( GreaterThan( p1, p2)) { Swap( p1, p2);}
          bool b = false;
          if ( GreaterThan( p2, p3)) { Swap( p2, p3); b = true;}
          if ( GreaterThan( p0, p1)) { Swap( p0, p1); b = true;}
          if ( b && GreaterThan( p1, p2)) { Swap( p1, p2);}
          return;
        }
      }
    }

    void SortRange( DATATYPE* left, DATATYPE* right)
    { 
      while ( left<right)
      { 
        size_t count = (right-left)/DATAWIDTH+1;

        if ( count < 5)
          return SortSmallRange( left, count);

        DATATYPE* pivotleft;
        DATATYPE* pivotright;

        // partition range
        { 
          pivotleft = Pivot( left, count);

          // move pivot to left end
          Swap( left, pivotleft);

          pivotleft = left;
          pivotright = right + DATAWIDTH;
          
          // move =< pivot to left, and > pivot to right
          for(;;)
          {
            // find next first item > pivot
            pivotleft += DATAWIDTH;
            if ( pivotleft >= pivotright)
              break;
            if ( !GreaterThan( pivotleft, left))
              continue;

            // find next last item =< pivot
            do
            {
              pivotright -= DATAWIDTH;
              if ( pivotleft >= pivotright)
                goto END; // to quickly exit a nested loop
            }
            while( GreaterThan( pivotright, left));

            Swap( pivotleft, pivotright);
          }

          END:

          pivotright -= DATAWIDTH;
          // move pivot to final place
          Swap( left, pivotright);
          pivotleft = pivotright;

          // avoid overhead when not likely that there are multiple items == pivot
          if ( pivotright >= right)
          {
            // the whole range is less or equal than pivot
            // check if there are values == pivot left of it. Speeds up sorting arrays with all or lots of equal items.
            for ( pivotleft -= DATAWIDTH; pivotleft > left; pivotleft -= DATAWIDTH)
            {
              if ( GreaterThan( pivotright, pivotleft))
                break;
            }
            pivotleft += DATAWIDTH;
          }
        }

        // limit max recursion depth to log(nel) by only recursing shorter part
        if ( pivotleft-left < right-pivotright)
        {
          // lower part is shorter
          SortRange( left, pivotleft-DATAWIDTH);
          left = pivotright+DATAWIDTH;
        }
        else
        {
          // upper part is shorter
          SortRange( pivotright+DATAWIDTH, right);
          right = pivotleft-DATAWIDTH;
        }
      }
    }

    void Sort()
    {
      SortRange( m_base, m_base + (m_nel-1)*DATAWIDTH);
    }
  };

  if ( !base || nel < 2 )
    return;
#if !defined(ON_SORT_TEMPLATE_TYPE)
  if ( width < 1 || !compar)
    return;
#endif

  CSorter sorter( base, nel, width, compar);
  sorter.Sort();
}

#undef Swap
#undef GreaterThan
#undef DATAWIDTH
#undef DATATYPE
