#include "Bullet2CollisionSdk.h"
#include "btBulletCollisionCommon.h"

struct Bullet2CollisionSdkInternalData
{
	btCollisionConfiguration* m_collisionConfig;
	btCollisionDispatcher* m_dispatcher;
	btBroadphaseInterface* m_aabbBroadphase;
	btCollisionWorld* m_collisionWorld;
	
	Bullet2CollisionSdkInternalData()
		:
	m_collisionConfig(0),
	m_dispatcher(0),
	m_aabbBroadphase(0),
	m_collisionWorld(0)
	{
	}
};

	
Bullet2CollisionSdk::Bullet2CollisionSdk()
{
	m_internalData = new Bullet2CollisionSdkInternalData;
}
	
Bullet2CollisionSdk::~Bullet2CollisionSdk()
{
	delete m_internalData;
	m_internalData = 0;
}
	
plCollisionWorldHandle Bullet2CollisionSdk::createCollisionWorld(int /*maxNumObjsCapacity*/, int /*maxNumShapesCapacity*/, int /*maxNumPairsCapacity*/)
{
	m_internalData->m_collisionConfig = new btDefaultCollisionConfiguration;
	
	m_internalData->m_dispatcher = new btCollisionDispatcher(m_internalData->m_collisionConfig);
	m_internalData->m_aabbBroadphase = new btDbvtBroadphase();
	m_internalData->m_collisionWorld = new btCollisionWorld(m_internalData->m_dispatcher,
															m_internalData->m_aabbBroadphase,
															m_internalData->m_collisionConfig);
	return (plCollisionWorldHandle) m_internalData->m_collisionWorld;
}
	
void Bullet2CollisionSdk::deleteCollisionWorld(plCollisionWorldHandle worldHandle)
{
	btCollisionWorld* world = (btCollisionWorld*) worldHandle;
	btAssert(m_internalData->m_collisionWorld == world);
	
	if (m_internalData->m_collisionWorld == world)
	{
		delete m_internalData->m_collisionWorld;
		m_internalData->m_collisionWorld=0;
		delete m_internalData->m_aabbBroadphase;
		m_internalData->m_aabbBroadphase=0;
		delete m_internalData->m_dispatcher;
		m_internalData->m_dispatcher=0;
		delete m_internalData->m_collisionConfig;
		m_internalData->m_collisionConfig=0;
	} 
}

plCollisionShapeHandle Bullet2CollisionSdk::createSphereShape(plCollisionWorldHandle /*worldHandle*/, plReal radius)
{
	btSphereShape* sphereShape = new btSphereShape(radius);
	return (plCollisionShapeHandle) sphereShape;
}

plCollisionShapeHandle Bullet2CollisionSdk::createPlaneShape(plCollisionWorldHandle worldHandle, 
													plReal planeNormalX, 
													plReal planeNormalY, 
													plReal planeNormalZ, 
													plReal planeConstant)
{
	btStaticPlaneShape* planeShape = new btStaticPlaneShape(btVector3(planeNormalX,planeNormalY,planeNormalZ),planeConstant);
	return (plCollisionShapeHandle) planeShape;
}

plCollisionShapeHandle Bullet2CollisionSdk::createCapsuleShape(plCollisionWorldHandle worldHandle, 
													plReal radius,
													plReal height,
													int capsuleAxis)
{
	btCapsuleShape* capsule = 0;

	switch (capsuleAxis)
	{
	case 0:
	{
		capsule = new btCapsuleShapeX(radius,height);
		break;
	}
	case 1:
	{
		capsule = new btCapsuleShape(radius,height);
		break;
	}
	case 2:
	{
		capsule = new btCapsuleShapeZ(radius,height);
		break;
	}
	default:
	{
		btAssert(0);
	}
	}
	return (plCollisionShapeHandle)capsule;
}

plCollisionShapeHandle Bullet2CollisionSdk::createCompoundShape(plCollisionWorldHandle worldHandle)
{
	return (plCollisionShapeHandle) new btCompoundShape();
}
void Bullet2CollisionSdk::addChildShape(plCollisionWorldHandle worldHandle,plCollisionShapeHandle compoundShapeHandle, plCollisionShapeHandle childShapeHandle,plVector3 childPos,plQuaternion childOrn)
{
	btCompoundShape* compound = (btCompoundShape*) compoundShapeHandle;
	btCollisionShape* childShape = (btCollisionShape*) childShapeHandle;
	btTransform localTrans;
	localTrans.setOrigin(btVector3(childPos[0],childPos[1],childPos[2]));
	localTrans.setRotation(btQuaternion(childOrn[0],childOrn[1],childOrn[2],childOrn[3]));
	compound->addChildShape(localTrans,childShape);
	
}

void Bullet2CollisionSdk::deleteShape(plCollisionWorldHandle /*worldHandle*/, plCollisionShapeHandle shapeHandle)
{
	btCollisionShape* shape = (btCollisionShape*) shapeHandle;
	delete shape;
}

void Bullet2CollisionSdk::addCollisionObject(plCollisionWorldHandle worldHandle, plCollisionObjectHandle objectHandle)
{
	btCollisionWorld* world = (btCollisionWorld*) worldHandle;
	btCollisionObject* colObj = (btCollisionObject*) objectHandle;
	btAssert(world && colObj); 
	if (world == m_internalData->m_collisionWorld && colObj)
	{
		world->addCollisionObject(colObj);
	}
}
void Bullet2CollisionSdk::removeCollisionObject(plCollisionWorldHandle worldHandle, plCollisionObjectHandle objectHandle)
{
	btCollisionWorld* world = (btCollisionWorld*) worldHandle;
	btCollisionObject* colObj = (btCollisionObject*) objectHandle;
	btAssert(world && colObj); 
	if (world == m_internalData->m_collisionWorld && colObj)
	{
		world->removeCollisionObject(colObj);
	} 
}

plCollisionObjectHandle Bullet2CollisionSdk::createCollisionObject(  plCollisionWorldHandle worldHandle, void* userPointer, int userIndex,  plCollisionShapeHandle shapeHandle ,
                                                       plVector3 startPosition,plQuaternion startOrientation )

{
	btCollisionShape* colShape =  (btCollisionShape*) shapeHandle;
	btAssert(colShape);
	if (colShape)
	{
		btCollisionObject* colObj= new btCollisionObject;
		colObj->setUserIndex(userIndex);
		colObj->setUserPointer(userPointer);
		colObj->setCollisionShape(colShape);
        btTransform tr;
        tr.setOrigin(btVector3(startPosition[0],startPosition[1],startPosition[2]));
        tr.setRotation(btQuaternion(startOrientation[0],startOrientation[1],startOrientation[2],startOrientation[3]));
		colObj->setWorldTransform(tr);
		return (plCollisionObjectHandle) colObj;
	}
	return 0;
}

void Bullet2CollisionSdk::deleteCollisionObject(plCollisionObjectHandle bodyHandle)
{
	btCollisionObject* colObj = (btCollisionObject*) bodyHandle;
	delete colObj;
}
void Bullet2CollisionSdk::setCollisionObjectTransform(plCollisionWorldHandle /*worldHandle*/, plCollisionObjectHandle bodyHandle,
												plVector3 position,plQuaternion orientation )
{
	btCollisionObject* colObj = (btCollisionObject*) bodyHandle;
	 btTransform tr;
    tr.setOrigin(btVector3(position[0],position[1],position[2]));
    tr.setRotation(btQuaternion(orientation[0],orientation[1],orientation[2],orientation[3]));
	colObj->setWorldTransform(tr);
}


struct   Bullet2ContactResultCallback : public btCollisionWorld::ContactResultCallback
{
    int m_numContacts;
    lwContactPoint* m_pointsOut;
    int m_pointCapacity;
    
    Bullet2ContactResultCallback(lwContactPoint* pointsOut, int pointCapacity) :
	m_numContacts(0),
		m_pointsOut(pointsOut),
		m_pointCapacity(pointCapacity)
    {
    }
    virtual   btScalar   addSingleResult(btManifoldPoint& cp,   const btCollisionObjectWrapper* colObj0Wrap,int partId0,int index0,const btCollisionObjectWrapper* colObj1Wrap,int partId1,int index1)
    {
        if (m_numContacts<m_pointCapacity)
        {
            lwContactPoint& ptOut = m_pointsOut[m_numContacts];
            ptOut.m_distance = cp.m_distance1;
            ptOut.m_normalOnB[0] = cp.m_normalWorldOnB.getX();
            ptOut.m_normalOnB[1] = cp.m_normalWorldOnB.getY();
            ptOut.m_normalOnB[2] = cp.m_normalWorldOnB.getZ();
            ptOut.m_ptOnAWorld[0] = cp.m_positionWorldOnA[0];
            ptOut.m_ptOnAWorld[1] = cp.m_positionWorldOnA[1];
            ptOut.m_ptOnAWorld[2] = cp.m_positionWorldOnA[2];
            ptOut.m_ptOnBWorld[0] = cp.m_positionWorldOnB[0];
            ptOut.m_ptOnBWorld[1] = cp.m_positionWorldOnB[1];
            ptOut.m_ptOnBWorld[2] = cp.m_positionWorldOnB[2];
            m_numContacts++;
        }
        
        return 1.f;
    }
};

int Bullet2CollisionSdk::collide(plCollisionWorldHandle worldHandle,plCollisionObjectHandle colA, plCollisionObjectHandle colB,
                    lwContactPoint* pointsOut, int pointCapacity)
{
	
    btCollisionWorld* world = (btCollisionWorld*) worldHandle;
    btCollisionObject* colObjA = (btCollisionObject*) colA;
    btCollisionObject* colObjB = (btCollisionObject*) colB;
    btAssert(world && colObjA && colObjB);
    if (world == m_internalData->m_collisionWorld && colObjA && colObjB)
    {
        Bullet2ContactResultCallback cb(pointsOut,pointCapacity);
        world->contactPairTest(colObjA,colObjB,cb);
        return cb.m_numContacts;
    }
    return 0;
}

static plNearCallback gTmpFilter;
static int gNearCallbackCount = 0;
static plCollisionSdkHandle gCollisionSdk = 0;
static plCollisionWorldHandle gCollisionWorldHandle = 0;

static void* gUserData = 0;

void Bullet2NearCallback(btBroadphasePair& collisionPair, btCollisionDispatcher& dispatcher, const btDispatcherInfo& dispatchInfo)
{
    btCollisionObject* colObj0 = (btCollisionObject*)collisionPair.m_pProxy0->m_clientObject;
    btCollisionObject* colObj1 = (btCollisionObject*)collisionPair.m_pProxy1->m_clientObject;
    plCollisionObjectHandle obA =(plCollisionObjectHandle) colObj0;
    plCollisionObjectHandle obB =(plCollisionObjectHandle) colObj1;
    if(gTmpFilter)
    {
        gTmpFilter(gCollisionSdk,gCollisionWorldHandle, gUserData,obA,obB);
        gNearCallbackCount++;
    }
}

void Bullet2CollisionSdk::collideWorld( plCollisionWorldHandle worldHandle,
                          plNearCallback filter, void* userData)
{
    btCollisionWorld* world = (btCollisionWorld*) worldHandle;
    //chain the near-callback
    gTmpFilter = filter;
    gNearCallbackCount = 0;
    gUserData = userData;
    gCollisionSdk = (plCollisionSdkHandle)this;
    gCollisionWorldHandle = worldHandle;
    m_internalData->m_dispatcher->setNearCallback(Bullet2NearCallback);
    world->performDiscreteCollisionDetection();
    gTmpFilter = 0;
}

plCollisionSdkHandle Bullet2CollisionSdk::createBullet2SdkHandle()
{
	return (plCollisionSdkHandle) new Bullet2CollisionSdk;
}
