/*
Bullet Continuous Collision Detection and Physics Library
Copyright (c) 2003-2007 Erwin Coumans  http://bulletphysics.com

This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose, 
including commercial applications, and to alter it and redistribute it freely, 
subject to the following restrictions:

1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/

#include <stdio.h>
#include "PosixThreadSupport.h"

#ifdef USE_PTHREADS
#include <errno.h>
#include <unistd.h>

#include "SpuCollisionTaskProcess.h"
#include "SpuNarrowPhaseCollisionTask/SpuGatheringCollisionTask.h"

#define checkPThreadFunction(returnValue) \
    if(0 != returnValue) { \
        printf("PThread problem at line %i in file %s: %i %d\n", __LINE__, __FILE__, returnValue, errno); \
    }

// The number of threads should be equal to the number of available cores
// Todo: each worker should be linked to a single core, using SetThreadIdealProcessor.

// PosixThreadSupport helps to initialize/shutdown libspe2, start/stop SPU tasks and communication
// Setup and initialize SPU/CELL/Libspe2
PosixThreadSupport::PosixThreadSupport(ThreadConstructionInfo& threadConstructionInfo)
{
	startThreads(threadConstructionInfo);
}

// cleanup/shutdown Libspe2
PosixThreadSupport::~PosixThreadSupport()
{
	stopSPU();
}

// this semaphore will signal, if and how many threads are finished with their work
static sem_t mainSemaphore;

static void createSem(sem_t* semaphore, const char* baseName)
{
	sem_init(semaphore, 0, 0);
}

static void destroySem(sem_t* semaphore)
{
	sem_destroy(semaphore);
}

static void *threadFunction(void *argument) 
{

	PosixThreadSupport::btSpuStatus* status = (PosixThreadSupport::btSpuStatus*)argument;
	
	while (1)
	{
		sem_wait(status->startSemaphore);
		
		void* userPtr = status->m_userPtr;

		if (userPtr) {
			btAssert(status->m_status);
			status->m_userThreadFunc(userPtr,status->m_lsMemory);
			status->m_status = 2;
			sem_post(&mainSemaphore);
			status->threadUsed++;
		} else {
			//exit Thread
			status->m_status = 3;
			sem_post(&mainSemaphore);
			break;
		}
	}
	return 0;
}

///send messages to SPUs
void PosixThreadSupport::sendRequest(uint32_t uiCommand, ppu_address_t uiArgument0, uint32_t taskId)
{
	///	gMidphaseSPU.sendRequest(CMD_GATHER_AND_PROCESS_PAIRLIST, (uint32_t) &taskDesc);
	///we should spawn an SPU task here, and in 'waitForResponse' it should wait for response of the (one of) the first tasks that finished
	
	switch (uiCommand)
	{
		case CMD_GATHER_AND_PROCESS_PAIRLIST:
		{
			btSpuStatus& spuStatus = m_activeSpuStatus[taskId];
			btAssert(taskId >= 0);
			btAssert(taskId < m_activeSpuStatus.size());

			spuStatus.m_commandId = uiCommand;
			spuStatus.m_status = 1;
			spuStatus.m_userPtr = (void*)uiArgument0;

			// fire event to start new task
			sem_post(spuStatus.startSemaphore);
			break;
		}
		default:
		{
			///not implemented
			btAssert(0);
		}
	};
}

///check for messages from SPUs
void PosixThreadSupport::waitForResponse(unsigned int *puiArgument0, unsigned int *puiArgument1)
{
	///We should wait for (one of) the first tasks to finish (or other SPU messages), and report its response
	///A possible response can be 'yes, SPU handled it', or 'no, please do a PPU fallback'

	btAssert(m_activeSpuStatus.size());

	// wait for any of the threads to finish
	sem_wait(&mainSemaphore);
        
	// get at least one thread which has finished
	size_t last = -1;
	
	for (size_t t = 0; t < size_t(m_activeSpuStatus.size()); ++t) {
		if (2 == m_activeSpuStatus[t].m_status) {
			last = t;
			break;
		}
	}

	btSpuStatus& spuStatus = m_activeSpuStatus[last];

	btAssert(spuStatus.m_status > 1);
	spuStatus.m_status = 0;

	// need to find an active spu
	btAssert(last >= 0);

	*puiArgument0 = spuStatus.m_taskId;
	*puiArgument1 = spuStatus.m_status;
}

void PosixThreadSupport::startThreads(ThreadConstructionInfo& threadConstructionInfo)
{
	m_activeSpuStatus.resize(threadConstructionInfo.m_numThreads);
	sem_t* startSemaphore = NULL;
	
	createSem(&mainSemaphore, "main");
	
	for (int i = 0;i < threadConstructionInfo.m_numThreads;i++)
	{
		startSemaphore = new sem_t;
		
		btSpuStatus& spuStatus = m_activeSpuStatus[i];
		createSem(startSemaphore, "threadLocal");                
        spuStatus.startSemaphore = startSemaphore;       
		pthread_create(&spuStatus.thread, NULL, &threadFunction, (void*)&spuStatus);

		spuStatus.m_userPtr = 0;
		spuStatus.m_taskId = i;
		spuStatus.m_commandId = 0;
		spuStatus.m_status = 0;
		spuStatus.m_lsMemory = threadConstructionInfo.m_lsMemoryFunc();
		spuStatus.m_userThreadFunc = threadConstructionInfo.m_userThreadFunc;
        spuStatus.threadUsed = 0;
	}
}

void PosixThreadSupport::startSPU()
{
}

///tell the task scheduler we are done with the SPU tasks
void PosixThreadSupport::stopSPU()
{
	for(size_t t = 0; t < size_t(m_activeSpuStatus.size()); ++t) {
		btSpuStatus& spuStatus = m_activeSpuStatus[t];
		
		spuStatus.m_userPtr = 0;       
		sem_post(spuStatus.startSemaphore);
		sem_wait(&mainSemaphore);
		
		destroySem(spuStatus.startSemaphore);
		pthread_join(spuStatus.thread, 0);
		delete spuStatus.startSemaphore;
	}
	destroySem(&mainSemaphore);
	m_activeSpuStatus.clear();
}

class PosixCriticalSection : public btCriticalSection 
{
	pthread_mutex_t m_mutex;
	
	public:
		PosixCriticalSection() 
		{
			pthread_mutex_init(&m_mutex, NULL);
		}
		virtual ~PosixCriticalSection() 
		{
			pthread_mutex_destroy(&m_mutex);
		}
		
		ATTRIBUTE_ALIGNED16(unsigned int mCommonBuff[32]);
		
		virtual unsigned int getSharedParam(int i)
		{
			return mCommonBuff[i];
		}
		virtual void setSharedParam(int i,unsigned int p)
		{
			mCommonBuff[i] = p;
		}
		
		virtual void lock()
		{
			pthread_mutex_lock(&m_mutex);
		}
		virtual void unlock()
		{
			pthread_mutex_unlock(&m_mutex);
		}
};


#if defined(_POSIX_BARRIERS) && (_POSIX_BARRIERS - 20012L) >= 0
/* OK to use barriers on this platform */
class PosixBarrier : public btBarrier 
{
	pthread_barrier_t m_barr;
	int m_numThreads;
	
	public:
		PosixBarrier()
		:m_numThreads(0)	{	}
		virtual ~PosixBarrier()	{
			pthread_barrier_destroy(&m_barr);
		}
		
		virtual void sync()
		{
			int rc = pthread_barrier_wait(&m_barr);
			if(rc != 0 && rc != PTHREAD_BARRIER_SERIAL_THREAD)
			{
				printf("Could not wait on barrier\n");
				exit(-1);
			}
		}
		virtual void setMaxCount(int numThreads)
		{
			int result = pthread_barrier_init(&m_barr, NULL, numThreads);
			m_numThreads = numThreads;
			btAssert(result==0);
		}
		virtual int  getMaxCount()
		{
			return m_numThreads;
		}
};
#else
/* Not OK to use barriers on this platform - insert alternate code here */
class PosixBarrier : public btBarrier 
{
	pthread_mutex_t m_mutex;
	pthread_cond_t m_cond;
	
	int m_numThreads;
	int	m_called;
	
	public:
		PosixBarrier()
		:m_numThreads(0)
		{
		}
		virtual ~PosixBarrier() 
		{
			if (m_numThreads>0)
			{
				pthread_mutex_destroy(&m_mutex);
				pthread_cond_destroy(&m_cond);
			}
		}
		
		virtual void sync()
		{		
			pthread_mutex_lock(&m_mutex);
			m_called++;
			if (m_called == m_numThreads) {
				m_called = 0;
				pthread_cond_broadcast(&m_cond);
			} else {
				pthread_cond_wait(&m_cond,&m_mutex);
			}
			pthread_mutex_unlock(&m_mutex);
			
		}
		virtual void setMaxCount(int numThreads)
		{
			if (m_numThreads>0)
			{
				pthread_mutex_destroy(&m_mutex);
				pthread_cond_destroy(&m_cond);
			}
			m_called = 0;
			pthread_mutex_init(&m_mutex,NULL);
			pthread_cond_init(&m_cond,NULL);
			m_numThreads = numThreads;
		}
		virtual int  getMaxCount()
		{
			return m_numThreads;
		}
};

#endif//_POSIX_BARRIERS

btBarrier* PosixThreadSupport::createBarrier()
{
	PosixBarrier* barrier = new PosixBarrier();
	barrier->setMaxCount(getNumTasks());
	return barrier;
}

btCriticalSection* PosixThreadSupport::createCriticalSection()
{
	return new PosixCriticalSection();
}

void	PosixThreadSupport::deleteBarrier(btBarrier* barrier)
{
	delete barrier;
}

void PosixThreadSupport::deleteCriticalSection(btCriticalSection* cs)
{
	delete cs;
}

#endif // USE_PTHREADS

