#include "foobar2000-sdk-pch.h"
#include "foosort.h"
#include "threadPool.h"
#include "genrand.h"

#define FOOSORT_PROFILING 0

namespace {
#if FOOSORT_PROFILING
	typedef pfc::hires_timer foosort_timer;
#endif

	class foosort {
	public:
		foosort(pfc::sort_callback & cb, abort_callback & a) : m_abort(a), p_callback(cb) {}

		foosort fork() {
			foosort ret ( * this );
			ret.genrand = genrand_service::g_create();
			return ret;
		}

		size_t myrand(size_t cnt) {
			PFC_ASSERT(cnt == (uint32_t)cnt);
			return genrand->genrand((uint32_t)cnt);
		}

		void squaresort(t_size const p_base, t_size const p_count) {
			const t_size max = p_base + p_count;
			for (t_size walk = p_base + 1; walk < max; ++walk) {
				for (t_size prev = p_base; prev < walk; ++prev) {
					p_callback.swap_check(prev, walk);
				}
			}
		}


		void _sort_2elem_helper(t_size & p_elem1, t_size & p_elem2) {
			if (p_callback.compare(p_elem1, p_elem2) > 0) pfc::swap_t(p_elem1, p_elem2);
		}

		t_size _pivot_helper(t_size const p_base, t_size const p_count) {
			PFC_ASSERT(p_count > 2);

			//t_size val1 = p_base, val2 = p_base + (p_count / 2), val3 = p_base + (p_count - 1);

			t_size val1 = myrand(p_count), val2 = myrand(p_count - 1), val3 = myrand(p_count - 2);
			if (val2 >= val1) val2++;
			if (val3 >= val1) val3++;
			if (val3 >= val2) val3++;

			val1 += p_base; val2 += p_base; val3 += p_base;

			_sort_2elem_helper(val1, val2);
			_sort_2elem_helper(val1, val3);
			_sort_2elem_helper(val2, val3);

			return val2;
		}

		void newsort(t_size const p_base, t_size const p_count, size_t concurrency) {
			if (p_count <= 4) {
				squaresort(p_base, p_count);
				return;
			}
			
			this->m_abort.check();
#if FOOSORT_PROFILING
			foosort_timer t;
			if ( concurrency > 1 ) t.start();
#endif

			t_size pivot = _pivot_helper(p_base, p_count);

			{
				const t_size target = p_base + p_count - 1;
				if (pivot != target) {
					p_callback.swap(pivot, target); pivot = target;
				}
			}


			t_size partition = p_base;
			{
				bool asdf = false;
				for (t_size walk = p_base; walk < pivot; ++walk) {
					const int comp = p_callback.compare(walk, pivot);
					bool trigger = false;
					if (comp == 0) {
						trigger = asdf;
						asdf = !asdf;
					} else if (comp < 0) {
						trigger = true;
					}
					if (trigger) {
						if (partition != walk) p_callback.swap(partition, walk);
						partition++;
					}
				}
			}
			if (pivot != partition) {
				p_callback.swap(pivot, partition); pivot = partition;
			}

			const auto base1 = p_base, count1 = pivot - p_base;
			const auto base2 = pivot + 1, count2 = p_count - (pivot + 1 - p_base);

			if (concurrency > 1) {

				size_t total = count1 + count2;
				size_t con1 = (size_t)((uint64_t)count1 * (uint64_t)concurrency / (uint64_t)total);
				if (con1 < 1) con1 = 1;
				if (con1 > concurrency - 1) con1 = concurrency - 1;
				size_t con2 = concurrency - con1;

#if FOOSORT_PROFILING
				FB2K_console_formatter() << "foosort pass: " << p_base << "+" << p_count << "(" << concurrency << ") took " << t.queryString();
				FB2K_console_formatter() << "foosort forking: " << base1 << "+" << count1 << "(" << con1 << ") + " << base2 << "+" << count2 << "(" << con2 << ")";
#endif
				pfc::counter cnt;
				fb2k::cpuThreadPool::runMultiHelper([&] {
					try {
						switch (cnt++) {
						case 0:
							{ foosort subsort = fork(); subsort.newsort(base1, count1, con1); }
							break;
						case 1:
							newsort(base2, count2, con2);
							break;
						}
					} catch (exception_aborted) {}
				}, 2);
				m_abort.check();
			} else {
				newsort(base1, count1, 1);
				newsort(base2, count2, 1);
			}
		}
	private:
		abort_callback & m_abort;
		pfc::sort_callback & p_callback;
		genrand_service::ptr genrand = genrand_service::g_create();
	};



}
namespace fb2k {
	void sort(pfc::sort_callback & cb, size_t count, size_t concurrency, abort_callback & aborter) {
#ifdef FOOSORT_LIMIT_THREADS
		if (concurrency > FOOSORT_LIMIT_THREADS) concurrency = FOOSORT_LIMIT_THREADS;
#endif
#if FOOSORT_PROFILING
		foosort_timer t; t.start();
#endif
		foosort theFooSort(cb, aborter);
		theFooSort.newsort(0, count, concurrency);
#if FOOSORT_PROFILING
		FB2K_console_formatter() << "foosort took: " << t.queryString();
#endif
	}
}
