// BlockSort.cpp

#include "StdAfx.h"

#include "BlockSort.h"

#include "Common/Alloc.h"

namespace NCompress {

static const int kNumHashBytes = 2;
static const UInt32 kNumHashValues = 1 << (kNumHashBytes * 8);

static const int kNumFlagsBits = 5; // 32 Flags in UInt32 word 
static const UInt32 kNumFlagsInWord = (1 << kNumFlagsBits);
static const UInt32 kFlagsMask = kNumFlagsInWord - 1;
static const UInt32 kAllFlags = 0xFFFFFFFF;

bool CBlockSorter::Create(UInt32 blockSizeMax)
{
  if (Indices != 0 && blockSizeMax == BlockSizeMax)
    return true;
  Free();
  BlockSizeMax = blockSizeMax;
  Indices = (UInt32 *)::BigAlloc((blockSizeMax * 2 + 
      ((blockSizeMax + kFlagsMask) >> kNumFlagsBits) + kNumHashValues) * sizeof(UInt32));
  return (Indices != 0);
}

void CBlockSorter::Free()
{
  ::BigFree(Indices);
  Indices = 0;
}

// SortGroup - is recursive Radix-Range-Sort function with Bubble-Sort optimization
// It uses both mask & maskSize (Range-Sort), since it can change values (Groups) 
// during sorting
// returns: 0 - if there are groups, 1 - no more groups
UInt32 CBlockSorter::SortGroup(UInt32 groupOffset, UInt32 groupSize, UInt32 mask, UInt32 maskSize)
{
  if (groupSize <= 2)
  {
    if (groupSize <= 1)
      return 0; 
    UInt32 *ind2 = Indices + groupOffset;
    UInt32 stringPos = ind2[0] + NumSortedBytes;
    if (stringPos >= BlockSize)
      stringPos -= BlockSize;
    UInt32 group = Groups[stringPos];
    stringPos = ind2[1] + NumSortedBytes;
    if (stringPos >= BlockSize)
      stringPos -= BlockSize;
    if (group == Groups[stringPos])
      return 1;
    if (group > Groups[stringPos])
    {
      UInt32 temp = ind2[0];
      ind2[0] = ind2[1];
      ind2[1] = temp;
    }
    Flags[groupOffset >> kNumFlagsBits] &= ~(1 << (groupOffset & kFlagsMask));
    Groups[ind2[1]] = groupOffset + 1;
    return 0;
  }

  // Check that all strings are in one group (cannot sort)
  UInt32 *ind2 = Indices + groupOffset;
  {
    UInt32 stringPos = ind2[0] + NumSortedBytes;
    if (stringPos >= BlockSize)
      stringPos -= BlockSize;
    UInt32 group = Groups[stringPos];
    UInt32 j;
    for (j = 1; j < groupSize; j++)
    {
      stringPos = ind2[j] + NumSortedBytes;
      if (stringPos >= BlockSize)
        stringPos -= BlockSize;
      if (Groups[stringPos] != group)
        break;
    }
    if (j == groupSize)
      return 1;
  }

  if (groupSize <= 15)
  {
    // Bubble-Sort
    UInt32 lastChange = groupSize;
    do
    {
      UInt32 stringPos = ind2[0] + NumSortedBytes;
      if (stringPos >= BlockSize)
        stringPos -= BlockSize;
      UInt32 group = Groups[stringPos];

      UInt32 sortSize = lastChange;
      lastChange = 0;
      for (UInt32 j = 1; j < sortSize; j++)
      {
        stringPos = ind2[j] + NumSortedBytes;
        if (stringPos >= BlockSize)
          stringPos -= BlockSize;
        if (Groups[stringPos] < group)
        {
          UInt32 temp = ind2[j];
          ind2[j] = ind2[j - 1];
          ind2[j - 1] = temp;
          lastChange = j;
        }
        else
          group = Groups[stringPos];
      }
    }
    while(lastChange > 1);

    // Write Flags
    UInt32 stringPos = ind2[0] + NumSortedBytes;
    if (stringPos >= BlockSize)
      stringPos -= BlockSize;
    UInt32 group = Groups[stringPos];

    UInt32 j;
    for (j = 1; j < groupSize; j++)
    {
      stringPos = ind2[j] + NumSortedBytes;
      if (stringPos >= BlockSize)
        stringPos -= BlockSize;
      if (Groups[stringPos] != group)
      {
        group = Groups[stringPos];
        UInt32 t = groupOffset + j - 1;
        Flags[t >> kNumFlagsBits] &= ~(1 << (t & kFlagsMask));
      }
    }

    // Write new Groups values and Check that there are groups
    UInt32 thereAreGroups = 0; 
    for (j = 0; j < groupSize; j++)
    {
      UInt32 group = groupOffset + j;
      while (true)
      {
        Groups[ind2[j]] = group;
        if ((Flags[(groupOffset + j) >> kNumFlagsBits] & (1 << ((groupOffset + j) & kFlagsMask))) == 0)
          break;
        j++;
        thereAreGroups = 1;
      }
    }
    return thereAreGroups;
  }

  // Radix-Range Sort
  UInt32 i;
  do
  {
    if (maskSize == 0)
      return 1;
    UInt32 j = groupSize;
    i = 0;
    do
    {
      UInt32 stringPos = ind2[i] + NumSortedBytes;
      if (stringPos >= BlockSize)
        stringPos -= BlockSize;
      if (Groups[stringPos] >= mask)
      {
        for (j--; j > i; j--)
        {
          stringPos = ind2[j] + NumSortedBytes;
          if (stringPos >= BlockSize)
            stringPos -= BlockSize;
          if (Groups[stringPos] < mask)
          {
            UInt32 temp = ind2[i];
            ind2[i] = ind2[j];
            ind2[j] = temp;
            break;
          }
        }
        if (i >= j)
          break;
      }
    }
    while(++i < j);
    maskSize >>= 1;
    if (i == 0)
      mask += maskSize;
    else if (i == groupSize)
      mask -= maskSize;
    else 
      break;
  }
  while(true);
  UInt32 t = (groupOffset + i - 1);
  Flags[t >> kNumFlagsBits] &= ~(1 << (t & kFlagsMask));

  for (UInt32 j = i; j < groupSize; j++)
    Groups[ind2[j]] = groupOffset + i;

  UInt32 res = SortGroup(groupOffset, i, mask - maskSize, maskSize);
  return res | SortGroup(groupOffset + i, groupSize - i, mask + maskSize, maskSize);
}

UInt32 CBlockSorter::Sort(const Byte *data, UInt32 blockSize)
{
  BlockSize = blockSize;
  UInt32 *counters = Indices + blockSize;
  Groups = counters + kNumHashValues;
  Flags = Groups + blockSize;
  UInt32 i;

  // Radix-Sort for 2 bytes
  for (i = 0; i < kNumHashValues; i++)
    counters[i] = 0;
  for (i = 0; i < blockSize - 1; i++)
    counters[((UInt32)data[i] << 8) | data[i + 1]]++;
  counters[((UInt32)data[i] << 8) | data[0]]++;

  {
    {
      UInt32 numWords = (blockSize + kFlagsMask) >> kNumFlagsBits;
      for (i = 0; i < numWords; i++)
        Flags[i] = kAllFlags;
    }

    UInt32 sum = 0;
    for (i = 0; i < kNumHashValues; i++)
    {
      UInt32 groupSize = counters[i];
      if (groupSize > 0)
      {
        UInt32 t = sum + groupSize - 1;
        Flags[t >> kNumFlagsBits] &= ~(1 << (t & kFlagsMask));
        sum += groupSize;
      }
      counters[i] = sum - groupSize;
    }

    for (i = 0; i < blockSize - 1; i++)
      Groups[i] = counters[((UInt32)data[i] << 8) | data[i + 1]];
    Groups[i] = counters[((UInt32)data[i] << 8) | data[0]];

    for (i = 0; i < blockSize - 1; i++)
      Indices[counters[((UInt32)data[i] << 8) | data[i + 1]]++] = i;
    Indices[counters[((UInt32)data[i] << 8) | data[0]]++] = i;
  }

  UInt32 mask;
  for (mask = 2; mask < blockSize; mask <<= 1);
  mask >>= 1;
  for (NumSortedBytes = kNumHashBytes; true; NumSortedBytes <<= 1)
  {
    UInt32 newLimit = 0;
    for (i = 0; i < blockSize;)
    {
      if ((Flags[i >> kNumFlagsBits] & (1 << (i & kFlagsMask))) == 0)
      {
        i++;
        continue;
      }
      UInt32 groupSize;
      for(groupSize = 1; 
        (Flags[(i + groupSize) >> kNumFlagsBits] & (1 << ((i + groupSize) & kFlagsMask))) != 0; 
        groupSize++);
      
      groupSize++;
      
      if (NumSortedBytes >= blockSize)
        for (UInt32 j = 0; j < groupSize; j++)
        {
          UInt32 t = (i + j);
          Flags[t >> kNumFlagsBits] &= ~(1 << (t & kFlagsMask));
          Groups[Indices[t]] = t;
        }
      else
        if (SortGroup(i, groupSize, mask, mask) != 0)
          newLimit = i + groupSize;
      i += groupSize;
    }
    if (newLimit == 0)
      break;
  }
  return Groups[0];
}

}
