#include <iostream>

#include <Ogre.h>
#include <OgreOverlay.h>
#include <OgreOverlaySystem.h>
// #include <OgreRenderTarget.h>

#include "OgreApplicationContext.h"
#include "OgrePageManager.h"
#include "lua_control.h"

#define CAM_HEIGHT 1.6f // height of camera above character's center of mass
#define RUN_SPEED 17 // character running speed in units per second
#define TURN_SPEED 500.0f // character turning in degrees per second
#define ANIM_FADE_SPEED \
	7.5f // animation crossfade speed in % of full weight per second
using Real = Ogre::Real;
using Math = Ogre::Math;

class WorldData {
	std::unique_ptr<Ogre::Root> mRoot;
	std::unique_ptr<Ogre::SceneManager> mScnMgr;
	std::unique_ptr<Ogre::PageManager> mPageManager;
	Ogre::PagedWorld *mPagedWorld;
	float fps_time;
	Ogre::RenderWindow *mRenderWindow;

private:
	static WorldData *singleton;
	class DummyPageProvider : public Ogre::PageProvider {
	public:
		bool
		prepareProceduralPage(Ogre::Page *page,
				      Ogre::PagedWorldSection *section) override
		{
			return true;
		}
		bool
		loadProceduralPage(Ogre::Page *page,
				   Ogre::PagedWorldSection *section) override
		{
			return true;
		}
		bool
		unloadProceduralPage(Ogre::Page *page,
				     Ogre::PagedWorldSection *section) override
		{
			return true;
		}
		bool unprepareProceduralPage(
			Ogre::Page *page,
			Ogre::PagedWorldSection *section) override
		{
			return true;
		}
	};
	DummyPageProvider mDummyPageProvider;

	WorldData(Ogre::Root *root, Ogre::SceneManager *scnMgr,
		  Ogre::RenderWindow *renderWindow)
		: mRoot(root)
		, mScnMgr(scnMgr)
		, mPageManager(nullptr)
		, mPagedWorld(nullptr)
		, mRenderWindow(renderWindow)

	{
	}

public:
	static void init(Ogre::Root *root, Ogre::SceneManager *scnMgr,
			 Ogre::RenderWindow *renderWindow)
	{
		singleton = new WorldData(root, scnMgr, renderWindow);
	}
	static WorldData *get_singleton()
	{
		return singleton;
	}
	static void cleanup()
	{
		if (singleton)
			delete singleton;
		singleton = nullptr;
	}
	Ogre::SceneManager *getSceneManager()
	{
		return mScnMgr.get();
	}
	Ogre::Root *getRoot()
	{
		return mRoot.get();
	}
	void createTrimesh(Ogre::Entity *entity)
	{
	}
	void update(float delta)
	{
		fps_time += delta;
		if (fps_time > 1.0f) {
			fps_time -= 1.0f;
			const Ogre::RenderTarget::FrameStats &stats =
				mRenderWindow->getStatistics();
			std::cout << "FPS: " << stats.lastFPS << "\n";
		}
	}
	void initPagedWorld(Ogre::Camera *camera)
	{
		mPageManager = std::make_unique<Ogre::PageManager>();
		mPageManager->setPageProvider(&mDummyPageProvider);
		mPageManager->addCamera(camera);
		mPageManager->setDebugDisplayLevel(0);
		mPagedWorld = mPageManager->createWorld();
	}
};
WorldData *WorldData::singleton = nullptr;

class MainWorld : public Ogre::FrameListener {
public:
	void setup()
	{
		//	mScnMgr = scnMgr;
		//        mDynWorld.reset(new Ogre::Bullet::DynamicsWorld(Ogre::Vector3(0, -9.8, 0)));
		//        mDbgDraw.reset(new Ogre::Bullet::DebugDrawer(mScnMgr->getRootSceneNode(), mDynWorld->getBtWorld()));
		Ogre::MeshManager::getSingleton().createPlane(
			"floor",
			Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
			Ogre::Plane(Ogre::Vector3::UNIT_Y, 0), 100, 100, 10, 10,
			true, 1, 10, 10, Ogre::Vector3::UNIT_Z);

		// create a floor entity, give it a material, and place it at the origin
		Ogre::SceneManager *scnMgr =
			WorldData::get_singleton()->getSceneManager();
		Ogre::Entity *floor = scnMgr->createEntity("Floor", "floor");
		scnMgr->getRootSceneNode()->attachObject(floor);
	}
	bool frameStarted(const Ogre::FrameEvent &evt) override;
};
class CharacterController : public OgreBites::InputListener,
			    Ogre::FrameListener {
	enum AnimID {
		ANIM_IDLE = 0,
		ANIM_WALK,
		ANIM_RUN,
		NUM_ANIMS,
		ANIM_NONE = NUM_ANIMS
	};
	Ogre::Node *mRootBone;
	Ogre::SceneNode *mCameraNode;
	Ogre::Camera *mCamera;
	Ogre::SceneManager *mScnMgr;

	Ogre::SceneNode *mCameraPivot;
	Ogre::SceneNode *mCameraGoal, *mBodyNode;
	Ogre::Entity *mBodyEnt;
	Real mPivotPitch;
	Real mVerticalVelocity;
	Ogre::Vector3
		mKeyDirection; // player's local intended direction based on WASD keys
	Ogre::Vector3 mGoalDirection; // actual intended direction in world-space
	Ogre::AnimationState *mAnims[NUM_ANIMS]; // master animation list
	Ogre::Animation *mSkelAnimations[NUM_ANIMS];
	Ogre::NodeAnimationTrack *mHipsTracks[NUM_ANIMS];
	Ogre::NodeAnimationTrack *mRootTracks[NUM_ANIMS];
	AnimID mAnimID;
	bool mFadingIn[NUM_ANIMS]; // which animations are fading in
	bool mFadingOut[NUM_ANIMS]; // which animations are fading out
	Real mTimer; // general timer to see how long animations have been playing
	Ogre::Skeleton *mSkeleton;
	bool mRunning;
	MainWorld *world;
	Ogre::Vector3 rootMotion;
	Ogre::Quaternion rootRotation;
	//    btRigidBody *mRigidBody;

public:
	CharacterController(Ogre::SceneNode *camNode, Ogre::Camera *cam,
			    Ogre::SceneManager *scnMgr, MainWorld *world);
	~CharacterController();

private:
	void setupBody();
	void setupCamera();
	void setupAnimations();

public:
	bool keyPressed(const OgreBites::KeyboardEvent &evt) override;
	bool keyReleased(const OgreBites::KeyboardEvent &evt) override;
	bool mouseMoved(const OgreBites::MouseMotionEvent &evt) override;
	bool mouseWheelRolled(const OgreBites::MouseWheelEvent &evt) override;
	bool mousePressed(const OgreBites::MouseButtonEvent &evt) override;
	bool frameStarted(const Ogre::FrameEvent &evt) override;
	void frameRendered(const Ogre::FrameEvent &evt) override;

private:
	void updateBody(Ogre::Real deltaTime);
	void updateAnimations(Real deltaTime);
	void updateRootMotion(Real deltaTime);
	void fadeAnimations(Real deltaTime);
	void updateCamera(Real deltaTime);
	void updateCameraGoal(Real deltaYaw, Real deltaPitch, Real deltaZoom);
	void setAnimation(AnimID id, bool reset = false);
#if 0
    struct testMotionResult {
    };
    struct recoverResult {
    };

    bool bodyTestMotion(btRigidBody *body,
		    const btTransform &from,
		    const btVector3 &motion, bool infinite_inertia,
		    textMotionResult *result, 
		    bool excludeRaycastShapes,
		    const std::set<btCollisionObject *> &exclude);
    bool recoverFromPenetration(btRigidBody *body,
		    const btTransform &body_position,
		    btScalar recover_movement_scale,
		    bool infinite_inertia,
		    btVector3 &delta_recover_movement,
		    recoverResult *recover_result,
		    const std::set<btCollisionObject *> &exclude);
#endif
};
CharacterController::CharacterController(Ogre::SceneNode *camNode,
					 Ogre::Camera *cam,
					 Ogre::SceneManager *scnMgr,
					 MainWorld *world)
	: mCameraNode(camNode)
	, mCamera(cam)
	, mScnMgr(scnMgr)
	, mPivotPitch(0)
	, mVerticalVelocity(0)
	, mAnimID(ANIM_NONE)
	, mRunning(false)
	, world(world)
{
	setupBody();
	setupCamera();
	setupAnimations();
}
CharacterController::~CharacterController()
{
}
void CharacterController::setupBody()
{
	mBodyEnt = mScnMgr->createEntity("normal-male.glb");
	mBodyNode = mScnMgr->getRootSceneNode()->createChildSceneNode();
	mBodyNode->attachObject(mBodyEnt);
	mSkeleton = mBodyEnt->getSkeleton();
	// mRigidBody = world->addCharacter(mBodyEnt, 0);
	// mCollisionShape = static_cast<btCompoundShape *>(mRigidBody->getCollisionShape());
	assert(mSkeleton->hasBone("Root"));
	mRootBone = mSkeleton->getBone("Root");
	assert(mRootBone);
}
void CharacterController::setupCamera()
{
	// create a pivot at roughly the character's shoulder
	mCameraPivot = mScnMgr->getRootSceneNode()->createChildSceneNode();
	mCameraGoal =
		mCameraPivot->createChildSceneNode(Ogre::Vector3(0, 2, 3));
	mCameraNode->setPosition(mCameraPivot->getPosition() +
				 mCameraGoal->getPosition());
	mCameraPivot->setFixedYawAxis(true);
	mCameraGoal->setFixedYawAxis(true);
	mCameraNode->setFixedYawAxis(true);
	// our model is quite small, so reduce the clipping planes
	mCamera->setNearClipDistance(0.1f);
	mCamera->setFarClipDistance(700);

	mPivotPitch = 0;
	mKeyDirection = Ogre::Vector3::ZERO;
	mVerticalVelocity = 0;
}
void CharacterController::setupAnimations()
{
	int i, j;
	mSkeleton->setBlendMode(Ogre::ANIMBLEND_CUMULATIVE);
	Ogre::String animNames[NUM_ANIMS] = { "idle", "walking", "running" };
	for (i = 0; i < NUM_ANIMS; i++) {
		mAnims[i] = mBodyEnt->getAnimationState(animNames[i]);
		mAnims[i]->setLoop(true);
		mAnims[i]->setEnabled(true);
		mAnims[i]->setWeight(0);
		mFadingIn[i] = false;
		mFadingOut[i] = false;
		mSkelAnimations[i] = mSkeleton->getAnimation(animNames[i]);
		for (const auto &it : mSkelAnimations[i]->_getNodeTrackList()) {
			Ogre::NodeAnimationTrack *track = it.second;
			Ogre::String trackName =
				track->getAssociatedNode()->getName();
			if (trackName == "mixamorig:Hips") {
				mHipsTracks[i] = track;
			} else if (trackName == "Root") {
				mRootTracks[i] = track;
				//				mRootTracks[i]->removeAllKeyFrames();
			}
		}
		Ogre::Vector3 delta = Ogre::Vector3::ZERO;
		Ogre::Vector3 motion = Ogre::Vector3::ZERO;
		for (j = 0; j < mRootTracks[i]->getNumKeyFrames(); j++) {
			Ogre::Vector3 trans = mRootTracks[i]
						      ->getNodeKeyFrame(j)
						      ->getTranslate();
			if (j == 0)
				delta = trans;
			else
				delta = trans - motion;
			mRootTracks[i]->getNodeKeyFrame(j)->setTranslate(delta);
			motion = trans;
		}
	}
#if 0
	for(i = 0; i < NUM_ANIMS - 1; i++) {
		// need to cache
		int j;
		Ogre::String animName = mAnims[i]->getAnimationName();
		Ogre::Animation *anim = mSkeleton->getAnimation(animName);
		Ogre::NodeAnimationTrack *hips_track = nullptr, *root_track = nullptr;
		Ogre::Node *root_node = nullptr;
		for (const auto& it : anim->_getNodeTrackList()) {
			Ogre::NodeAnimationTrack* track = it.second;
			Ogre::String trackName = track->getAssociatedNode()->getName();
			std::cout << animName << " track: " << trackName << "\n";
			if (trackName == "mixamorig:Hips")
				hips_track = track;
			else if (trackName == "Root") {
				root_track = track;
				root_node = track->getAssociatedNode();
			}
		}
		assert(false);
		root_track->removeAllKeyFrames();
		std::cout << hips_track << " " << root_track << "\n";
		std::cout << hips_track->getNumKeyFrames() << " " << root_track->getNumKeyFrames() << "\n";
		assert(hips_track && root_track);
		Ogre::Vector3 delta = Ogre::Vector3::ZERO;
		for(j = 0; j < hips_track->getNumKeyFrames(); j++) {
			float timePos = hips_track->getNodeKeyFrame(j)->getTime();
			Ogre::Vector3 trans = hips_track->getNodeKeyFrame(j)->getTranslate();
			Ogre::Vector3 hips_trans(0, 0, 0);
			Ogre::Vector3 root_trans(0, 0, 0);
			hips_track->getNodeKeyFrame(j)->setTranslate(hips_trans);
			Ogre::TransformKeyFrame *nk = root_track->createNodeKeyFrame(timePos);
			nk->setTranslate(root_trans - delta);
			nk->setScale(Ogre::Vector3(1, 1, 1));
			nk->setRotation(Ogre::Quaternion());
			std::cout << animName << " delta: " << j << " " << timePos << " " << root_trans - delta << "\n";
			delta = root_trans;
		}
		for(j = 0; j < root_track->getNumKeyFrames(); j++) {
			float timePos = hips_track->getNodeKeyFrame(j)->getTime();
			Ogre::Vector3 root_trans = hips_track->getNodeKeyFrame(j)->getTranslate();
			std::cout << animName << " delta: root: " << j << " " << timePos << " " << root_trans << "\n";
		}
	}
//	assert(false);
#endif
	setAnimation(ANIM_IDLE);
}
bool CharacterController::keyPressed(const OgreBites::KeyboardEvent &evt)
{
	OgreBites::Keycode key = evt.keysym.sym;
	if (key == 'q' && (mAnimID == ANIM_IDLE)) {
		/* ... */
		mTimer = 0;
	} else if (key == 'e') {
	} else if (key == 'w')
		mKeyDirection.z = -1;
	else if (key == 'a')
		mKeyDirection.x = -1;
	else if (key == 's')
		mKeyDirection.z = 1;
	else if (key == 'd')
		mKeyDirection.x = 1;
	if (key == OgreBites::SDLK_LSHIFT)
		mRunning = true;
	if (!mKeyDirection.isZeroLength() && mAnimID == ANIM_IDLE) {
		if (mRunning)
			setAnimation(ANIM_RUN, true);
		else
			setAnimation(ANIM_WALK, true);
		// std::cout << "Walking\n";
	} else if (!mKeyDirection.isZeroLength() && mAnimID == ANIM_WALK &&
		   mRunning)
		setAnimation(ANIM_RUN);
	return true;
}
bool CharacterController::keyReleased(const OgreBites::KeyboardEvent &evt)
{
	OgreBites::Keycode key = evt.keysym.sym;
	if (key == 'w' && mKeyDirection.z == -1)
		mKeyDirection.z = 0;
	else if (key == 'a' && mKeyDirection.x == -1)
		mKeyDirection.x = 0;
	else if (key == 's' && mKeyDirection.z == 1)
		mKeyDirection.z = 0;
	else if (key == 'd' && mKeyDirection.x == 1)
		mKeyDirection.x = 0;
	if (key == OgreBites::SDLK_LSHIFT)
		mRunning = false;

	if (mKeyDirection.isZeroLength() &&
	    (mAnimID == ANIM_WALK || mAnimID == ANIM_RUN))
		setAnimation(ANIM_IDLE);
	else if (!mKeyDirection.isZeroLength() && mAnimID == ANIM_RUN &&
		 !mRunning)
		setAnimation(ANIM_WALK);

	return true;
}
bool CharacterController::mouseMoved(const OgreBites::MouseMotionEvent &evt)
{
	// update camera goal based on mouse movement
	updateCameraGoal(-0.18f * evt.xrel, -0.12f * evt.yrel, 0);
	return true;
}
bool CharacterController::mouseWheelRolled(const OgreBites::MouseWheelEvent &evt)
{
	// update camera goal based on mouse movement
	updateCameraGoal(0, 0, -0.15f * evt.y);
	return true;
}
bool CharacterController::mousePressed(const OgreBites::MouseButtonEvent &evt)
{
	std::cout << "Mouse press\n";
	return false;
}
void CharacterController::frameRendered(const Ogre::FrameEvent &evt)
{
	updateBody(evt.timeSinceLastFrame);
	updateAnimations(evt.timeSinceLastFrame);
	updateCamera(evt.timeSinceLastFrame);
	if (evt.timeSinceLastFrame > 0)
		updateRootMotion(evt.timeSinceLastFrame);
}
bool CharacterController::frameStarted(const Ogre::FrameEvent &evt)
{
	return true;
}
void CharacterController::updateCameraGoal(Real deltaYaw, Real deltaPitch,
					   Real deltaZoom)
{
	mCameraPivot->yaw(Ogre::Degree(deltaYaw), Ogre::Node::TS_PARENT);
	if (!(mPivotPitch + deltaPitch > 25 && deltaPitch > 0) &&
	    !(mPivotPitch + deltaPitch < -60 && deltaPitch < 0)) {
		mCameraPivot->pitch(Ogre::Degree(deltaPitch),
				    Ogre::Node::TS_LOCAL);
		mPivotPitch += deltaPitch;
	}
	Real dist = mCameraGoal->_getDerivedPosition().distance(
		mCameraPivot->_getDerivedPosition());
	Real distChange = deltaZoom * dist;

	// bound the zoom
	if (!(dist + distChange < 8 && distChange < 0) &&
	    !(dist + distChange > 25 && distChange > 0))
		mCameraGoal->translate(0, 0, distChange, Ogre::Node::TS_LOCAL);
}
void CharacterController::updateBody(Real delta)
{
	mGoalDirection = Ogre::Vector3::ZERO;
	if (mKeyDirection != Ogre::Vector3::ZERO) {
		// calculate actually goal direction in world based on player's key directions
		mGoalDirection +=
			mKeyDirection.z * mCameraNode->getOrientation().zAxis();
		mGoalDirection +=
			mKeyDirection.x * mCameraNode->getOrientation().xAxis();
		mGoalDirection.y = 0;
		mGoalDirection.normalise();

		Ogre::Quaternion toGoal =
			mBodyNode->getOrientation().zAxis().getRotationTo(
				mGoalDirection);
		// calculate how much the character has to turn to face goal direction
		Real yawToGoal = toGoal.getYaw().valueDegrees();
		// this is how much the character CAN turn this frame
		Real yawAtSpeed =
			yawToGoal / Math::Abs(yawToGoal) * delta * TURN_SPEED;
		// reduce "turnability" if we're in midair
		// if (mBaseAnimID == ANIM_JUMP_LOOP) yawAtSpeed *= 0.2f;
		if (yawToGoal < 0)
			yawToGoal = std::min<Real>(
				0,
				std::max<Real>(
					yawToGoal,
					yawAtSpeed)); //yawToGoal = Math::Clamp<Real>(yawToGoal, yawAtSpeed, 0);
		else if (yawToGoal > 0)
			yawToGoal = std::max<Real>(
				0,
				std::min<Real>(
					yawToGoal,
					yawAtSpeed)); //yawToGoal = Math::Clamp<Real>(yawToGoal, 0, yawAtSpeed);
		mBodyNode->yaw(Ogre::Degree(yawToGoal));
		// move in current body direction (not the goal direction)
//		mBodyNode->translate(0, 0, delta * RUN_SPEED * mAnims[mAnimID]->getWeight(),
//				Ogre::Node::TS_LOCAL);
#if 0
        if (mBaseAnimID == ANIM_JUMP_LOOP)
        {
            // if we're jumping, add a vertical offset too, and apply gravity
            mBodyNode->translate(0, mVerticalVelocity * deltaTime, 0, Node::TS_LOCAL);
            mVerticalVelocity -= GRAVITY * deltaTime;
            
            Vector3 pos = mBodyNode->getPosition();
            if (pos.y <= CHAR_HEIGHT)
            {
                // if we've hit the ground, change to landing state
                pos.y = CHAR_HEIGHT;
                mBodyNode->setPosition(pos);
                setBaseAnimation(ANIM_JUMP_END, true);
                mTimer = 0;
            }
        }
#endif
	}
}
void CharacterController::updateAnimations(Real delta)
{
	int i, j, k;
	Real animSpeed = 1;
	mTimer += delta;
	if (mAnimID != ANIM_NONE) {
		if (mAnimID == ANIM_WALK)
			mAnims[mAnimID]->addTime(delta * 1.0f);
		else
			mAnims[mAnimID]->addTime(delta * animSpeed);
	}
	fadeAnimations(delta);
}
void CharacterController::updateRootMotion(Real delta)
{
	Ogre::Vector3 boneMotion = mRootBone->getPosition();
	OgreAssert(delta > 0.0f, "Zero delta");
	// std::cout << "Root bone position: " << boneMotion << "\n";
#if 0
	Ogre::Vector3 motion = boneMotion - rootMotion;
	if (motion.squaredLength() > 0.1f * 0.1f)
		motion = Ogre::Vector3();
	rootMotion = boneMotion;
#endif
#if 0
	float mass = mRigidBody->getMass();
	std::cout << "body mass: " << mass << "\n";
#endif
#if 0
	/* Kinematic motion */
	Ogre::Quaternion rot = mBodyNode->getOrientation();
	Ogre::Vector3 velocity = rot * boneMotion / delta;
	Ogre::Vector3 gravity(0, -9.8, 0);
	if (mGhostObject->getNumOverlappingObjects() > 0) // hack to detect surface
	    gravity *= 0.01f; /* avoid jitter: just fine for surface but slow for falling */
	velocity += gravity;
	Ogre::Vector3 rotMotion = velocity * delta;
	btTransform from(convert(mBodyNode->getOrientation()), convert(mBodyNode->getPosition()));
	mBodyNode->setPosition(mBodyNode->getPosition() + rotMotion);
#endif
	//	WorldData::get_singleton()->getWorld()->testBodyMotion(mRigidBody, from, Ogre::Bullet::convert(rotMotion), true,
	//            nullptr, false, std::set<btCollisionObject *>());
}
void CharacterController::fadeAnimations(Real delta)
{
	int i;
	for (i = 0; i < NUM_ANIMS; i++) {
		if (mFadingIn[i]) {
			// slowly fade this animation in until it has full weight
			Real newWeight = mAnims[i]->getWeight() +
					 delta * ANIM_FADE_SPEED;
			mAnims[i]->setWeight(
				Math::Clamp<Real>(newWeight, 0, 1));
			if (newWeight >= 1)
				mFadingIn[i] = false;
		} else if (mFadingOut[i]) {
			// slowly fade this animation out until it has no weight, and then disable it
			Real newWeight = mAnims[i]->getWeight() -
					 delta * ANIM_FADE_SPEED;
			mAnims[i]->setWeight(
				Math::Clamp<Real>(newWeight, 0, 1));
			if (newWeight <= 0) {
				mAnims[i]->setEnabled(false);
				mFadingOut[i] = false;
			}
		}
	}
}
void CharacterController::updateCamera(Real delta)
{
	// place the camera pivot roughly at the character's shoulder
	mCameraPivot->setPosition(mBodyNode->getPosition() +
				  Ogre::Vector3::UNIT_Y * CAM_HEIGHT);
	// move the camera smoothly to the goal
	Ogre::Vector3 goalOffset =
		mCameraGoal->_getDerivedPosition() - mCameraNode->getPosition();
	mCameraNode->translate(goalOffset * delta * 9.0f);
	// always look at the pivot
	mCameraNode->lookAt(mCameraPivot->_getDerivedPosition(),
			    Ogre::Node::TS_PARENT);
}
void CharacterController::setAnimation(AnimID id, bool reset)
{
	assert(id >= 0 && id < NUM_ANIMS);
	if (mAnimID != ANIM_NONE) {
		mFadingIn[mAnimID] = false;
		mFadingOut[mAnimID] = true;
	}
	mAnimID = id;
	if (id != ANIM_NONE) {
		mAnims[id]->setEnabled(true);
		mAnims[id]->setWeight(0);
		mFadingOut[id] = false;
		mFadingIn[id] = true;
		if (reset)
			mAnims[id]->setTimePosition(0);
	}
}

#if 0
bool CharacterController::recoverFromPenetration(btRigidBody *body,
		    const btTransform &body_position,
		    btScalar recover_movement_scale,
		    bool infinite_inertia,
		    btVector3 &delta_recover_movement,
		    recoverResult *recover_result,
		    const std::set<btCollisionObject *> &exclude)
{
	return false;
}

bool CharacterController::bodyTestMotion(btRigidBody *body,
		    const btTransform &from,
		    const btVector3 &motion, bool infinite_inertia,
		    textMotionResult *result, 
		    bool excludeRaycastShapes,
		    std::set<btCollisionObject *> exclude)
{
	int t;
	btTransform body_transform;
	btVector3 initial_recover_motion(0, 0, 0);
	// phase one - depenetration
	for (t = 0; t < RECOVERING_MOVEMENT_CYCLES; t++)
		if (!recoverFromPenetration(body,
					body_transform,
					RECOVERING_MOVEMENT_SCALE,
					infinite_inertia,
					initial_recover_motion,
					nullptr, exclude))
			break;

	return false;
}
#endif

class KeyHandler : public OgreBites::InputListener {
	bool keyPressed(const OgreBites::KeyboardEvent &evt) override
	{
		if (evt.keysym.sym == OgreBites::SDLK_ESCAPE) {
			Ogre::Root::getSingleton().queueEndRendering();
		}
		return true;
	}
};
bool MainWorld::frameStarted(const Ogre::FrameEvent &evt)
{
	WorldData::get_singleton()->update(evt.timeSinceLastFrame);
	return true;
}
class App : public OgreBites::ApplicationContext {
	Ogre::SceneNode *mCameraNode;
	Ogre::SceneManager *mScnMgr;
	std::unique_ptr<CharacterController> mCharacter;
	KeyHandler mKeyHandler;
	MainWorld mWorld;
	LuaControl lua;

public:
	App();
	virtual ~App();

	void setup();
	void locateResources();
	void initCamera();
	Ogre::SceneManager *getSceneManager()
	{
		return mScnMgr;
	}
	void createContent();
	void createCharacter();
	void setupWorld();
};

App::App()
	: OgreBites::ApplicationContext("App")
{
}
void App::setup()
{
	OgreBites::ApplicationContext::setup();
	Ogre::Root *root = getRoot();
	Ogre::SceneManager *scnMgr = root->createSceneManager();
	mScnMgr = scnMgr;
}
void App::locateResources()
{
	OgreBites::ApplicationContext::locateResources();
}
App::~App()
{
}
void App::initCamera()
{
	// also need to tell where we are
	mCameraNode = mScnMgr->getRootSceneNode()->createChildSceneNode();
	mCameraNode->setPosition(0, 2, 3);
	mCameraNode->lookAt(Ogre::Vector3(0, 1, -1), Ogre::Node::TS_PARENT);

	// create the camera
	Ogre::Camera *cam = mScnMgr->createCamera("tps_camera");
	cam->setNearClipDistance(0.1f); // specific to this sample
	cam->setAutoAspectRatio(true);
	mCameraNode->attachObject(cam);

	// and tell it to render into the main window
	getRenderWindow()->addViewport(cam);
}
void App::setupWorld()
{
	addInputListener(&mKeyHandler);
	mWorld.setup();
	getRoot()->addFrameListener(&mWorld);
}
void App::createCharacter()
{
	Ogre::Camera *cam = static_cast<Ogre::Camera *>(
		mCameraNode->getAttachedObject("tps_camera"));
	mCharacter = std::make_unique<CharacterController>(mCameraNode, cam,
							   mScnMgr, &mWorld);
	//    mInputListenerChain = TouchAgnosticInputListenerChain(getRenderWindow(), {&mKeyHandler, mCharacter.get()});
	addInputListener(mCharacter.get());
	WorldData::get_singleton()->initPagedWorld(cam);
}
void App::createContent()
{
	lua.setup(mScnMgr);
	/*
    Ogre::OverlaySystem* overlaySystem = Ogre::OverlaySystem::getSingletonPtr();
    Ogre::OverlayElement* fpsOverlayElement = overlaySystem->getOverlayManager()->getOverlay("MyFpsOverlay");
    if (fpsOverlayElement != nullptr) {
	    Ogre::TextPanel* textPanel = static_cast<Ogre::TextPanel*>(fpsOverlayElement->getChild("MyFpsTextPanel"));
	    if (textPanel != nullptr) {
		    Ogre::String lastFpS = Ogre::StringConverter::toString(renderWindow->getLastFPS());
		    textPanel->setCaption(lastFpS);
	    }
    }
    */
	// without light we would just get a black screen
	Ogre::Light *light = mScnMgr->createLight(lua.dlight_name);
	Ogre::SceneNode *lightNode =
		mScnMgr->getRootSceneNode()->createChildSceneNode();
	//    lightNode->setPosition(0, 10, 15);
	lightNode->setDirection(lua.dlight_direction);
	lightNode->attachObject(light);
	light->setType(Ogre::Light::LT_DIRECTIONAL);
	light->setDiffuseColour(lua.diffuse);
	light->setSpecularColour(lua.specular);
	mScnMgr->setSkyBox(true, lua.skybox_name, lua.skybox_size);
}
int main(int argc, char *argv[])
{
	App ctx;
	ctx.initApp();
	// get a pointer to the already created root
	Ogre::Root *root = ctx.getRoot();
	Ogre::SceneManager *scnMgr = ctx.getSceneManager();

	// register our scene with the RTSS
	Ogre::RTShader::ShaderGenerator *shadergen =
		Ogre::RTShader::ShaderGenerator::getSingletonPtr();
	shadergen->addSceneManager(scnMgr);
	WorldData::init(root, scnMgr, ctx.getRenderWindow());
	ctx.setWindowGrab(true);
	ctx.createContent();
	ctx.initCamera();
	ctx.setupWorld();
	ctx.createCharacter();
	// register for input events
	//    KeyHandler keyHandler;
	//    ctx.addInputListener(&keyHandler);

	ctx.getRoot()->startRendering();
	ctx.setWindowGrab(false);
	ctx.closeApp();
	WorldData::cleanup();
	return 0;
}
