#define __STDC_LIMIT_MACROS
#include "Jam.hpp"

#include <Graphics/GLExtensions.hpp>
#include <Graphics/GLSLShader.hpp>
#include <Graphics/Font.hpp>
#include <Vector/VectorGL.hpp>
#include <Basis/Error.hpp>

#include "Plate.hpp"
#include "live_plate.hpp"
#include "mixer.hpp"

#include <utility>
#include <limits>

using std::make_pair;
using std::numeric_limits;
using std::swap;

REQUIRE_GL_EXTENSION( GL_ARB_texture_rectangle )
REQUIRE_GL_EXTENSION( GL_ARB_shader_objects )
REQUIRE_GL_EXTENSION( GL_ARB_multitexture )

//some layout constants:
namespace {
	const float Width = 0.07f;
	const float Space = 0.007f;

	Graphics::ProgramObjectRef plate_shader = NULL;
	Graphics::ProgramObjectRef subpixel_shader = NULL;

	const unsigned int TDir = 1; //the main "time" direction.

	const float LoudLimit = 2.0f;
	const float BoredLimit = 60.0f;

	const float DMin = -10.0f;
	const float DMax = 100.0f;

	const float KMin =  -10.0f;
	const float KMax = 2000.0f;

	const float GainMin = 0.0f;
	const float GainMax = (1000 << 16);


}

Jam::Jam() {

	{
		Graphics::ShaderObjectRef frag = Graphics::get_shader_object("plate.glsl", GL_FRAGMENT_SHADER_ARB);
		assert(frag.ref);
		plate_shader = Graphics::get_program_object(frag);
		assert(plate_shader.ref);
		glUseProgramObjectARB(plate_shader->handle);
		glUniform1iARB(glGetUniformLocationARB(plate_shader->handle, "vibrate"), 0);
		glUniform1iARB(glGetUniformLocationARB(plate_shader->handle, "image"), 1);
		glUseProgramObjectARB(0);
	}

	{
		Graphics::ShaderObjectRef frag = Graphics::get_shader_object("subpixel.glsl", GL_FRAGMENT_SHADER_ARB);
		assert(frag.ref);
		subpixel_shader = Graphics::get_program_object(frag);
		assert(subpixel_shader.ref);
		glUseProgramObjectARB(subpixel_shader->handle);
		glUniform1iARB(glGetUniformLocationARB(subpixel_shader->handle, "image"), 0);
		glUseProgramObjectARB(0);
	}



	{ //allocate a blank image.
		vector< float > blank(PlateSize * PlateSize, 1.0f);
		images.push_back(new Image(&(blank[0])));
	}

	samples_per_beat = Mixer::Rate * 60 / 120;

	drag_box = NULL;

	mouse = make_vector(0.0f, 0.0f);

	image_scroll = 0;
	image_scroll_target = 0;

	close_hit = NULL;
	close_hit_dirty = true;

	//Set up layout with central 'main box' and side 'loops' and 'source' boxes:
	main_box.max.x = Graphics::aspectf();
	main_box.min.x =-Graphics::aspectf();
	plate_box.max.x = main_box.max.x - Width * 0.5f;
	plate_box.min.x = main_box.min.x + Width * 0.5f;

	main_box.max.y = 1.0f;
	main_box.min.y = main_box.max.y - plate_box.size().x;

	plate_box.min.y = main_box.min.y;
	plate_box.max.y = main_box.max.y;

	times.resize(2);

	times[0].box.min.x = main_box.min.x;
	times[0].box.max.x = plate_box.min.x;
	times[0].box.min.y = main_box.min.y;
	times[0].box.max.y = main_box.max.y;

	times[0].beats = 8;

	times[1].box.max.x = main_box.max.x;
	times[1].box.min.x = plate_box.max.x;
	times[1].box.min.y = main_box.min.y;
	times[1].box.max.y = main_box.max.y;

	times[1].beats = 9;

	float params_size = main_box.min.y - (-1.0f) - Space * 3 - Width * 2;

	params_box.max.x = Graphics::aspectf();
	params_box.min.x = params_box.max.x - params_size;
	params_box.max.y = main_box.min.y;
	params_box.min.y = params_box.max.y - params_size;

	gain_box.max.y = params_box.min.y - Space;
	gain_box.min.y = gain_box.max.y - Width;
	gain_box.min.x = params_box.min.x;
	gain_box.max.x = params_box.max.x;

	reset_box.max.y = gain_box.min.y - Space;
	reset_box.min.y = reset_box.max.y - Width;
	reset_box.min.x = params_box.min.x;
	reset_box.max.x = params_box.max.x;

	images_box.max.y = main_box.min.y - Space;
	images_box.min.y = -1.0f;
	images_box.min.x = -Graphics::aspectf();
	images_box.max.x = params_box.min.x - Space;

	reset(); //and all the other parameters to a known default.
}

Jam::~Jam() {
}

void Jam::update(float elapsed_time) {
	//accumulators for various things:
	if (LivePlate::max_out >= INT16_MAX) {
		loud_acc += elapsed_time;
	} else {
		loud_acc -= elapsed_time * 2.0f;
	}
	if (loud_acc >= LoudLimit) {
		reset();
	}
	if (!demo_mode) {
		bored_acc += elapsed_time;
	}
	if (bored_acc >= BoredLimit) {
		reset();
		demo_mode = true;
		demo_mode_image_switch = 6.0f;
		demo_mode_hits = 1.0f;
		demo_mode_setting = 0;
		demo_mode_setting_swap = 2.5f;
	}
	//for finding good demos:
	//INFO(LivePlate::plate.Dampping << " " << LivePlate::plate.K << " " << LivePlate::gain);

	if (demo_mode) {
		const unsigned int DemoSettings = 5;
		static int32_t demo_settings[DemoSettings * 3] = {
			12, 256, 252144,
			3, 10, 515430,
			100, 185, 123952,
			1, 1897, 9500,
			100, 1, 30647272
		};

		//do the tween:
		{
			if (demo_mode_setting >= DemoSettings) demo_mode_setting = demo_mode_setting % DemoSettings;
			int32_t *t = demo_settings + demo_mode_setting*3;
			D_amt = powf(0.02f, elapsed_time) * (D_amt - t[0]) + t[0];
			K_amt = powf(0.02f, elapsed_time) * (K_amt - t[1]) + t[1];
			gain_amt = powf(0.02f, elapsed_time) * (gain_amt - t[2]) + t[2];
			if (D_amt < DMin) D_amt = DMin;
			if (D_amt > DMax) D_amt = DMax;
			if (K_amt < KMin) K_amt = KMin;
			if (K_amt > KMax) K_amt = KMax;
			if (gain_amt < GainMin) gain_amt = GainMin;
			if (gain_amt > GainMax) gain_amt = GainMax;
			LivePlate::plate.Dampping = (int)(D_amt + 0.5f);
			LivePlate::plate.K = (int)(K_amt + 0.5f);
			LivePlate::gain = (int)(gain_amt + 0.5f);
		}

		demo_mode_setting_swap -= elapsed_time;
		if (demo_mode_setting_swap < 0.0f) {
			demo_mode_setting = rand() % DemoSettings;
			demo_mode_setting_swap = (rand() / float(RAND_MAX)) * 60.0f + 20.0f;
		}
		demo_mode_image_switch -= elapsed_time;
		if (demo_mode_image_switch < 0.0f) {
			image_scroll_target = rand() % images.size();
			memcpy(LivePlate::plate.inv_mass, &(images[image_scroll_target]->inv_mass[0]), PlateSize*PlateSize*sizeof(int32_t));
			demo_mode_image_switch = (rand() / float(RAND_MAX)) * 80.0f + 10.0f;
		}
		demo_mode_hits -= elapsed_time;
		if (demo_mode_hits < 0.0f) {
			demo_mode_hits = (rand() / float(RAND_MAX)) * 80.0f + 20.0f;
			for (typeof(times.begin()) t = times.begin(); t != times.end(); ++t) {
				t->hits.clear();
				unsigned int count = rand() % 5;
				for (unsigned int i = 0; i < count; ++i) {
					t->hits.push_back(Hit());
					t->hits.back().position = make_vector(rand() / float(RAND_MAX), rand() / float(RAND_MAX));
					t->hits.back().start = rand() % (t->beats * samples_per_beat);
					t->hits.back().values.resize(6, (1<<16));
				}
			}
			hits_dirty = true;
			close_hit = NULL;
			close_hit_dirty = true;
		}
	}

	image_scroll = powf(0.01f, elapsed_time) * (image_scroll - image_scroll_target) + image_scroll_target;

	ImageWatcher::get_new_images(images);

	if (drag_box == NULL && close_hit_dirty) {
		Vector2f pos;
		pos.x = (mouse.x - plate_box.min.x) / (plate_box.max.x - plate_box.min.x);
		pos.y = (mouse.y - plate_box.min.y) / (plate_box.max.y - plate_box.min.y);
		close_hit = NULL;
		bool timeboxed = false;
		for (typeof(times.begin()) t = times.begin(); t != times.end(); ++t) {
			if (!t->box.contains(mouse)) continue;
			timeboxed = true;
			float d = numeric_limits< float >::infinity();
			for (typeof(t->hits.begin()) h = t->hits.begin(); h != t->hits.end(); ++h) {
				float test = h->start / float(t->beats * samples_per_beat) * t->box.size().c[TDir] - (mouse.c[TDir] - t->box.min.c[TDir]);
				if (test < 0) test = -test;
				if (test < d) {
					d = test;
					close_hit = &(*h);
				}
			}
			if (d > 0.01f) {
				close_hit = NULL;
			}
		}
		if (!timeboxed) {
			float d = numeric_limits< float >::infinity();
			for (typeof(times.begin()) t = times.begin(); t != times.end(); ++t) {
				for (typeof(t->hits.begin()) h = t->hits.begin(); h != t->hits.end(); ++h) {
					float test = length_squared(pos - h->position);
					if (test < d) {
						d = test;
						close_hit = &(*h);
					}
				}
			}
			if (d > 0.01f * 0.01f) {
				close_hit = NULL;
			}
		}
		close_hit_dirty = false;
	}

	if (hits_dirty) {
		list< LivePlate::PlateHit > new_plate_hits;
		for (typeof(times.begin()) t = times.begin(); t != times.end(); ++t) {
			for (typeof(t->hits.begin()) h = t->hits.begin(); h != t->hits.end(); ++h) {
				if (h->position.y > 1.0f) continue; //currently dragging, not on plate yet.
				new_plate_hits.push_back(LivePlate::PlateHit());
				Vector2i pi;
				pi.x = (int)(h->position.x * PlateSize);
				pi.y = (int)(h->position.y * PlateSize);
				if (pi.x < 0) pi.x = 0;
				if (pi.x >= (int)PlateSize) pi.x = PlateSize - 1;
				if (pi.y < 0) pi.y = 0;
				if (pi.y >= (int)PlateSize) pi.y = PlateSize - 1;
				new_plate_hits.back().tap = pi.x + PlateSize * pi.y;
				new_plate_hits.back().start = h->start;
				new_plate_hits.back().values = h->values;
				new_plate_hits.back().period = t->beats * samples_per_beat;
			}
		}
		SDL_LockMutex(LivePlate::hits_lock);
		LivePlate::hits = new_plate_hits;
		for (typeof(LivePlate::hits.begin()) h = LivePlate::hits.begin(); h != LivePlate::hits.end(); ++h) {
			h->start -= (int)(LivePlate::played_samples % (int64_t)h->period);
			if (h->start + (int)h->values.size() <= 0) {
				h->start += h->period;
			}
			assert(h->start + (int)h->values.size() > 0);
			assert(h->start < (int)h->period);
		}
		SDL_UnlockMutex(LivePlate::hits_lock);
		hits_dirty = false;
	}
}

namespace {
	float to_log(float val) {
		if (val > 0) {
			return logf(val + 1.0f);
		} else {
			return -logf(-val + 1.0f);
		}
	}
	float from_log(float val) {
		if (val > 0) {
			return expf(val) - 1.0f;
		} else {
			return -(expf(-val) - 1.0f);
		}
	}
	float get_box_pos(float const BoxMin, float const BoxMax, float const Min, float const Max, float pos) {
		float log_min = to_log(Min);
		float log_max = to_log(Max);
		return (to_log(pos) - log_min) / (log_max - log_min) * (BoxMax - BoxMin) + BoxMin;
	}
	void set_log_pos(float const BoxMin, float const BoxMax, float const Min, float const Max, float &box_pos, float &log_pos, int32_t &int_pos) {
		if (box_pos < BoxMin) box_pos = BoxMin;
		if (box_pos > BoxMax) box_pos = BoxMax;
		log_pos = from_log((box_pos - BoxMin) / (BoxMax - BoxMin) * (to_log(Max) - to_log(Min)) + to_log(Min));
		if (log_pos > 0.0f) {
			int_pos = (int32_t)(log_pos + 0.5f);
		} else {
			int_pos = (int32_t)(log_pos - 0.5f);
		}
	}
	void big_message(string message, float amt, Vector3f color, bool bar = true) {
		Graphics::FontRef gentium = Graphics::get_font("gentium.txf");
		const float h = 0.15f;
		float l = gentium->get_length(message, h);
		Vector2f c = make_vector(0.0f, 0.0f);
		glBegin(GL_QUADS);
		glColor4f(0.0f, 0.0f, 0.0f, 0.5f * ((amt < 0.2f)?amt * 5:1.0f));
		glVertex2f(c.x - 0.5f * l - Space, c.y - Space);
		glVertex2f(c.x - 0.5f * l - Space, c.y + h + Space);
		glVertex2f(c.x + 0.5f * l + Space, c.y + h + Space);
		glVertex2f(c.x + 0.5f * l + Space, c.y - Space);
		glColor(color, amt * 1.5f);
		if (bar) {
			glVertex2f(c.x - 0.5f * l - Space, c.y - Space);
			glVertex2f(c.x - 0.5f * l - Space, c.y);
			glVertex2f(c.x - 0.5f * l - Space + (l + 2 * Space) * amt, c.y);
			glVertex2f(c.x - 0.5f * l - Space + (l + 2 * Space) * amt, c.y - Space);
		}
		glEnd();
		gentium->draw(message, make_vector(-0.5f * l, 0.0f) + c, h);
		float h2 = 0.05f;
		glColor4f(1.0f, 1.0f, 1.0f, 0.5f * ((amt < 0.2f)?amt * 5:1.0f));
		gentium->draw("(move mouse to cancel)", make_vector(-0.5f * gentium->get_length("(move_mouse_to_cancel)",h2), -h2 - Space * 0.5f) + c, h2);
	}
}

void Jam::handle_event(SDL_Event const &event) {
	bored_acc = 0.0f;
	loud_acc = 0.0f;
	demo_mode = false;
	if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE) {
		quit_screen(this);
		return;
	}
	if (event.type == SDL_MOUSEMOTION) {
		mouse.x = (event.motion.x / float(Graphics::screen_x) - 0.5f) * 2.0f * Graphics::aspectf();
		mouse.y =-(event.motion.y / float(Graphics::screen_y) - 0.5f) * 2.0f;
		close_hit_dirty = true;
		if (drag_box != NULL) {
			mouse = drag_box->clamp(mouse);
		}
		if (drag_box == &(plate_box) || drag_box == &(main_box)) {
			if (!close_hit) {
				ERROR("Expected a hit to move.");
			} else {
				close_hit->position.x = (mouse.x - plate_box.min.x) / (plate_box.max.x - plate_box.min.x);
				close_hit->position.y = (mouse.y - plate_box.min.y) / (plate_box.max.y - plate_box.min.y);
				hits_dirty = true;
			}
		}
		for (typeof(times.begin()) t = times.begin(); t != times.end(); ++t) {
			if (drag_box == &(t->box)) {
				if (!close_hit) {
					ERROR("Expected a hit to move.");
				} else {
					close_hit->start = (unsigned int)(0.5f + (mouse.c[TDir] - t->box.min.c[TDir]) / t->box.size().c[TDir] * t->beats * samples_per_beat);
					hits_dirty = true;
				}
			}
		}
		if (drag_box == &(gain_box)) {
			set_log_pos(gain_box.min.x, gain_box.max.x, GainMin, GainMax, mouse.x, gain_amt, LivePlate::gain);
		}
		if (drag_box == &(params_box)) {
			set_log_pos(params_box.min.x, params_box.max.x, KMin, KMax, mouse.x, K_amt, LivePlate::plate.K);
			set_log_pos(params_box.min.y, params_box.max.y, DMin, DMax, mouse.y, D_amt, LivePlate::plate.Dampping);
		}

	}

	if (event.type == SDL_MOUSEBUTTONDOWN) {
		if (plate_box.contains(mouse)) {
			Vector2f pos;
			pos.x = (mouse.x - plate_box.min.x) / (plate_box.max.x - plate_box.min.x);
			pos.y = (mouse.y - plate_box.min.y) / (plate_box.max.y - plate_box.min.y);
			if (close_hit && event.button.button == SDL_BUTTON_LEFT) {
				drag_box = &(main_box);
			} else {
				Vector2i pi;
				pi.x = (int)(pos.x * PlateSize);
				pi.y = (int)(pos.y * PlateSize);
				if (pi.x < 0) pi.x = 0;
				if (pi.x >= (int)PlateSize) pi.x = PlateSize - 1;
				if (pi.y < 0) pi.y = 0;
				if (pi.y >= (int)PlateSize) pi.y = PlateSize - 1;

				LivePlate::plate.pos[pi.x + pi.y * PlateSize] += (6 << 16);
			}
		}
		for (typeof(times.begin()) t = times.begin(); t != times.end(); ++t) {
			if (t->box.contains(mouse)) {
				if (event.button.button == SDL_BUTTON_LEFT) {
					if (close_hit) {
						drag_box = &(t->box);
					} else {
						t->hits.push_back(Hit());
						t->hits.back().position.x = (mouse.x - plate_box.min.x) / plate_box.size().x;
						t->hits.back().position.y = (mouse.y - plate_box.min.y) / plate_box.size().y;
						t->hits.back().start = (unsigned int)( (mouse.c[TDir] - t->box.min.c[TDir]) / t->box.size().c[TDir] * t->beats * samples_per_beat + 0.5f);
						t->hits.back().values.resize(6, (1<<16));
						close_hit = &(t->hits.back());
						drag_box = &(main_box);
						hits_dirty = true;
					}
				}
			}
		}
		if (gain_box.contains(mouse)) {
			set_log_pos(gain_box.min.x, gain_box.max.x, GainMin, GainMax, mouse.x, gain_amt, LivePlate::gain);
			drag_box = &(gain_box);
		}

		if (params_box.contains(mouse)) {
			set_log_pos(params_box.min.x, params_box.max.x, KMin, KMax, mouse.x, K_amt, LivePlate::plate.K);
			set_log_pos(params_box.min.y, params_box.max.y, DMin, DMax, mouse.y, D_amt, LivePlate::plate.Dampping);
			drag_box = &(params_box);
		}

		if (reset_box.contains(mouse)) {
			reset();
		}

		if (images_box.contains(mouse)) {
			for (unsigned int i = 0; i < images.size(); ++i) {
				Box2f im = get_image_box(i);
				if (im.contains(mouse)) {
					image_scroll_target = i;
					memcpy(LivePlate::plate.inv_mass, &(images[i]->inv_mass[0]), PlateSize*PlateSize*sizeof(int32_t));
					break;
				}
			}
		}
	}
	if (event.type == SDL_MOUSEBUTTONUP) {
		if (drag_box == &(main_box) && close_hit) {
			if (close_hit->position.x < 0.0f || close_hit->position.x > 1.0f) {
				for (typeof(times.begin()) t = times.begin(); t != times.end(); ++t) {
					for (unsigned int h = 0; h < t->hits.size(); ++h) {
						if (&(t->hits[h]) == close_hit) {
							t->hits.erase(t->hits.begin() + h);
							close_hit = NULL;
							close_hit_dirty = true;
							hits_dirty = true;
							break;
						}
					}
				}
			}
		}
		drag_box = NULL;
	}
}

namespace {

void gradient_box(Box2f box, Vector3f top, Vector3f bottom) {
	glBegin(GL_QUADS);
	glColor(top);
	glVertex2f(box.min.x, box.max.y);
	glVertex2f(box.max.x, box.max.y);
	glColor(bottom);
	glVertex2f(box.max.x, box.min.y);
	glVertex2f(box.min.x, box.min.y);
	glEnd();
}

void text_box(Box2f box, string const &text, Vector3f top, Vector3f bottom, Vector3f text_col) {
	gradient_box(box, top, bottom);
	Graphics::FontRef gentium = Graphics::get_font("gentium.txf");
	glColor(text_col);
	float l = gentium->get_length(text, box.max.y - box.min.y);
	gentium->draw(text, make_vector((box.max.x + box.min.x - l) * 0.5f, box.min.y), box.max.y - box.min.y);
}

} //end helper namespace.

void Jam::draw() {
	glClear(GL_COLOR_BUFFER_BIT);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glScalef(1.0f / Graphics::aspectf(), 1.0f, 1.0f);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	//----------------------------
	{ //main box.
		{ //plate
			vector< int32_t > pos_grab(PlateSize*PlateSize);
			memcpy(&(pos_grab[0]), LivePlate::plate.pos, sizeof(int32_t)*PlateSize*PlateSize);
			vector< float > pos_interp(PlateSize*PlateSize);
			for (unsigned int i = 0; i < PlateSize*PlateSize; ++i) {
				pos_interp[i] = pos_grab[i] / float(1 << 16);
			}
			static GLuint plate_tex = 0;
			if (plate_tex == 0)	glGenTextures(1, &plate_tex);
			glUseProgramObjectARB(plate_shader->handle);
			//----
			glActiveTextureARB(GL_TEXTURE0);
			glBindTexture(GL_TEXTURE_RECTANGLE_ARB, plate_tex);
			glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_LUMINANCE, PlateSize, PlateSize, 0, GL_LUMINANCE, GL_FLOAT, &(pos_interp[0]));

			//----
			glActiveTextureARB(GL_TEXTURE1);
			glBindTexture(GL_TEXTURE_RECTANGLE_ARB, images[image_scroll_target]->texture);
			//----
			glBegin(GL_QUADS);
			glColor3f(1.0f, 1.0f, 1.0f);
			glTexCoord2f(0.0f, 0.0f);
			glVertex2f(plate_box.min.x, plate_box.min.y);
			glTexCoord2f(PlateSize, 0.0f);
			glVertex2f(plate_box.max.x, plate_box.min.y);
			glTexCoord2f(PlateSize, PlateSize);
			glVertex2f(plate_box.max.x, plate_box.max.y);
			glTexCoord2f(0.0f, PlateSize);
			glVertex2f(plate_box.min.x, plate_box.max.y);
			glEnd();
			glActiveTextureARB(GL_TEXTURE1);
			glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
			glDisable(GL_TEXTURE_RECTANGLE_ARB);
			glActiveTextureARB(GL_TEXTURE0);
			glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
			glDisable(GL_TEXTURE_RECTANGLE_ARB);
			glUseProgramObjectARB(0);
		}
		Vector3f T = make_vector(0.7f, 0.7f, 0.6f);
		Vector3f B = make_vector(0.1f, 0.1f, 0.2f);
		{ //gain box
			gradient_box(gain_box, T, B);
			float amt = get_box_pos(gain_box.min.x, gain_box.max.x, 0.0f, (1 << 16), LivePlate::max_out);
			if (amt >= gain_box.max.x) {
				amt = gain_box.max.x;
				glColor3f(1.0f, 0.0f, 0.0f);
			} else {
				glColor3f(0.5f, 1.0f, 0.5f);
			}
			glBegin(GL_QUADS);
			glVertex2f(gain_box.min.x, gain_box.min.y);
			glVertex2f(gain_box.min.x, gain_box.max.y);
			glVertex2f(amt, gain_box.max.y);
			glVertex2f(amt, gain_box.min.y);
			glEnd();
			float g = get_box_pos(gain_box.min.x, gain_box.max.x, GainMin, GainMax, LivePlate::gain);
			glBegin(GL_LINES);
			glColor3f(1.0f, 1.0f, 1.0f);
			glVertex2f(g, gain_box.min.y);
			glVertex2f(g, gain_box.max.y);
			glEnd();
		}
		{ //params_box
			gradient_box(params_box, T, B);
			Vector2f zero;
			zero.x = get_box_pos(params_box.min.x, params_box.max.x, KMin, KMax, 0.0f);
			zero.y = get_box_pos(params_box.min.y, params_box.max.y, DMin, DMax, 0.0f);
			Vector2f pos;
			pos.x = get_box_pos(params_box.min.x, params_box.max.x, KMin, KMax, LivePlate::plate.K);
			pos.y = get_box_pos(params_box.min.y, params_box.max.y, DMin, DMax, LivePlate::plate.Dampping);
			//m -> min, M -> max, C -> color, zC -> zero color.
			Vector3f KmC =  make_vector(0.3f, 0.3f, 0.0f);
			Vector3f KmzC = make_vector(0.1f, 0.0f, 0.0f);
			Vector3f KMC =  make_vector(0.1f, 0.1f, 0.5f);
			Vector3f KMzC = make_vector(0.1f, 0.1f, 0.1f);

			Vector3f DmC =  make_vector(0.5f, 0.1f, 0.0f);
			Vector3f DmzC = make_vector(0.1f, 0.0f, 0.0f);
			Vector3f DMC =  make_vector(0.1f, 0.3f, 0.3f);
			Vector3f DMzC = make_vector(0.1f, 0.1f, 0.1f);
			glBegin(GL_QUADS);
			//---
			glColor(KmzC + DmzC);
			glVertex2f(zero.x, zero.y);
			glColor(KmzC + DmC);
			glVertex2f(zero.x, params_box.min.y);
			glColor(KmC + DmC);
			glVertex2f(params_box.min.x, params_box.min.y);
			glColor(KmC + DmzC);
			glVertex2f(params_box.min.x, zero.y);
			//---
			glColor(KmzC + DMzC);
			glVertex2f(zero.x, zero.y);
			glColor(KmC + DMzC);
			glVertex2f(params_box.min.x, zero.y);
			glColor(KmC + DMC);
			glVertex2f(params_box.min.x, params_box.max.y);
			glColor(KmzC + DMC);
			glVertex2f(zero.x, params_box.max.y);
			//---
			glColor(KMzC + DmzC);
			glVertex2f(zero.x, zero.y);
			glColor(KMC + DmzC);
			glVertex2f(params_box.max.x, zero.y);
			glColor(KMC + DmC);
			glVertex2f(params_box.max.x, params_box.min.y);
			glColor(KMzC + DmC);
			glVertex2f(zero.x, params_box.min.y);
			//---
			glColor(KMzC + DMzC);
			glVertex2f(zero.x, zero.y);
			glColor(KMzC + DMC);
			glVertex2f(zero.x, params_box.max.y);
			glColor(KMC + DMC);
			glVertex2f(params_box.max.x, params_box.max.y);
			glColor(KMC + DMzC);
			glVertex2f(params_box.max.x, zero.y);
			//---
			glEnd();
			glBegin(GL_LINES);
			glColor3f(1.0f, 1.0f, 1.0f);
			glVertex2f(pos.x, params_box.min.y);
			glVertex2f(pos.x, params_box.max.y);
			glVertex2f(params_box.min.x, pos.y);
			glVertex2f(params_box.max.x, pos.y);
			glEnd();
		}
		//time boxes:
		for (typeof(times.begin()) t = times.begin(); t != times.end(); ++t) {
			unsigned int draw_sample = (LivePlate::played_samples % (int64_t)(t->beats * samples_per_beat));
			//gradient_box(t->box, T, B);
			glLineWidth(2.0f);
			glBegin(GL_LINES);
			for (unsigned int b = 0; b <= t->beats; ++b) {
				float p = b / float(t->beats) * (t->box.max.c[TDir] - t->box.min.c[TDir]) + t->box.min.c[TDir];
				Vector2f max = t->box.max;
				max.c[TDir] = p;
				Vector2f min = t->box.min;
				min.c[TDir] = p;
				if (max.x > 0.0f) swap(max, min);
				glColor4f(0.2f, 0.2f, 0.2f, 0.7f);
				glVertex(max);
				glColor4f(0.2f, 0.2f, 0.2f, 0.7f);
				glVertex(min);
			}
			glEnd();
			{ //time sweep:
				float p = draw_sample / float(t->beats * samples_per_beat) * (t->box.max.c[TDir] - t->box.min.c[TDir]) + t->box.min.c[TDir];
				Vector2f max = t->box.max;
				max.c[TDir] = p;
				Vector2f min = t->box.min;
				min.c[TDir] = p;
				if (max.x > 0.0f) swap(max, min);

				glLineWidth(3.0f);
				glBegin(GL_LINES);
				glColor4f(0.0f, 0.0f, 0.0f, 0.4f);
				glVertex(max);
				glColor4f(0.0f, 0.0f, 0.0f, 0.2f);
				glVertex(min);
				glEnd();
				glLineWidth(1.0f);
				glBegin(GL_LINES);
				glColor3f(1.0f, 1.0f, 1.0f);
				glVertex(max);
				glColor3f(0.4f, 0.4f, 0.6f);
				glVertex(min);
				glEnd();
			}
		}
		{ //hits

			vector< float > amts;
			for (typeof(times.begin()) t = times.begin(); t != times.end(); ++t) {
				unsigned int draw_sample = (LivePlate::played_samples % (int64_t)(t->beats * samples_per_beat));
				for (typeof(t->hits.begin()) h = t->hits.begin(); h != t->hits.end(); ++h) {
					float amt = 0.0f;
					unsigned int test = draw_sample;
					if (test < h->start) test += t->beats * samples_per_beat;
					if (test >= h->start && test < h->start + Mixer::Rate * 0.5f) {
						amt = ((int)test - (int)h->start) / (Mixer::Rate * 0.5f);
						if (amt > 0.0f) {
							amt = sqrtf(amt);
						}
						if (amt > 1.0f) amt = 1.0f;
						amt = 1.0f - amt;
					}
					amts.push_back(amt);
				}
			}

			//time:
			glLineWidth(3.0f);
			glBegin(GL_LINES);
			{
				typeof(amts.begin()) a = amts.begin();
				for (typeof(times.begin()) t = times.begin(); t != times.end(); ++t) {
					for (typeof(t->hits.begin()) h = t->hits.begin(); h != t->hits.end(); ++h, ++a) {
						if (&(*h) == close_hit) {
							glColor4f(1.0f, 1.0f, 1.0f,(h->position.x < 0 || h->position.x > 1)?0.5f:1.0f);
						} else {
							glColor3f(0.8f, 0.8f, 0.2f + *a);
						}
						float p = h->start / float(t->beats * samples_per_beat) * (t->box.max.c[TDir] - t->box.min.c[TDir]) + t->box.min.c[TDir];
						Vector2f max = t->box.max - make_vector(Space, Space);
						max.c[TDir] = p;
						Vector2f min = t->box.min + make_vector(Space, Space);
						min.c[TDir] = p;
						glVertex(max);
						glVertex(min);
					}
				}
				assert(a == amts.end());
			}
			glEnd();
			glLineWidth(1.5f);
			//stems:
			glBegin(GL_LINES);
			{
				typeof(amts.begin()) a = amts.begin();
				for (typeof(times.begin()) t = times.begin(); t != times.end(); ++t) {
					for (typeof(t->hits.begin()) h = t->hits.begin(); h != t->hits.end(); ++h, ++a) {
						if (&(*h) == close_hit) {
							glColor4f(0.6f, 0.6f, 0.6f + *a, (h->position.x < 0 || h->position.x > 1)?0.5f:1.0f);
						} else {
							glColor3f(0.3f, 0.3f, 0.3f + *a);
						}
						float p = h->start / float(t->beats * samples_per_beat) * (t->box.max.c[TDir] - t->box.min.c[TDir]) + t->box.min.c[TDir];
						if (t->box.max.x == plate_box.min.x) {
							glVertex2f(t->box.max.x - Space, p);
						} else {
							glVertex2f(t->box.min.x + Space, p);
						}
						glVertex(plate_box.min + product(plate_box.size(), h->position));
					}
				}
				assert(a == amts.end());
			}
			glEnd();

			glLineWidth(2.0f);
			//points:
			{
				typeof(amts.begin()) a = amts.begin();
				for (typeof(times.begin()) t = times.begin(); t != times.end(); ++t) {
					for (typeof(t->hits.begin()) h = t->hits.begin(); h != t->hits.end(); ++h, ++a) {
						if (&(*h) == close_hit) {
							glColor4f(0.8f, 0.8f, 0.8f,(h->position.x < 0 || h->position.x > 1)?0.5f:1.0f);
						} else {
							glColor3f(0.3f, 0.3f, 0.3f);
						}
						Vector2f pos = product(plate_box.size(), h->position) + plate_box.min;
						{ //little 'x':
							Vector2f s = plate_box.size() * 0.01f;
							glBegin(GL_LINES);
							glVertex2f(pos.x-s.x, pos.y-s.y);
							glVertex2f(pos.x+s.x, pos.y+s.y);
							glVertex2f(pos.x-s.x, pos.y+s.y);
							glVertex2f(pos.x+s.x, pos.y-s.y);
							glEnd();
						}
						if (*a > 0.0f) {
							Vector2f s = plate_box.size() * 0.05f * (1.0f - *a);
							glColor4f(1.0f, 1.0f, 1.0f, 0.5f * *a);
							glBegin(GL_QUADS);
							glVertex2f(pos.x+s.x,pos.y+s.y);
							glVertex2f(pos.x+s.x,pos.y-s.y);
							glVertex2f(pos.x-s.x,pos.y-s.y);
							glVertex2f(pos.x-s.x,pos.y+s.y);
							glEnd();
						}
					}
				}
				assert(a == amts.end());
			}
			glLineWidth(1.0f);
		}
	}

	//----------------------------
	{ //source boxes
		//Vector3f T = make_vector(0.7f, 0.7f, 0.6f);
		//Vector3f B = make_vector(0.1f, 0.1f, 0.2f);
		//gradient_box(images_box, T, B);
		for (unsigned int i = 0; i < images.size(); ++i) {
			Box2f im = get_image_box(i);
			glUseProgramObjectARB(subpixel_shader->handle);
			glEnable(GL_TEXTURE_RECTANGLE_ARB);
			glBindTexture(GL_TEXTURE_RECTANGLE_ARB, images[i]->texture);
			glBegin(GL_QUADS);
			glColor3f(1.0f, 1.0f, 1.0f);
			float p = 0.5f * (2.0f / Graphics::screen_y) * (PlateSize / im.size().y);
			glTexCoord2f(0.0f-p, 0.0f-p);
			glMultiTexCoord2fARB(GL_TEXTURE1, 0.0f + p, 0.0f + p);
			glVertex2f(im.min.x, im.min.y);
			glTexCoord2f(PlateSize-p, 0.0f-p);
			glMultiTexCoord2fARB(GL_TEXTURE1, PlateSize + p, 0.0f + p);
			glVertex2f(im.max.x, im.min.y);
			glTexCoord2f(PlateSize-p, PlateSize-p);
			glMultiTexCoord2fARB(GL_TEXTURE1, PlateSize + p, PlateSize + p);
			glVertex2f(im.max.x, im.max.y);
			glTexCoord2f(0.0f-p, PlateSize-p);
			glMultiTexCoord2fARB(GL_TEXTURE1, 0.0f + p, PlateSize + p);
			glVertex2f(im.min.x, im.max.y);
			glEnd();
			glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
			glDisable(GL_TEXTURE_RECTANGLE_ARB);
			glUseProgramObjectARB(0);
			glBegin(GL_LINE_LOOP);
			glColor3f(0.4f, 0.4f, 0.6f);
			glVertex2f(im.min.x, im.min.y);
			glVertex2f(im.max.x, im.min.y);
			glVertex2f(im.max.x, im.max.y);
			glVertex2f(im.min.x, im.max.y);
			glEnd();
		}
	}
	//----------------------------
	//reset button.
	text_box(reset_box, "Reset", make_vector(0.7f, 0.4f, 0.4f), make_vector(0.5f, 0.2f, 0.25f), make_vector(1.0f, 1.0f, 1.0f));
	//----------------------------
	{ //mouse.
		if (!demo_mode) {
			glBegin(GL_TRIANGLES);
			glColor4f(0.0f, 0.0f, 0.0f, 0.5f);
			glVertex2f(mouse.x, mouse.y);
			glColor4f(0.0f, 0.0f, 0.0f, 0.1f);
			const float S = 0.005f;
			glVertex2f(mouse.x + 0.05f + S * 0.7f, mouse.y - 0.05f - S * 0.7f);
			glVertex2f(mouse.x + S * 0.7f, mouse.y - 0.07f - S * 0.7f);

			glColor3f(1.0f, 1.0f, 1.0f);
			glVertex2f(mouse.x, mouse.y);
			glColor4f(1.0f, 1.0f, 1.0f, 0.5f);
			glVertex2f(mouse.x + 0.05f, mouse.y - 0.05f + S);
			glVertex2f(mouse.x, mouse.y - 0.07f + S);
			glEnd();
		}
	}
	//-----------------------------
	//Loud acc.
	{
		if (loud_acc > LoudLimit - 1.5f) {
			big_message("Resetting: Too Loud", 1.0f - (LoudLimit - loud_acc) / 1.5f, make_vector(1.0f, 0.0f, 0.0f));
		}
	}
	//-----------------------------
	//Bored
	{
		if (!demo_mode && bored_acc > BoredLimit - 1.5f) {
			big_message("Going to demo mode...", 1.0f - (BoredLimit - bored_acc) / 1.5f, make_vector(0.3f, 0.3f, 0.8f));
		}
		if (demo_mode) {
			big_message("Demo mode.", 0.5f, make_vector(1.0f, 1.0f, 1.0f), false);
		}
	}


	Graphics::gl_errors("Jam::draw");
}

namespace {
	void build_fib_layout(Vector2i up, unsigned int count, vector< Box2i > &layout) {
		vector< int > sizes(2,1);
		while (sizes.size() < count) {
			sizes.push_back(sizes[sizes.size()-2]+sizes[sizes.size()-1]);
		}
		layout.clear();
		if (count == 0) return;
		up = -perpendicular(up);
		Vector2i at = make_vector(0,0);
		Vector2i d = up + perpendicular(up);
		at -= d * sizes[count-1];
		for (unsigned int b = count-1; b < sizes.size(); --b) {
			int s = sizes[b];
			at += d * s;
			layout.push_back(make_box(at-make_vector(s,s),at+make_vector(s,s)));
			at += d * s;
			d = perpendicular(d);
		}
	}
	void build_dual_fib_layout(Box2f container, unsigned int count, unsigned int center, vector< Box2f > &layout) {
		assert(center < count);
		vector< Box2i > before;
		vector< Box2i > after;
		build_fib_layout(make_vector(-1,0), center+1, before);
		build_fib_layout(make_vector(1,0), count-center, after);
		float min_pos =-0.5f;
		float max_pos = 0.5f;
		if (before.size() > 1) {
			min_pos -= before[1].size().x / float(before[0].size().x);
		}
		if (after.size() > 1) {
			max_pos += after[1].size().x / float(after[0].size().x);
		}
		float scale = container.size().x / (max_pos - min_pos);
		if (container.size().y < scale * 1.0f) {
			scale = container.size().y / 1.0f;
		}
		Vector2f c = make_vector< float >(container.center());
		c.x -= (max_pos + min_pos) * 0.5f * scale;
		layout.clear();

		float before_scale = scale / float(before[0].size().x);
		for (typeof(before.rbegin()) b = before.rbegin(); b + 1 != before.rend(); ++b) {
			Vector2f p = make_vector< float >(b->min + b->max) * 0.5f * before_scale + c;
			float s = b->size().x * before_scale * 0.5f;
			layout.push_back(make_box(p - make_vector(s,s), p + make_vector(s,s)));
		}

		float after_scale = scale / float(after[0].size().x);
		for (typeof(after.begin()) b = after.begin(); b != after.end(); ++b) {
			Vector2f p = make_vector< float >(b->min + b->max) * 0.5f * after_scale + c;
			float s = b->size().x * after_scale * 0.5f;
			layout.push_back(make_box(p - make_vector(s,s), p + make_vector(s,s)));
		}
	}
}

Box2f Jam::get_image_box(unsigned int i) {
	assert(!images.empty());
	static vector< Box2f > layout;
	static vector< Box2f > next_layout;
	static unsigned int scroll_base = (unsigned)-1;
	int current_base = (int)image_scroll;
	float current_frac = image_scroll - current_base;
	if (current_base == (int)images.size() && images.size() > 1) {
		current_base = images.size() - 1;
		current_frac = 1.0f;
	}
	if (scroll_base != (unsigned)current_base || layout.size() != images.size() || next_layout.size() != images.size()) {
		build_dual_fib_layout(images_box, images.size(), current_base, layout);
		build_dual_fib_layout(images_box, images.size(), current_base+1, next_layout);
	}
	assert(layout.size() == images.size());
	assert(next_layout.size() == images.size());
	Box2f ret;
	ret.min = lerp(layout[i].min, next_layout[i].min, current_frac);
	ret.max = lerp(layout[i].max, next_layout[i].max, current_frac);
	return ret;
}


void Jam::reset() {
	SDL_LockMutex(LivePlate::hits_lock);
	SDL_Delay(100); //hoping we can stick the render thread a bit.

	drag_box = NULL;
	close_hit = NULL;
	image_scroll = 0;
	image_scroll_target = 0;
	close_hit_dirty = true;
	hits_dirty = true;

	for (typeof(times.begin()) t = times.begin(); t != times.end(); ++t) {
		t->hits.clear();
	}

	LivePlate::hits.clear();
	LivePlate::tap = PlateSize * (PlateSize / 2) + (PlateSize / 2);
	gain_amt = LivePlate::gain = (4 << 16);
	LivePlate::played_samples = 0;
	//This will work:
	for (unsigned int i = 0; i < PlateSize*PlateSize; ++i) {
		LivePlate::plate.inv_mass[i] = 1 << 16;
	}
	K_amt = LivePlate::plate.K = 256;
	D_amt = LivePlate::plate.Dampping = 12;
	//brute-force-y reset. Who knows if it actually will work!
	for (unsigned int i = 0; i < 10; ++i) {
		memset(LivePlate::plate.inv_mass + PlateSize*PlateSize, '\0', PlateSize * PlateSize * 2 * sizeof(int32_t));
	}
	SDL_UnlockMutex(LivePlate::hits_lock);

	loud_acc = 0.0f;
	bored_acc = 0.0f;
	demo_mode = false;
}
