#include "../public/VHACD.h"
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <thread>
#include <atomic>
#include <mutex>
#include <string>
#include <float.h>

#define ENABLE_ASYNC 1

#define HACD_ALLOC(x) malloc(x)
#define HACD_FREE(x) free(x)
#define HACD_ASSERT(x) assert(x)

namespace VHACD
{

class MyHACD_API : public VHACD::IVHACD, public VHACD::IVHACD::IUserCallback, VHACD::IVHACD::IUserLogger
{
public:
	MyHACD_API(void)
	{
		mVHACD = VHACD::CreateVHACD();
	}

	virtual ~MyHACD_API(void)
	{
		releaseHACD();
		Cancel();
		mVHACD->Release();
	}

	
	virtual bool Compute(const double* const _points,
		const uint32_t countPoints,
		const uint32_t* const _triangles,
		const uint32_t countTriangles,
		const Parameters& _desc) final
	{
#if ENABLE_ASYNC
		Cancel(); // if we previously had a solution running; cancel it.
		releaseHACD();

		// We need to copy the input vertices and triangles into our own buffers so we can operate
		// on them safely from the background thread.
		mVertices = (double *)HACD_ALLOC(sizeof(double)*countPoints * 3);
		mIndices = (uint32_t *)HACD_ALLOC(sizeof(uint32_t)*countTriangles * 3);
		memcpy(mVertices, _points, sizeof(double)*countPoints * 3);
		memcpy(mIndices, _triangles, sizeof(uint32_t)*countTriangles * 3);
		mRunning = true;
		mThread = new std::thread([this, countPoints, countTriangles, _desc]()
		{
			ComputeNow(mVertices, countPoints, mIndices, countTriangles, _desc);
			mRunning = false;
		});
#else
		releaseHACD();
		ComputeNow(_points, countPoints, _triangles, countTriangles, _desc);
#endif
		return true;
	}

	bool ComputeNow(const double* const points,
		const uint32_t countPoints,
		const uint32_t* const triangles,
		const uint32_t countTriangles,
		const Parameters& _desc) 
	{
		uint32_t ret = 0;

		mHullCount	= 0;
		mCallback	= _desc.m_callback;
		mLogger		= _desc.m_logger;

		IVHACD::Parameters desc = _desc;
		// Set our intercepting callback interfaces if non-null
		desc.m_callback = desc.m_callback ? this : nullptr;
		desc.m_logger = desc.m_logger ? this : nullptr;

		if ( countPoints )
		{
			bool ok = mVHACD->Compute(points, countPoints, triangles, countTriangles, desc);
			if (ok)
			{
				ret = mVHACD->GetNConvexHulls();
				mHulls = new IVHACD::ConvexHull[ret];
				for (uint32_t i = 0; i < ret; i++)
				{
					VHACD::IVHACD::ConvexHull vhull;
					mVHACD->GetConvexHull(i, vhull);
					VHACD::IVHACD::ConvexHull h;
					h.m_nPoints = vhull.m_nPoints;
					h.m_points = (double *)HACD_ALLOC(sizeof(double) * 3 * h.m_nPoints);
					memcpy(h.m_points, vhull.m_points, sizeof(double) * 3 * h.m_nPoints);
					h.m_nTriangles = vhull.m_nTriangles;
					h.m_triangles = (uint32_t *)HACD_ALLOC(sizeof(uint32_t) * 3 * h.m_nTriangles);
					memcpy(h.m_triangles, vhull.m_triangles, sizeof(uint32_t) * 3 * h.m_nTriangles);
					h.m_volume = vhull.m_volume;
					h.m_center[0] = vhull.m_center[0];
					h.m_center[1] = vhull.m_center[1];
					h.m_center[2] = vhull.m_center[2];
					mHulls[i] = h;
					if (mCancel)
					{
						ret = 0;
						break;
					}
				}
			}
		}

		mHullCount = ret;
		return ret ? true : false;
	}

	void releaseHull(VHACD::IVHACD::ConvexHull &h)
	{
		HACD_FREE((void *)h.m_triangles);
		HACD_FREE((void *)h.m_points);
		h.m_triangles = nullptr;
		h.m_points = nullptr;
	}

	virtual void GetConvexHull(const uint32_t index, VHACD::IVHACD::ConvexHull& ch) const final
	{
		if ( index < mHullCount )
		{
			ch = mHulls[index];
		}
	}

	void	releaseHACD(void) // release memory associated with the last HACD request
	{
		for (uint32_t i=0; i<mHullCount; i++)
		{
			releaseHull(mHulls[i]);
		}
		delete[]mHulls;
		mHulls = nullptr;
		mHullCount = 0;
		HACD_FREE(mVertices);
		mVertices = nullptr;
		HACD_FREE(mIndices);
		mIndices = nullptr;
	}


	virtual void release(void) // release the HACD_API interface
	{
		delete this;
	}

	virtual uint32_t	getHullCount(void)
	{
		return mHullCount;
	}

	virtual void Cancel() final
	{
		if (mRunning)
		{
			mVHACD->Cancel();	// Set the cancel signal to the base VHACD
		}
		if (mThread)
		{
			mThread->join();	// Wait for the thread to fully exit before we delete the instance
			delete mThread;
			mThread = nullptr;
			Log("Convex Decomposition thread canceled\n");
		}
		mCancel = false; // clear the cancel semaphore
	}

	virtual bool Compute(const float* const points,
		const uint32_t countPoints,
		const uint32_t* const triangles,
		const uint32_t countTriangles,
		const Parameters& params) final
	{

		double *vertices = (double *)HACD_ALLOC(sizeof(double)*countPoints * 3);
		const float *source = points;
		double *dest = vertices;
		for (uint32_t i = 0; i < countPoints; i++)
		{
			dest[0] = source[0];
			dest[1] = source[1];
			dest[2] = source[2];
			dest += 3;
			source += 3;
		}

		bool ret =  Compute(vertices, countPoints, triangles, countTriangles, params);
		HACD_FREE(vertices);
		return ret;
	}

	virtual uint32_t GetNConvexHulls() const final
	{
		processPendingMessages();
		return mHullCount;
	}

	virtual void Clean(void) final // release internally allocated memory
	{
		Cancel();
		releaseHACD();
		mVHACD->Clean();
	}

	virtual void Release(void) final  // release IVHACD
	{
		delete this;
	}

	virtual bool OCLInit(void* const oclDevice,
		IVHACD::IUserLogger* const logger = 0) final
	{
		return mVHACD->OCLInit(oclDevice, logger);
	}
		
	virtual bool OCLRelease(IVHACD::IUserLogger* const logger = 0) final
	{
		return mVHACD->OCLRelease(logger);
	}

	virtual void Update(const double overallProgress,
		const double stageProgress,
		const double operationProgress,
		const char* const stage,
		const char* const operation) final
	{
		mMessageMutex.lock();
		mHaveUpdateMessage = true;
		mOverallProgress = overallProgress;
		mStageProgress = stageProgress;
		mOperationProgress = operationProgress;
		mStage = std::string(stage);
		mOperation = std::string(operation);
		mMessageMutex.unlock();
	}

	virtual void Log(const char* const msg) final
	{
		mMessageMutex.lock();
		mHaveLogMessage = true;
		mMessage = std::string(msg);
		mMessageMutex.unlock();
	}

	virtual bool IsReady(void) const final
	{
		processPendingMessages();
		return !mRunning; 
	}

	// As a convenience for the calling application we only send it update and log messages from it's own main
	// thread.  This reduces the complexity burden on the caller by making sure it only has to deal with log
	// messages in it's main application thread.
	void processPendingMessages(void) const
	{
		// If we have a new update message and the user has specified a callback we send the message and clear the semaphore
		if (mHaveUpdateMessage && mCallback)
		{
			mMessageMutex.lock();
			mCallback->Update(mOverallProgress, mStageProgress, mOperationProgress, mStage.c_str(), mOperation.c_str());
			mHaveUpdateMessage = false;
			mMessageMutex.unlock();
		}
		// If we have a new log message and the user has specified a callback we send the message and clear the semaphore
		if (mHaveLogMessage && mLogger)
		{
			mMessageMutex.lock();
			mLogger->Log(mMessage.c_str());
			mHaveLogMessage = false;
			mMessageMutex.unlock();
		}
	}

	// Will compute the center of mass of the convex hull decomposition results and return it
	// in 'centerOfMass'.  Returns false if the center of mass could not be computed.
	virtual bool ComputeCenterOfMass(double centerOfMass[3]) const
	{
		bool ret = false;

		centerOfMass[0] = 0;
		centerOfMass[1] = 0;
		centerOfMass[2] = 0;

		if (mVHACD && IsReady() )
		{
			ret = mVHACD->ComputeCenterOfMass(centerOfMass);
		}
		return ret;
	}

	// Will analyze the HACD results and compute the constraints solutions.
	// It will analyze the point at which any two convex hulls touch each other and 
	// return the total number of constraint pairs found
	virtual uint32_t ComputeConstraints(void) final
	{
		uint32_t ret = 0;
		if (mVHACD && IsReady())
		{
			ret = mVHACD->ComputeConstraints();
		}
		return ret;
	}

	virtual const Constraint *GetConstraint(uint32_t index) const final
	{
		const Constraint * ret = nullptr;
		if (mVHACD && IsReady())
		{
			ret = mVHACD->GetConstraint(index);
		}
		return ret;

	}



private:
	double							*mVertices{ nullptr };
	uint32_t						*mIndices{ nullptr };
	std::atomic< uint32_t>			mHullCount{ 0 };
	VHACD::IVHACD::ConvexHull		*mHulls{ nullptr };
	VHACD::IVHACD::IUserCallback	*mCallback{ nullptr };
	VHACD::IVHACD::IUserLogger		*mLogger{ nullptr };
	VHACD::IVHACD					*mVHACD{ nullptr };
	std::thread						*mThread{ nullptr };
	std::atomic< bool >				mRunning{ false };
	std::atomic<bool>				mCancel{ false };

	// Thread safe caching mechanism for messages and update status.
	// This is so that caller always gets messages in his own thread
	// Member variables are marked as 'mutable' since the message dispatch function
	// is called from const query methods.
	mutable std::mutex						mMessageMutex;
	mutable std::atomic< bool >				mHaveUpdateMessage{ false };
	mutable std::atomic< bool >				mHaveLogMessage{ false };
	mutable double							mOverallProgress{ 0 };
	mutable double							mStageProgress{ 0 };
	mutable double							mOperationProgress{ 0 };
	mutable std::string						mStage;
	mutable std::string						mOperation;
	mutable std::string						mMessage;
};

IVHACD* CreateVHACD_ASYNC(void)
{
	MyHACD_API *m = new MyHACD_API;
	return static_cast<IVHACD *>(m);
}


}; // end of VHACD namespace

