#include "SampleCallbacksGame.h"
#include <sstream>
#include <iomanip>
#include <algorithm>

// to powinno byc gdzies w bibliotece
GxF32 rand(GxF32 a, GxF32 b)
{
	const GxF32 tmp = b - a;
	return (rand() % 100) / 100.0f * tmp + a;
}

SampleCallbacksGame::SampleCallbacksGame()
	: planeGraphicsMaterial(Color::Black), isBrokenJoint(false), triggerMaterial(Color::Red), triggerFound(false)
{
	preferAntialising(4);

	triggerMaterial.alpha = 0.1f;
}

void SampleCallbacksGame::initialize()
{
	SampleGame::initialize();

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	planeActor = createStaticActor(PxTransformFromPlaneEquation(PxPlane(0, 1, 0, 0)), PxPlaneGeometry(), defaultPhysicsMaterial, &planeGraphicsMaterial);
	scene->addActor(*planeActor);

	// sciany
	this->createBreakableWall(PxTransform(PxVec3(0, 0, -10)));

	for (PxU32 i = 0 ; i < 10 ; ++i)
	{
		this->addWallBoxesAt(PxVec3(0, 0, -20.0f * PxReal(i+1)), 6, 10, defaultPhysicsMaterial, &Material::Emerald);
	}

	// aktor z ustawiona notyfikacją o usypianiu
	PxRigidDynamic* sleepingActor = createDynamicActor(PxTransform(PxVec3(-20, 5, 10)), PxBoxGeometry(5, 5, 5), defaultPhysicsMaterial);
	sleepingActor->setActorFlag(PxActorFlag::eSEND_SLEEP_NOTIFIES, true);
	scene->addActor(*sleepingActor);

	// aktor z kształtem oznaczonym jako wyzwalacz
	triggerActor = physics->createRigidStatic(PxTransform(PxVec3(50, 0, 0)));
	PxShape* triggerShape = triggerActor->createShape(PxSphereGeometry(15), *defaultPhysicsMaterial);
	triggerShape->setFlag(PxShapeFlag::eSIMULATION_SHAPE, false);
	triggerShape->setFlag(PxShapeFlag::eTRIGGER_SHAPE, true);
	triggerShape->userData = (void*)&triggerMaterial;
	scene->addActor(*triggerActor);

	//
	camera->lookAt(PxVec3(0, 20, 105), PxVec3(0, 15, 0));
}

void SampleCallbacksGame::release()
{
	SampleGame::release();
}

void SampleCallbacksGame::input(GxF32 elapsedTime)
{
	SampleGame::input(elapsedTime);

	if ( getKeyboard().isKeyPressed(Keys::Space) )
	{
		PxVec3 velocity = camera->getViewDir();
		velocity *= 70.0f;

		if ( (rand() % 2) == 0 )
			this->throwBox(camera->getPosition(), 2.0f, velocity, defaultPhysicsMaterial, 0, 2.0f);
		else
			this->throwBall(camera->getPosition(), 1.5f, velocity, defaultPhysicsMaterial);
	}

	if ( getKeyboard().isKeyPressed(Keys::Z) )
	{
		PxVec3 position;
		if ( raycastFromCamera(position, planeActor) )
		{
			this->addStackedBoxesAt(position, 8, defaultPhysicsMaterial, &Material::Obsidian);
		}
	}

	if ( getKeyboard().isKeyPressed(Keys::X) )
	{
		PxVec3 position;
		if ( raycastFromCamera(position, planeActor) )
		{
			this->addWallBoxesAt(position, 5, 8, defaultPhysicsMaterial, &Material::Turquoise);
		}
	}

	if ( getKeyboard().isKeyPressed(Keys::C) )
	{
		PxVec3 position;
		if ( raycastFromCamera(position, planeActor) )
		{
			position.y += 10.0f;
			this->addCapsuleAt(position, 2, 1, defaultPhysicsMaterial, &Material::Turquoise);
		}
	}

	if ( getKeyboard().isKeyPressed(Keys::V) )
	{
		PxVec3 position;
		if ( raycastFromCamera(position, planeActor) )
		{
			position.y += 10.0f;
			this->addSphereAt(position, 2, defaultPhysicsMaterial, &Material::PolishedGold);
		}
	}

	if ( getKeyboard().isKeyPressed(Keys::T) )
	{
		triggerFound = true;
	}

	const GxF32 fps = getStepper().getFps();
	std::stringstream stream;
	stream << "Sample Callbacks | Fps: " << std::fixed << std::setprecision(1) << fps 
		<< " | Dynamic actors: " << scene->getNbActors(PxActorTypeSelectionFlag::eRIGID_DYNAMIC)
		<< " | Static actors: " << scene->getNbActors(PxActorTypeSelectionFlag::eRIGID_STATIC)
		<< " | Joints: " << joints.size()
		<< " | Camera position: " << camera->getPosition().x << " ; " << camera->getPosition().y << " ; " << camera->getPosition().z;

	this->setTitle(stream.str());
}

void SampleCallbacksGame::update(GxF32 elapsedTime)
{
	SampleGame::update(elapsedTime);

	if ( isBrokenJoint )
	{
		std::vector<PxJoint*> temp(joints);
		joints.clear();
		size_t nbJoints = temp.size();
		for (size_t i = 0 ; i < nbJoints ; ++i)
		{
			if ( temp[i]->getConstraintFlags() & PxConstraintFlag::eBROKEN )
			{
				temp[i]->release();
			}
			else
			{
				joints.push_back(temp[i]);
			}
		}

		isBrokenJoint = false;
	}

	if ( triggerFound )
	{
		PxReal r = rand(-5.0f, 5.0f);
		this->throwBall(PxVec3(r, 8.0f + r, 30.0f), 1.5f, PxVec3(0, 0, -200), defaultPhysicsMaterial);

		triggerFound = false;
	}
}

void SampleCallbacksGame::render(GxF32 elapsedTime)
{
	SampleGame::render(elapsedTime);

	graphics->clear(Color::CornflowerBlue);

	effect->bind();
	effect->setProjMatrix(projectionMtx);
	effect->setViewMatrix(camera->getViewMatrix());

	forceAlpha = false;
	this->renderScene(scene);
	forceAlpha = true;
	this->renderRigidActor(triggerActor);

	effect->unbind();
}

//tu metody zdarzeniowe

void SampleCallbacksGame::createBreakableWall(const PxTransform& transform)
{
	const PxReal brickHalfWidth = 3.0f;
	const PxReal brickHalfHeight = 1.5f;
	const PxReal brickHalfDepth = 1.0f;
	const PxReal brickBreakForce = 10000.0f;

	const PxU32 countOfBricksX = 10;
	const PxU32 countOfBricksY = 10;

	const PxVec3 brickDims = PxVec3(brickHalfWidth, brickHalfHeight, brickHalfDepth);

	PxTransform pose = PxTransform::createIdentity();
	pose.p.x -= (countOfBricksX - 1) * brickHalfWidth;
	pose.p.y += brickHalfHeight;

	std::vector<PxRigidDynamic*> bricks;

	for (PxU32 i = 0 ; i < countOfBricksY ; ++i) // numeruje Y
	{
		for (PxU32 j = 0 ; j < countOfBricksX ; ++j)
		{
			PxRigidDynamic* brick = this->createDynamicActor(transform * pose, PxBoxGeometry(brickDims), defaultPhysicsMaterial, &Material::Default, 0.2f);
			brick->setSolverIterationCounts(8, 8);

			bricks.push_back(brick);

			pose.p.x += 2 * brickHalfWidth;
		}
		pose.p.y += 2 * brickHalfHeight;
		pose.p.x -= 2 * countOfBricksX * brickHalfWidth;
	}

	for (PxU32 i = 0 ; i < countOfBricksY ; ++i)
	{
		for (PxU32 j = 0 ; j < countOfBricksX ; ++j)
		{
			const PxU32 k = i * countOfBricksX + j;
			const PxU32 p = (i + 1) * countOfBricksX + j;

			if ( j < countOfBricksX - 1 )
			{
				PxFixedJoint* jointRight = PxFixedJointCreate(*physics, bricks[k], PxTransform(PxVec3(brickHalfWidth, 0, 0)),
					bricks[k+1], PxTransform(PxVec3(-brickHalfWidth, 0, 0)));

				jointRight->setBreakForce(brickBreakForce, brickBreakForce);
				jointRight->setProjectionLinearTolerance(0.5f);
				joints.push_back(jointRight);
			}

			if ( i < countOfBricksY - 1 )
			{
				PxFixedJoint* jointUp = PxFixedJointCreate(*physics, bricks[k], PxTransform(PxVec3(0, brickHalfHeight, 0)),
					bricks[p], PxTransform(PxVec3(0, -brickHalfHeight, 0)));

				jointUp->setBreakForce(brickBreakForce, brickBreakForce);
				jointUp->setProjectionLinearTolerance(0.5f);
				joints.push_back(jointUp);
			}

			scene->addActor(*bricks[k]);
		}
	}
}

void SampleCallbacksGame::printHelp()
{
	SampleGame::printHelp();

	std::cout << std::endl << "# Dodawanie ksztaltow" << std::endl
		<< "Spacja - rzucenie pudelkiem lub sfera w kierunku patrzenia" << std::endl
		<< "Z - dodanie stosu pudelek" << std::endl
		<< "X - dodanie sciany zbudowanej z pudelek" << std::endl
		<< "C - dodanie kapsuly" << std::endl
		<< "V - dodanie sfery" << std::endl
		<< "T - wymuszenie wyzwalacza" << std::endl << std::endl
		<< "# Opis" << std::endl
		<< "Przykladowa aplikacja pokazuje w jaki sposob korzystac z mechanizmu funkcji zwrotnych." << std::endl;
}