#include <iostream>
#include <vector>
#include <cassert>
#include <ctime>


template <class T>
void print(const std::string &s, std::vector<T>& data, int start = -1, int end = -1, int pivot = -1) {
  if (data.size() > 20) return;
  if (start == -1) start = 0;
  if (end == -1) end = data.size()-1;
  std::cout << s << "  ";
  for (unsigned int i=0; i< data.size(); ++i) {
    std::cout << ((i == start) ? "[" : " ");
    if (i == pivot) {
      std::cout << " (" << data[i] << ") ";
    } else {
      std::cout << "  " << data[i] << "  ";
    }
    std::cout << ((i == end) ? "]" : " "); 
  } 
  std::cout << std::endl;
}


template <class T>
int partition(std::vector<T>& data, int start, int end, int& partitions, int& compares, int& swaps) {
  partitions++;
  // We have a few options for choosing the pivot:
  //int pivot = start;
  // int pivot = end;
  int pivot = (start+end)/2;  // midpoint
  // int pivot = start+std::rand()%(end+1-start);
  print("partition: ",data,start,end,pivot);
  // Simultaneously walk from both ends of the range looking for a pair of values to swap
  int i = start;
  int j = end;
  while (true) {
    // Search the low range for an element greater than the pivot
    while (data[i] <= data[pivot] && i < pivot) { compares++; i++; }
    // Search the high range for an element less than the pivot
    while (data[j] >= data[pivot] && j > pivot) { compares++; j--; }
    // Swap the values
    if (i < j) {
      compares++; swaps++; std::swap(data[i], data[j]);
      // If the pivot value was swapped, update the pivot index
      if (i == pivot) pivot=j;
      else if (j == pivot) pivot=i;
      print("  swap "+std::to_string(i)+"&"+std::to_string(j)+" ",data,start,end,pivot);
    } 
    else { compares++; break; }
  }
  print("finished:  ",data,start,end,pivot);
  return j;
}


template <class T>
void quicksort(std::vector<T>& data, int start, int end, int& partitions, int& compares, int& swaps) {
  if (start < end) {
    // partition the data -- after this call the PIVOT will be in its final position
    int pivot = partition(data, start, end, partitions, compares, swaps);

    // DEBUGGING: verify partition is complete
    for (int i = start; i < pivot; i++) { assert (data[i] <= data[pivot]); }
    for (int i = pivot+1; i <= end; i++) { assert (data[i] >= data[pivot]); }

    // recurse on data before and after the pivot
    quicksort(data, start, pivot-1, partitions, compares, swaps);
    quicksort(data, pivot+1, end, partitions, compares, swaps);

    // DEBUGGING: verify range is sorted
    for (int i = start; i < end; i++) { assert (data[i] <= data[i+1]); }
  }
}


// DRIVER FUNCTION
template <class T>
void quicksort(std::vector<T>& data) {
  int num_partitions = 0;
  int num_compares = 0;
  int num_swaps = 0;
  print("before:    ",data);
  quicksort(data, 0, data.size()-1, num_partitions, num_compares, num_swaps);
  print("after:     ",data);

  // ANALYSIS:  measure the cost of the algorithm
  std::cout << "num partitions = " << num_partitions << std::endl;
  std::cout << "num compares = " << num_compares << std::endl;
  std::cout << "num swaps = " << num_swaps << std::endl;
}


int main() {

  std::srand(time(NULL));

  std::vector<int> pts;

  pts = { 2, 9, 5, 1, 6, 3, 8, 4, 7 };
  //for (int i = 0; i < 1000; i++) { pts.push_back(std::rand()%1000); }
  //for (int i = 0; i < 1000; i++) { pts.push_back(i); }
  //for (int i = 0; i < 1000; i++) { pts.push_back(1000-i); }

  quicksort(pts);
}

