////////////////////////////////
// Functions: Sort

MR4TH_SYMBOL void
sort_merge(void *data_raw, U32 itemsize, U32 count,
           SORT_Compare *compare, void *udata){
  ArenaTemp scratch = arena_get_scratch(0, 0);
  
  typedef struct SortRange{
    struct SortRange *next;
    U32 first;
    U32 opl;
  } SortRange;
  
  U8 *data = (U8*)data_raw;
  
  // identify already-sorted ranges
  SortRange *first_range = 0;
  SortRange *last_range = 0;
  {
    U64 i = 0;
    for (;i < count;){
      U64 first = i;
      U8 *ptr = data + itemsize*i;
      for (i += 1; i < count; i += 1, ptr += itemsize){
        if (compare(ptr + itemsize, ptr, udata) < 0){
          break;
        }
      }
      U64 opl = i;
      
      SortRange *range = push_array(scratch.arena, SortRange, 1);
      SLLQueuePush(first_range, last_range, range);
      range->first = first;
      range->opl = opl;
    }
  }
  
  // setup a swap buffer
  U8 *swap_data = push_array(scratch.arena, U8, count*itemsize);
  
  // sort from src to dst by merging
  U8 *src_data = data;
  U8  *dst_data = swap_data;
  for (;;){
    // check if we're done
    if (first_range == last_range){
      break;
    }
    
    // transfer ranges to a stack and reset the queue
    SortRange *range_stack = first_range;
    first_range = 0;
    last_range = 0;
    
    // merge neighboring ranges
    for (;;){
      // range popping
      SortRange *range1 = range_stack;
      if (range1 == 0){
        break;
      }
      SLLStackPop(range_stack);
      SortRange *range2 = range_stack;
      if (range2 == 0){
        U32 rfirst = range1->first;
        MemoryCopy(dst_data + rfirst*itemsize, src_data + rfirst*itemsize,
                   itemsize*(range1->opl - rfirst));
        SLLQueuePush(first_range, last_range, range1);
        break;
      }
      SLLStackPop(range_stack);
      
      Assert(range1->opl == range2->first);
      
      // array merge
      U64 i      = range1->first;
      U64 j1     = range1->first;
      U64 j1_opl = range1->opl;
      U64 j2     = range2->first;
      U64 j2_opl = range2->opl;
      
      for (;j1 < j1_opl && j2 < j2_opl;){
        U8* o1 = src_data + j1*itemsize;
        U8* o2 = src_data + j2*itemsize;
        if (compare(o1, o2, udata) <= 0){
          MemoryCopy(dst_data + i*itemsize, o1, itemsize);
          j1 += 1;
        }
        else{
          MemoryCopy(dst_data + i*itemsize, o2, itemsize);
          j2 += 1;
        }
        i += 1;
      }
      for (;j1 < j1_opl;){
        MemoryCopy(dst_data + i*itemsize, src_data + j1*itemsize, itemsize);
        j1 += 1;
        i += 1;
      }
      for (;j2 < j2_opl;){
        MemoryCopy(dst_data + i*itemsize, src_data + j2*itemsize, itemsize);
        j2 += 1;
        i += 1;
      }
      
      // combine into a single range on range queue
      range1->opl = range2->opl;
      SLLQueuePush(first_range, last_range, range1);
    }
    
    // after a pass swap src & dst
    Swap(U8*, src_data, dst_data);
  }
  
  // transfer data if it's in the wrong buffer
  if (src_data != data){
    MemoryCopy(data, src_data, itemsize*count);
  }
  
  arena_release_scratch(&scratch);
}
