module gedo.video.skinnedmesh;



private import gedo.video.staticmesh;
private import gedo.video.animation;
private import gedo.video.meshfile;
private import gedo.video.texture;
private import gedo.video.color;
private import gedo.video.ggl;
private import gedo.math.matrix;
private import gedo.system.cache;



public class SkinnedMesh : StaticMesh
{
public:
	static SkinnedMesh opCall(string path)
	{
		if(!_cache)
		{
			_cache = new typeof(_cache);
		}

		return _cache.get(path,

				(out uint size)
				{
					auto file = new MeshFile(path);

					size = file.meshSize + file.animationSize;

					return new SkinnedMesh(file);
				});
	}


	static void clearCache()
	{
		if(_cache)
		{
			_cache.clear();
		}
	}


	void draw(uint index, uint frame)
	{
		draw(index, frame, texture);
	}


	void draw(uint index, uint frame, Texture t, Color c = WHITE)
	{
		auto matrices = _animations[index].matrices(frame, _matrices);

		if(t)
		{
			GGL_drawSkinnedMesh(_mesh, t.gglTexture, &c, matrices.ptr);
		}
		else
		{
			GGL_drawSkinnedMesh(_mesh, null, &c, matrices.ptr);
		}
	}


	uint[char[]] nameToIndex()
	{
		return _nameToIndex;
	}


	uint animationLength(uint index)
	{
		return _animations[index].frameCount();
	}


	uint animationCount()
	{
		return _animations.length;
	}


private:
	GGL_SkinnedMesh _mesh;
	GGL_Matrix[]    _matrices;
	uint[char[]]    _nameToIndex;
	Animation[]     _animations;


	this(MeshFile file)
	{
		_mesh = GGL_createSkinnedMesh(
				file.vertices.ptr,
				file.vertices.length,
				file.faces.ptr,
				file.faces.length,
				file.cvInBones.ptr
			);

		_matrices.length = file.cvInBones.length;

		_animations  = file.animations;
		_nameToIndex = file.nameToIndex;

		super(file.vertices, file.texture, GGL_castMesh(_mesh));
	}
}



private const uint INTRO = 0;
private const uint LOOP  = 1;
private const uint INDEX = 0;
private const uint TIME  = 1;



public class Timer
{
public:
	this(SkinnedMesh mesh)
	{
		_mesh = mesh;

		set(0, 0);
	}


	void set(uint i0, uint i1)
	{
		_position = INTRO;

		_time[INTRO][INDEX] = i0;
		_time[INTRO][TIME ] = 0;
		_time[LOOP ][INDEX] = i1;
		_time[LOOP ][TIME ] = 0;
	}


	void set(uint i1)
	{
		_position = LOOP;

		_time[INTRO][INDEX] = 0;
		_time[INTRO][TIME ] = 0;
		_time[LOOP ][INDEX] = i1;
		_time[LOOP ][TIME ] = 0;
	}


	void update()
	{
		_time[_position][TIME] += 1;
		if(_time[_position][TIME] >= _mesh.animationLength(_time[_position][INDEX]))
		{
			if(_position == 0)
			{
				++_position;
			}

			_time[_position][TIME] = 0;
		}
	}


	void get(out uint i, out uint t)
	{
		i = _time[_position][INDEX];
		t = _time[_position][TIME];
	}


	bool isLooped()
	{
		return (_position != 0);
	}


	uint index()
	{
		return _time[_position][INDEX];
	}


	uint time()
	{
		return _time[_position][TIME];
	}


	int left()
	{
		if(_position == 0)
		{
			return _mesh.animationLength(_time[_position][INDEX]) - _time[_position][TIME];
		}
		else
		{
			return 0;
		}
	}


	void draw()
	{
		_mesh.draw(index, time);
	}


	SkinnedMesh mesh()
	{
		return _mesh;
	}


	void rewind()
	{
		_time[_position][TIME] = 0;
	}


private:
	SkinnedMesh _mesh;
	uint[2][2]  _time;
	uint        _position;
}



private Cache!(SkinnedMesh, 8<<20) _cache;



private void multiply(float[] m, float[] vi, float[] vo)
{
	vo[0] = m[0]*vi[0] + m[4]*vi[1] + m[8]*vi[2] + m[12];
	vo[1] = m[1]*vi[0] + m[5]*vi[1] + m[9]*vi[2] + m[13];
	vo[2] = m[2]*vi[0] + m[6]*vi[1] + m[10]*vi[2] + m[14];
}



// t    f
// 0    1.5
// 1    2
// 2    2.5
// 3    3
public int f2t(int f)
{
	return (f<<1) - 2;
}
