// WORK IN PROGRESS

/**
	An add-on for simpledisplay.d, joystick.d, and simpleaudio.d
	that includes helper functions for writing games (and perhaps
	other multimedia programs).

	Usage example:

	---
	class MyGame {
		/// Called when it is time to redraw the frame
		/// it will try for a particular FPS
		final void drawFrame() {

		}

		final void update(Duration deltaTime) {

		}

		final void fillAudioBuffer(short[] buffer) {

		}
	}

	void main() {
		auto game = new MyGame();

		runGame(game, maxRedrawRate, maxUpdateRate);
	}
	---

	It provides an audio thread, input scaffold, and helper functions.


	The MyGame handler is actually a template, so you don't have virtual
	function indirection and not all functions are required. The interfaces
	are just to help you get the signatures right, they don't work at
	runtime.
*/
module arsd.gamehelpers;

public import arsd.color;
public import simpledisplay;

import std.math;

/// The max rates are given in executions per second
/// Redraw will never be called unless there has been at least one update
void runGame(T)(T game, int maxRedrawRate, int maxUpdateRate) {

}

/// Simple class for putting a TrueColorImage in as an OpenGL texture.
///
/// Doesn't do mipmapping btw.
final class OpenGlTexture {
	private uint _tex;
	private int _width;
	private int _height;
	private float _texCoordWidth;
	private float _texCoordHeight;

	void bind() {
		glBindTexture(GL_TEXTURE_2D, _tex);
	}

	// For easy 2d drawing of it
	void draw(Point where, int width = 0, int height = 0, float rotation = 0.0, Color bg = Color.white) {
		glPushMatrix();
		glTranslatef(where.x, where.y, 0);
		glRotatef(rotation, 0,0, 1);

		if(width == 0)
			width = this.originalImageWidth;
		if(height == 0)
			height = this.originalImageHeight;

		glColor4f(cast(float)bg.r/255.0, cast(float)bg.g/255.0, cast(float)bg.b/255.0, cast(float)bg.a / 255.0);
		glBindTexture(GL_TEXTURE_2D, _tex);
		glBegin(GL_QUADS); 
			glTexCoord2f(0, 0); 				glVertex2i(0, 0);
			glTexCoord2f(texCoordWidth, 0); 		glVertex2i(width, 0); 
			glTexCoord2f(texCoordWidth, texCoordHeight); 	glVertex2i(width, height); 
			glTexCoord2f(0, texCoordHeight); 		glVertex2i(0, height); 
		glEnd();

		glBindTexture(GL_TEXTURE_2D, 0); // unbind the texture

		glPopMatrix();
	}

	/// Use for glTexCoord2f
	float texCoordWidth() { return _texCoordWidth; }
	float texCoordHeight() { return _texCoordHeight; } /// ditto

	/// Returns the texture ID
	uint tex() { return _tex; }

	/// Returns the size of the image
	int originalImageWidth() { return _width; }
	int originalImageHeight() { return _height; } /// ditto

	/// Make a texture from an image.
	this(TrueColorImage from) {
		assert(from.width > 0 && from.height > 0);

		_width = from.width;
		_height = from.height;

		auto _texWidth = _width;
		auto _texHeight = _height;

		const(ubyte)[] data = from.imageData.bytes;

		// gotta round them to the nearest power of two which means padding the image
		if((_texWidth & (_texWidth - 1)) || (_texHeight & (_texHeight - 1))) {
			_texWidth = nextPowerOfTwo(_texWidth);
			_texHeight = nextPowerOfTwo(_texHeight);

			auto n = new ubyte[](_texWidth * _texHeight * 4);
			auto size = from.width * 4;
			auto advance = _texWidth * 4;
			int at = 0;
			int at2 = 0;
			foreach(y; 0 .. from.height) {
				n[at .. at + size] = from.imageData.bytes[at2 .. at2+ size];
				at += advance;
				at2 += size;
			}

			data = n[];

			// the rest of data will be initialized to zeros automatically which is fine.
		}

		glGenTextures(1, &_tex);
		glBindTexture(GL_TEXTURE_2D, tex);

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

		glTexImage2D(
			GL_TEXTURE_2D,
			0,
			GL_RGBA,
			_texWidth, // needs to be power of 2
			_texHeight,
			0,
			GL_RGBA,
			GL_UNSIGNED_BYTE,
			data.ptr);

		assert(!glGetError());

		_texCoordWidth = cast(float) _width / _texWidth;
		_texCoordHeight = cast(float) _height / _texHeight;
	}

	/// Generates from text. Requires stb_truetype.d
	/// pass a pointer to the TtfFont as the first arg (it is template cuz of lazy importing, not because it actually works with different types)
	this(T, FONT)(FONT* font, int size, in T[] text) if(is(T == char)) {
		assert(font !is null);
		int width, height;
		auto data = font.renderString(text, size, width, height);
		auto image = new TrueColorImage(width, height);
		int pos = 0;
		foreach(y; 0 .. height)
		foreach(x; 0 .. width) {
			image.imageData.bytes[pos++] = 255;
			image.imageData.bytes[pos++] = 255;
			image.imageData.bytes[pos++] = 255;
			image.imageData.bytes[pos++] = data[0];
			data = data[1 .. $];
		}
		assert(data.length == 0);

		this(image);
	}

	~this() {
		glDeleteTextures(1, &_tex);
	}
}


// Some math helpers

int nextPowerOfTwo(int v) {
	v--;
	v |= v >> 1;
	v |= v >> 2;
	v |= v >> 4;
	v |= v >> 8;
	v |= v >> 16;
	v++;
	return v;
}

void crossProduct(
	float u1, float u2, float u3,
	float v1, float v2, float v3,
	out float s1, out float s2, out float s3)
{
	s1 = u2 * v3 - u3 * v2;
	s2 = u3 * v1 - u1 * v3;
	s3 = u1 * v2 - u2 * v1;
}

void rotateAboutAxis(
	float theta, // in RADIANS
	float x, float y, float z,
	float u, float v, float w,
	out float xp, out float yp, out float zp)
{
	xp = u * (u*x + v*y + w*z) * (1 - cos(theta)) + x * cos(theta) + (-w*y + v*z) * sin(theta);
	yp = v * (u*x + v*y + w*z) * (1 - cos(theta)) + y * cos(theta) + (w*x - u*z) * sin(theta);
	zp = w * (u*x + v*y + w*z) * (1 - cos(theta)) + z * cos(theta) + (-v*x + u*y) * sin(theta);
}