#define __STDC_LIMIT_MACROS
#include "live_plate.hpp"

#include <SDL.h>
#include <SDL_thread.h>

#include "mixer.hpp"

#include <iostream>
#include <utility>

using std::cout;
using std::endl;

using std::pair;
using std::make_pair;

namespace LivePlate {

	Plate plate;
	unsigned int tap = PlateSize / 2 * PlateSize + PlateSize / 2;
	int32_t gain = (6 << 16);
	unsigned int max_out = 0;
	int64_t played_samples = 0;

	SDL_mutex *hits_lock = NULL;

	list< PlateHit > hits;

	int render_thread(void *) {
		while (1) {
			vector< PlateHit > active_hits;
			{ //read in valid hits:
				SDL_LockMutex(hits_lock);
				played_samples += BufferBlockSize;
				list< PlateHit >::iterator h = hits.begin();
				while (h != hits.end()) {
					assert(h->start + (int)h->values.size() > 0);
					if (h->start < (int)BufferBlockSize) {
						active_hits.push_back(*h);
						//are we done with this one:
						if (h->start + (int)h->values.size() <= (int)BufferBlockSize) {
							if (h->period == 0) {
								list< PlateHit >::iterator prev = h;
								++h;
								hits.erase(prev);
							} else {
								assert(h->period >= BufferBlockSize);
								h->start = h->start + (int)h->period - (int)BufferBlockSize;
								++h;
							}
							continue;
						}
					}
					h->start -= (int)BufferBlockSize;
					++h;
				} //end while(h != hits.end())
				SDL_UnlockMutex(hits_lock);
			}
			Sint16 *block = Mixer::live.alloc_block();
			static int64_t last_value_offset = 0;
			static int64_t value_offset = 0;
			int64_t value_total = 0;
			int64_t max_value = 0;
			int64_t min_value = 0;
			unsigned int pos = 0;
			//unsigned int DEBUG_mods = 0;
			while (pos < BufferBlockSize) {
				unsigned int step = BufferBlockSize - pos;
				vector< pair< unsigned int, int32_t * > > current;
				for (typeof(active_hits.begin()) h = active_hits.begin(); h != active_hits.end(); ++h) {
					//case: we're before this one's start:
					if (h->start > (int)pos) {
						//don't step into it:
						if ((int)pos + (int)step > h->start) {
							step = h->start - pos;
						}
						continue;
					}
					//case: we're already past this one's end:
					if (h->start + h->values.size() <= pos) continue;
					if (pos + step >= h->start + h->values.size()) {
						step = h->start + h->values.size() - pos;
					}
					//So now we're inside this sample:
					assert((unsigned)(pos - h->start) < h->values.size());
					current.push_back(make_pair(h->tap, &(h->values[pos - h->start])));
				}
				assert(step > 0);
				for (unsigned int i = 0; i < step; ++i) {
					//plate.prev_pos[tap] = 0;
					plate.pos[tap] = 0;
					for (typeof(current.begin()) c = current.begin(); c != current.end(); ++c) {
						plate.pos[c->first] += c->second[i];
						//++DEBUG_mods;
					}
					plate.step();
					int64_t value = plate.pos[tap] - plate.prev_pos[tap];
					value_total += value;
					value += (value_offset * pos + last_value_offset * (BufferBlockSize - pos)) / BufferBlockSize;
					value = (value * gain) >> 16;
					/*
					//"smooth clipping" up to 18 bits.
					int sign = 1;
					if (value < 0) {
						sign = -1;
						value = -value;
					}
					if (value <= (1 << 14)) {
						//good.
					} else {
						value -= (1 << 14);
						value *= ((1 << 16) - (1 << 14));
						value /= ((1 << 18) - (1 << 14));
						value += (1 << 14);
					}
					value = value * sign;
					*/
					if (value > max_value) max_value = value;
					if (value < min_value) min_value = value;
					if (value > INT16_MAX) value = INT16_MAX;
					if (value < INT16_MIN) value = INT16_MIN;
					block[pos + i] = value;
				}
				pos += step;
			} //end while(pos);
			/*if (DEBUG_mods != 0) {
				cout << "Made " << DEBUG_mods << " changes." << endl;
			}*/
			//update running dc-removal:
			last_value_offset = value_offset;
			value_offset = -(value_total / BufferBlockSize);
			/*
			cout << endl;
			cout << "Value offset: " << value_offset << endl;
			cout << "Last value offset: " << last_value_offset << endl;
			cout << "Value total: " << value_total << endl;
			cout <<  "Range: " << max_value << " to " << min_value << endl;
			*/

			max_value = max_value - min_value;
			if (max_value > UINT32_MAX) {
				max_value = UINT32_MAX;
			}
			if (max_value >= INT16_MAX) {
				cout << "Shrinking" << endl;
				plate.shrink();
			}
			max_out = max_value;
			Mixer::live.send_block(block);
		}
		return 0;
	}

	SDL_Thread *render_thread_ptr = NULL;

	void init() {
		assert(!render_thread_ptr);
		render_thread_ptr = SDL_CreateThread(render_thread, NULL);
		assert(!hits_lock);
		hits_lock = SDL_CreateMutex();
	}

	void deinit() {
		cout << "Killing off live plate thread..."; cout.flush();
#ifdef MACOSX
		cout << " just kidding, that makes this freeze on osx." << endl;
#else
		//TODO: signaling, queue flushing, and all that rot.
		SDL_KillThread(render_thread_ptr);
		cout << "  dead!" << endl;
		render_thread_ptr = NULL;
		SDL_DestroyMutex(hits_lock);
		hits_lock = NULL;
#endif
	}

}
