/*
Using parts of the TUIO C++ Demo TuioDemo.cpp

TUIO C++ Demo - part of the reacTIVision project
http://reactivision.sourceforge.net/

Copyright (c) 2005-2009 Martin Kaltenbrunner <mkalten@iua.upf.edu>
*/

#include "stdafx.h"
#include "MultiTouchManager.h"

#include "Touchable.h"
#include "Infrastructure/Camera.h"
#include "Infrastructure/World.h"

#include <algorithm>

using namespace TUIO;

#define MULTITOUCH_MANAGER_BLABLABLA false

const int MultiTouchManager::PORT_NUMBER = 3333;

Vector2 MultiTouchManager::TUIOToScreenCoordinates(Vector2 tuioCoords)
{
	return Vector2(tuioCoords.X * theCamera.GetWindowWidth(), tuioCoords.Y * theCamera.GetWindowHeight());
}

Vector2 MultiTouchManager::TUIOToAngelCoordinates(Vector2 tuioCoords)
{
	Vector2 angelCoordSizeOfViewport(theCamera.GetWorldMaxVertex() - theCamera.GetWorldMinVertex());
	Vector2 angelCoordOriginOffset(theCamera.GetPosition().X, theCamera.GetPosition().Y);
	return Vector2((tuioCoords.X - 0.5f) * angelCoordSizeOfViewport.X - angelCoordOriginOffset.X, (tuioCoords.Y - 0.5f) * -angelCoordSizeOfViewport.Y - angelCoordOriginOffset.Y);
}

void MultiTouchManager::TUIOPointsToAngelPoints(std::list<TUIO::TuioPoint> *tuioPoints, std::list<Vector2> *angelPointsOut)
{
	if (tuioPoints && angelPointsOut)
	{
		std::list<TuioPoint>::iterator it = tuioPoints->begin();
		while(it != tuioPoints->end())
		{
			Vector2 angelPoint(TUIOToAngelCoordinates(Vector2(it->getX(), it->getY())));
			angelPointsOut->push_back(angelPoint);
			++it;
		}
	}
}


MultiTouchManager::MultiTouchManager() :
cursorEventBuffer()
{
	verbose = MULTITOUCH_MANAGER_BLABLABLA;

	if (verbose)
		std::cout << "multitouch constructor" << std::endl;

	tuioClient = new TuioClient(PORT_NUMBER);
	tuioClient->addTuioListener(this);
	tuioClient->connect();

	callbackList = new std::vector<Touchable*>();
	cursorIDToTouchableMapping = new std::map<int, Touchable*>();
	
#ifndef WIN32	
	pthread_mutex_init(&cursorEventBufferMutex,NULL);
#else
	cursorEventBufferMutex = CreateMutex(NULL,FALSE,L"cursorEventBufferMutex");
#endif		
}

MultiTouchManager::~MultiTouchManager()
{
	tuioClient->disconnect();
	delete tuioClient;

	delete callbackList;
	delete cursorIDToTouchableMapping;

#ifndef WIN32	
	pthread_mutex_destroy(&cursorEventBufferMutex);
#else
	CloseHandle(cursorEventBufferMutex);
#endif
}

void MultiTouchManager::addTuioObject(TUIO::TuioObject *tobj)
{	
	if (verbose)
		std::cout << "add obj " << tobj->getSymbolID() << " (" << tobj->getSessionID() << ") "<< tobj->getX() << " " << tobj->getY() << " " << tobj->getAngle() << std::endl;
}

void MultiTouchManager::updateTuioObject(TUIO::TuioObject *tobj)
{	
	if (verbose) 	
		std::cout << "set obj " << tobj->getSymbolID() << " (" << tobj->getSessionID() << ") "<< tobj->getX() << " " << tobj->getY() << " " << tobj->getAngle() 
		<< " " << tobj->getMotionSpeed() << " " << tobj->getRotationSpeed() << " " << tobj->getMotionAccel() << " " << tobj->getRotationAccel() << std::endl;
}

void MultiTouchManager::removeTuioObject(TUIO::TuioObject *tobj)
{
	if (verbose)
		std::cout << "del obj " << tobj->getSymbolID() << " (" << tobj->getSessionID() << ")" << std::endl;
}
	
void MultiTouchManager::addTuioCursor(TUIO::TuioCursor *tcur)
{
	if (verbose) 
		std::cout << theWorld.GetCurrentTimeSeconds() << ": add cur " << tcur->getCursorID() << " (" <<  tcur->getSessionID() << ") " << tcur->getX() << " " << tcur->getY() << std::endl;

	Vector2 cursorPos(TUIOToAngelCoordinates(Vector2(tcur->getX(), tcur->getY())));
	
	lockCursorEventBuffer();
	cursorEventBuffer.push_back(TouchEvent(tcur->getCursorID(), TouchEventTypes::FingerDown, cursorPos, NULL));
	unlockCursorEventBuffer();
}

void MultiTouchManager::updateTuioCursor(TUIO::TuioCursor *tcur)
{
	//if (verbose) 	
	//	std::cout << "set cur " << tcur->getCursorID() << " (" <<  tcur->getSessionID() << ") " << tcur->getX() << " " << tcur->getY() 
	//		<< " " << tcur->getMotionSpeed() << " " << tcur->getMotionAccel() << " " << std::endl;

	Vector2 cursorPos(TUIOToAngelCoordinates(Vector2(tcur->getX(), tcur->getY())));
	
	lockCursorEventBuffer();
	cursorEventBuffer.push_back(TouchEvent(tcur->getCursorID(), TouchEventTypes::FingerMove, cursorPos, NULL));
	unlockCursorEventBuffer();
}

void MultiTouchManager::removeTuioCursor(TUIO::TuioCursor *tcur)
{
	if (verbose)
		std::cout << theWorld.GetCurrentTimeSeconds() << ": del cur " << tcur->getCursorID() << " (" <<  tcur->getSessionID() << ")" << std::endl;
	
	Vector2 cursorPos(TUIOToAngelCoordinates(Vector2(tcur->getX(), tcur->getY())));
	
	std::list<Vector2> *path = new std::list<Vector2>();
	TUIOPointsToAngelPoints(&(tcur->getPath()), path);
	
	lockCursorEventBuffer();
	cursorEventBuffer.push_back(TouchEvent(tcur->getCursorID(), TouchEventTypes::FingerUp, cursorPos, path));
	unlockCursorEventBuffer();
}

void MultiTouchManager::refresh(TUIO::TuioTime ftime)
{
	//std::list<TuioCursor*> cursorList = tuioClient->getTuioCursors();
}

void MultiTouchManager::update()
{
	forwardCursorEventsToTouchables();
}

void MultiTouchManager::forwardCursorEventsToTouchables()
{
	lockCursorEventBuffer();
	while (!cursorEventBuffer.empty())
	{
		int cursorId = cursorEventBuffer.front().cursorID;
		Vector2 cursorPos = cursorEventBuffer.front().pos;
		TouchEventTypes::Enum eventType = cursorEventBuffer.front().eventType;
		std::list<Vector2> *path = cursorEventBuffer.front().path;

		cursorEventBuffer.pop_front();

		if (eventType == TouchEventTypes::FingerDown)
		{
			Touchable* bestTouched = getTouchableAtPosition(cursorPos);

			if (bestTouched)
			{
				cursorIDToTouchableMapping->insert(std::pair<int, Touchable*>(cursorId, bestTouched));
				bestTouched->fingerDown(cursorPos);

				if (verbose)
					std::cout << "added " << bestTouched->GetName() << " at " << bestTouched->GetPosition().X << ", " << bestTouched->GetPosition().Y << " to Cursor ID " << cursorId << std::endl;
			}
			else
			{
				cursorIDToTouchableMapping->insert(std::pair<int, Touchable*>(cursorId, bestTouched));

				if (verbose)
					std::cout << "added cursor without touchable" << std::endl;
			}
		}
		else if (eventType == TouchEventTypes::FingerMove)
		{
			std::map<int, Touchable*>::iterator it(cursorIDToTouchableMapping->find(cursorId));
			if (it != cursorIDToTouchableMapping->end())
			{
				if (it->second != NULL)
				{
					it->second->fingerMove(cursorPos);

					//if (verbose)
					//	std::cout << "updatet Position of " << it->second->GetName() << " to " << cursorPos.X << ", " << cursorPos.Y << " of Cursor ID " << cursorId << std::endl;
				}
			}
		}
		else if (eventType == TouchEventTypes::FingerUp)
		{
			std::map<int, Touchable*>::iterator it(cursorIDToTouchableMapping->find(cursorId));
			if (it != cursorIDToTouchableMapping->end())
			{		
				if (it->second != NULL)
				{

					if (verbose)
						std::cout << "remove " << it->second->GetName() << " at " << cursorPos.X << ", " << cursorPos.Y << " from Cursor ID " << cursorId << std::endl;

					Touchable *thisTouchable = it->second;
					cursorIDToTouchableMapping->erase(it);
					thisTouchable->fingerUp(cursorPos);
				}
				else
				{
					if (verbose)
						std::cout << "remove cursor " << cursorId << " without touchable" << std::endl;

					removeTuioCursorWithoutTouchable(path);
					cursorIDToTouchableMapping->erase(it);
				}
			}
			delete path;
		}
	}
	unlockCursorEventBuffer();
}

void MultiTouchManager::addTouchableToCallbackList(Touchable *rhs)
{
	if (verbose)
		std::cout << theWorld.GetCurrentTimeSeconds() << ": add touchable to the callback list" << std::endl;

	callbackList->push_back(rhs);
}

void MultiTouchManager::removeTouchableFromCallbackList(Touchable *rhs)
{
	if (verbose)
		std::cout << theWorld.GetCurrentTimeSeconds() << ": remove touchable of " << rhs->GetName() << " from the callback list" << std::endl;

	std::vector<Touchable*>::iterator it = std::find(callbackList->begin(), callbackList->end(), rhs);
	if (it != callbackList->end())
		callbackList->erase(it);

	removeTouchableFromCursorMap(rhs);
}

Touchable* MultiTouchManager::getTouchableAtPosition(Vector2 fingerPos)
{
	Touchable* bestHit = NULL;
	float bestHitDistance = 0;
	int bestPriorityLevel = -1;

	std::vector<Touchable*>::iterator it = callbackList->begin();
	while(it != callbackList->end())
	{
		//BoundingBox bb = (*it)->GetBoundingBox();
		//if (bb.Contains(fingerPos))
		float currentHitDistance = ((*it)->GetPosition() - fingerPos).Length();
		if (currentHitDistance < (*it)->GetSize().X * 0.5f)
		{
			//float currentHitDistance((bb.Centroid() - fingerPos).Length());
			int currentPriorityLevel = (*it)->getPriorityLevel();
			if (bestHit == NULL || (currentPriorityLevel > -1 && bestPriorityLevel <= currentPriorityLevel) || 
				(currentHitDistance < bestHitDistance && bestPriorityLevel == currentPriorityLevel))
			{
				if (!(*it)->isAlreadyTouched())
				{
					bestPriorityLevel = currentPriorityLevel;
					bestHit = (*it);
					bestHitDistance = currentHitDistance;
				}
			}
		}
		++it;
	}
	return (bestHit);
}

void MultiTouchManager::removeTouchableFromCursorMap(Touchable* remThis)
{
	if (verbose)
		std::cout << theWorld.GetCurrentTimeSeconds() << ": remove touchable of " << remThis->GetName() << " from the cursor map" << std::endl;

	std::map<int, Touchable*>::iterator it(cursorIDToTouchableMapping->begin());

	while (it != cursorIDToTouchableMapping->end())
	{
		if ((*it).second == remThis)
		{
			removeCursorFromEventBuffers((*it).first);
			it = cursorIDToTouchableMapping->erase(it);
		}
		else
			++it;
	}
}

void MultiTouchManager::removeCursorFromEventBuffers(int removeThisID)
{
	lockCursorEventBuffer();
	std::list<TouchEvent>::iterator addIt(cursorEventBuffer.begin());
	while (addIt != cursorEventBuffer.end())
	{
		if ((*addIt).cursorID == removeThisID)
		{
			addIt = cursorEventBuffer.erase(addIt);
		}
		else
			++addIt;
	}
	unlockCursorEventBuffer();
}

void MultiTouchManager::lockCursorEventBuffer() {
#ifndef WIN32	
	pthread_mutex_lock(&cursorEventBufferMutex);
#else
	WaitForSingleObject(cursorEventBufferMutex, INFINITE);
#endif		
}

void MultiTouchManager::unlockCursorEventBuffer() {
#ifndef WIN32	
	pthread_mutex_unlock(&cursorEventBufferMutex);
#else
	ReleaseMutex(cursorEventBufferMutex);
#endif
}
