#include "CooneyAnimatedMesh.h"
#include "CooneyMath.h"

using namespace Cooney;
using namespace Cooney::Math;

#include <iostream>
#include <fstream>
using namespace std;

namespace Cooney
{
	AnimatedMesh::AnimatedMesh()
	{
		timeRate = 1.0f;
		bLoop = true;
		bPlaying = false;
		currentTime = 0.0f;
		
		// since the mesh is constantly updating(animating) we need it to stream to openGL.
		interpolatedMesh.SetGLStreaming(true, true, true, false, false, false, false);
	}
	
	AnimatedMesh::~AnimatedMesh()
	{
		keys.clear();
		for(unsigned int i=0; i<(unsigned int)keymeshes.size(); ++i)
		{
			delete keymeshes[i];
		}
		keymeshes.clear();
	}
	
	bool AnimatedMesh::AddKey(float time, const char* fileName)
	{
		if( !fileName || time < 0.0f )
			return false;
		
		Cooney::Mesh* newMesh = new Cooney::Mesh();
		if( !newMesh->Load(fileName) )
		{
			std::cout << "Could not load the keyframe mesh: " << fileName << "\n";
			return false;
		}
		
		// Some safety, only allow meshes to load in that are the same size as the
		// first keyframe added/the size of the interpolated mesh. If it's the
		// first keyframe added, we set the interpolatedMesh to be the same size
		// as the first key.
		if( keys.size() == 0 )
		{
			interpolatedMesh.Copy(*newMesh);
			interpolatedMesh.PopGLData(); // just in case.
		}
		else if( newMesh->NumVertexes() != interpolatedMesh.NumVertexes()
				|| newMesh->NumFaces() != interpolatedMesh.NumFaces()
				|| newMesh->NumQuads() != interpolatedMesh.NumQuads() )
		{
			delete newMesh;
			std::cout << "The keyframe mesh was not of proper size: " << fileName << "\n";
			return false;
		}
		
		// Need to save this mesh for memory cleanup later.
		keymeshes.push_back(newMesh);
		
		// Assign the new keyframe.
		keys[time] = newMesh;
		
		return true;
	}
	
	bool AnimatedMesh::AddKey(float time, Cooney::Mesh* mesh)
	{
		if( !mesh || time < 0.0f )
			return false;
		
		// Some safety, only allow meshes to load in that are the same size as the
		// first keyframe added/the size of the interpolated mesh. If it's the
		// first keyframe added, we set the interpolatedMesh to be the same size
		// as the first key.
		if( keys.size() == 0 )
		{
			interpolatedMesh.Copy(*mesh);
			interpolatedMesh.PopGLData(); // Just in case.
		}
		else if( mesh->NumVertexes() != interpolatedMesh.NumVertexes()
				|| mesh->NumFaces() != interpolatedMesh.NumFaces()
				|| mesh->NumQuads() != interpolatedMesh.NumQuads() )
		{
			std::cout << "The keyframe mesh was not of proper size\n";
			return false;
		}
		
		// Assign the new keyframe.
		keys[time] = mesh;
		
		return false;
	}
	
	bool AnimatedMesh::DeleteKey(float time)
	{
		return (keys.erase(time) != 0);
	}
	
	bool AnimatedMesh::DeleteKeys(float lowTime, float highTime)
	{
		// To erase, get an iterator for the start and end.
		std::map<float, Cooney::Mesh*>::iterator it = keys.lower_bound(lowTime), itend = it;
		if( it == keys.end() )
			return false;
		
		// Find the end.
		while( itend->first <= highTime )
			itend++;
		
		// Erase.
		keys.erase(it, itend);
		
		return true;
	}
	
	bool AnimatedMesh::SetTime(float time)
	{
		if( keys.size() == 0 )
		{
			// No keyframes, can't set the time.
			currentTime = 0.0f;
			return true;
		}
		if( keys.size() == 1 )
		{
			// One keyframe, set the time to that time.
			currentTime = keys.begin()->first;
			return true;
		}
		
		float lowTime=keys.begin()->first, highTime = keys.rbegin()->first;		
		currentTime = time;
		
		// Loop so that the time winds up in the proper place,
		// instead of merely clamping time. This is useful for
		// larger timesteps. (larger relative to the length
		// of the animation and the timeRate).
		while(true)
		{
			if( currentTime < lowTime )
			{
				currentTime = highTime + currentTime - lowTime;
				continue;
			}
		
			if( currentTime > highTime )
			{
				currentTime = lowTime + currentTime - highTime;
				continue;
			}
			
			break;
		}

		return true;
	}
	
	bool AnimatedMesh::Play(bool bShouldLoop, float timePlayRate)
	{
		// Can't play if there are 1 or less keyframes.
		if( keys.size() <= 1 )
			return false;
		
		bLoop = bShouldLoop;
		timeRate = timePlayRate;
		if( timeRate < 0.0001f )
			timeRate = 0.0001f; // epsilon'ish.
		
		bPlaying = true;
		
		return true;
	}
	
	void AnimatedMesh::Stop(bool bReset)
	{
		bPlaying = false;
		
		if( bReset )
			Reset();
	}
	
	void AnimatedMesh::Reset()
	{
		if( keys.size() == 0 )
			currentTime = 0.0f;
		else if( keys.size() >= 1 )
			currentTime = keys.begin()->first;
	}
	
	void AnimatedMesh::Update(float timeStep)
	{
		// Can't actually step if there are one or no keys.
		if( keys.size() <= 1 )
			return;

		if( bPlaying )
		{
			// scale the timeStep by the timeRate for the modified step size.
			float scaledStep = timeStep * timeRate;

			// If we aren't looping, we need to clamp the timestep.
			if( !bLoop )
			{
				// If we are about to step over the last (rbegin) keyframe,
				// clamp it to the end.
				if( currentTime + scaledStep > keys.rbegin()->first )
					scaledStep = keys.rbegin()->first - currentTime;
				else if( currentTime + scaledStep < keys.begin()->first ) // Reverse check if the timeRate is backwards.
					scaledStep = keys.begin()->first - currentTime;
			}
			
			// Only update the mesh if we've moved through time.
			if( scaledStep != 0.0f )
			{
				// Step through time.
				SetTime(currentTime + scaledStep);

				// Update the mesh model. Remember animations are
				// mesh interpolations, not skeletal systems.
				UpdateMesh();
			}
		}
	}
	
	void AnimatedMesh::UpdateMesh()
	{
		if( keys.size() == 0 )
			return; // no keyframes.
		
		Cooney::Mesh *influenceA=NULL, *influenceB=NULL;
		float interpolation = 0.0f;
		
		// Find the two influences for the updated mesh.
		// (left and right keyframes).

		// If we have only one influence, we just need the one keyframe.
		if( keys.size() == 1 )
		{
			interpolation = 0.0f;
			influenceA = keys.begin()->second;
			influenceB = 0x0;
		}
		else
		{
			// use iterators for finding the left and right keyframe.
			std::map<float, Cooney::Mesh*>::iterator ita=keys.begin(), itb=ita;
			// move iterator b one step ahead of a.
			++itb;
			
			// Loop through until we find what we want.
			// Iterator B should be ahead of time, iterator A should
			// be behind time.
			while(1)
			{
				// First check to see if itb is already ahead of the current time.
				// If so, bail out!
				if( itb->first > currentTime )
					break;
				
				// Step iterator B before A. It is now
				// two steps ahead of A.
				++itb;

				// Check to see if we went into dead space.
				// If we did, stop now. This put's iterator
				// A one before the last keyframe.
				if( itb == keys.end() )
					break;
				
				// We're still safe so iterate A with B.
				++ita;
			}

			// The B iterator may have gone into dead space.
			// Validate its values.
			itb = ita;
			itb++;
			
			// Get the mesh pointers from the mapped
			// iterator parameter.
			influenceA = ita->second;
			influenceB = itb->second;
			
			// Determine the interpolation value.
			if( ita->first >= currentTime )
			{
				// The left keyframe is ahead of the current
				// time. We use no interpolation.
				interpolation = 0.0f;
			}
			else if( itb->first <= currentTime )
			{
				// The right keyframe is before the current
				// time. We use no interpolation.
				interpolation = 1.0f;
			}
			else
			{
				// Calculate the interpolation value as a ratio
				// of time.
				float lowTime = ita->first, highTime = itb->first;
				if( highTime == lowTime )
					interpolation = 0.0f; // Bad, just use the first mesh.
				else
					interpolation = (currentTime - lowTime) / (highTime - lowTime);
			}
		}
		
		// Deform beween the two meshes into the interpolatedMesh by the interpolation value.
		Interp(*influenceA, *influenceB, interpolation, interpolatedMesh);
	}
	
	void AnimatedMesh::PushGL(bool bPositions, bool bNormals, bool bTangents, bool bColors, bool bTexCoords)
	{
		// Delegate rendering to the mesh object.
		interpolatedMesh.PushGL(bPositions, bNormals, bTangents, bColors, bTexCoords);
	}
	
	bool AnimatedMesh::Load(const char* filePath)
	{
		ifstream inFile(filePath, ios::in | ios::binary);
		
		if( !inFile.is_open() )
		{
			std::cout << "Could not open file: " << filePath << "\n";
			return false;
		}
		
		// Expects CNYAMSH0 as its magic number.
		char inIdBuffer[8] = {0};
		char identifier[8] = {'C','N','Y','A','M','S','H','0'};
		inFile.read((char *)inIdBuffer, 8);
		
		bool bValidFile = false;
		if( !memcmp(inIdBuffer, identifier, 8) )
			bValidFile = true;
		if( !bValidFile )
		{
			// Invalid file.
			std::cout << "File is invalid: " << filePath << "\n";
			inFile.close();
			return false;
		}
		
		// Stream the mesh data from the file stream.
		StreamInAnimatedMesh(inFile);
		
		inFile.close();
		
		return true;
	}
	
	bool AnimatedMesh::Save(const char* filePath)
	{
		if( keys.size() < 1 )
			return false;
		
		std::ofstream outFile(filePath, ios::out | ios::binary | ios::trunc);
		
		if( !outFile.is_open() )
			return false;
		
		// Format is of magic number CNYAMSH0.
		char identifier[8] = {'C','N','Y','A','M','S','H','0'};
		outFile.write((char *)identifier, 8);
		
		// Stream the mesh data to the file stream.
		StreamOutAnimatedMesh(outFile);
		
		outFile.close();
		
		return false;
	}
	
	void AnimatedMesh::StreamInAnimatedMesh(std::istream& inStream)
	{
		// First thing, read the number of keyframes.
		unsigned int numKeys = 0;
		inStream.read((char*)&numKeys, sizeof(unsigned int));
		
		// Each key is actually just a mesh object.
		// Loop through for each keyframe and stream in a mesh.
		for(unsigned int i=0; i<numKeys; ++i)
		{
			// Store the new mesh object for local memory management.
			Cooney::Mesh* newMesh = new Cooney::Mesh();
			keymeshes.push_back(newMesh);
			
			// Read the time associated with the mesh.
			float keyTime=0.0f;
			inStream.read((char*)&keyTime, sizeof(float));

			// Stream in the mesh using the mesh object's stream operation.
			newMesh->StreamInMesh(inStream);
			
			// Add the key to the list.
			AddKey(keyTime, newMesh);
		}
	}
	
	void AnimatedMesh::StreamOutAnimatedMesh(std::ostream& outStream)
	{
		// Write the number of keyframes to the stream.
		unsigned int numKeys = (unsigned int)keys.size();
		outStream.write((char*)&numKeys, sizeof(unsigned int));
		
		std::map<float, Cooney::Mesh*>::iterator it = keys.begin();
		do
		{
			// Write the time associated with the keyframe to stream.
			outStream.write((char*)&it->first, sizeof(float));

			// Write the mesh to the stream with the object's stream opration.
			it->second->StreamOutMesh(outStream);

		}while( (++it) != keys.end() );
	}
	
	void Interp(Cooney::Mesh &meshA, Cooney::Mesh &meshB, float interpolation, Cooney::Mesh &meshOut)
	{
		float invinterpolation = 1.0f - interpolation;
		for(unsigned int i=0; i<meshOut.NumVertexes(); ++i)
		{
			// Simple linear interpolation.
			meshOut.VertPositions()[i] = meshA.VertPositions()[i] * invinterpolation + meshB.VertPositions()[i] * interpolation;
			meshOut.VertColors()[i] = meshA.VertColors()[i] * invinterpolation + meshB.VertColors()[i] * interpolation;
			meshOut.VertTexCoords()[i] = meshA.VertTexCoords()[i] * invinterpolation + meshB.VertTexCoords()[i] * interpolation;
			
			// Normalize normal/tangent interpolation.
			meshOut.VertNormals()[i] = Normalized3(meshA.VertNormals()[i] * invinterpolation + meshB.VertNormals()[i] * interpolation);
			meshOut.VertTangents()[i] = Normalized3(meshA.VertTangents()[i] * invinterpolation + meshB.VertTangents()[i] * interpolation);
		}
	}
};