#include <iostream>
#include <Ogre.h>
#include <Jolt/Jolt.h>
#include <Jolt/Physics/Body/BodyID.h>
#include "Components.h"
#include "GameData.h"
#include "PhysicsModule.h"
#include "EditorGizmoModule.h"
#include "EditorInputModule.h"

namespace ECS
{
static void updateCameraGoal(Camera &camera, Ogre::Real deltaYaw,
			     Ogre::Real deltaPitch, Ogre::Real deltaZoom)
{
	camera.mCameraPivot->yaw(Ogre::Degree(deltaYaw), Ogre::Node::TS_PARENT);
	if (!(camera.mPivotPitch + deltaPitch > 25 && deltaPitch > 0) &&
	    !(camera.mPivotPitch + deltaPitch < -60 && deltaPitch < 0)) {
		camera.mCameraPivot->pitch(Ogre::Degree(deltaPitch),
					   Ogre::Node::TS_LOCAL);
		camera.mPivotPitch += deltaPitch;
	}
	Ogre::Real dist = camera.mCameraGoal->_getDerivedPosition().distance(
		camera.mCameraPivot->_getDerivedPosition());
	Ogre::Real distChange = deltaZoom * dist;

	// bound the zoom
	if (!(dist + distChange < 8 && distChange < 0) &&
	    !(dist + distChange > 25 && distChange > 0))
		camera.mCameraGoal->translate(0, 0, distChange,
					      Ogre::Node::TS_LOCAL);
}
EditorInputModule::EditorInputModule(flecs::world &ecs)
{
	ecs.module<EditorInputModule>();
	ecs.system<const EngineData, Input, Camera>("HandleEditorInput")
		.kind(flecs::OnUpdate)
		.each([this](const EngineData &eng, Input &input,
			     Camera &camera) {
			if (!camera.configured) {
				// create a pivot at roughly the character's shoulder
				camera.mCameraPivot =
					eng.mScnMgr->getRootSceneNode()
						->createChildSceneNode();
				camera.mCameraGoal =
					camera.mCameraPivot
						->createChildSceneNode(
							Ogre::Vector3(0, 2, 0));
				camera.mCameraNode->setPosition(
					camera.mCameraPivot->getPosition() +
					camera.mCameraGoal->getPosition());
				camera.mCameraGoal->lookAt(
					Ogre::Vector3(0, 0, -10),
					Ogre::Node::TS_PARENT);
				camera.mCameraPivot->setFixedYawAxis(true);
				camera.mCameraGoal->setFixedYawAxis(true);
				camera.mCameraNode->setFixedYawAxis(true);
				// our model is quite small, so reduce the clipping planes
				camera.mCamera->setNearClipDistance(0.1f);
				camera.mCamera->setFarClipDistance(700);

				camera.mPivotPitch = 0;
				camera.configured = true;
			}
			/* handle input */
			//			if (input.control == input.control_prev)
			//				return;
			uint32_t pressed = input.control & ~input.control_prev;
			uint32_t released = input.control_prev & ~input.control;
			uint32_t active = input.control;
			float zaxis = input.motion.z;
			zaxis *= 0.9f;
			if (!camera.mCameraPivot || !camera.mCameraGoal)
				return;
			// OgreAssert(false, "EditorInput");
			if (pressed & 1)
				std::cout << "W pressed\n";
			if (released & 1)
				std::cout << "W released\n";
			if (active & 1)
				zaxis -= 1.0f;
			if (active & 4)
				zaxis += 1.0f;
			if (zaxis > -1.0f && zaxis < 1.0f)
				zaxis = 0.0f;
			else
				zaxis = Ogre::Math::Sign(zaxis);
			if (active & 32)
				input.act = true;
			else
				input.act = false;
			if (pressed & 32)
				input.act_pressed = true;
			else
				input.act_pressed = false;
			if (active & 64)
				input.act2 = true;
			else
				input.act2 = false;
			if (pressed & 64)
				input.act2_pressed = true;
			else
				input.act2_pressed = false;
			input.motion.z = zaxis;
			float xaxis = input.motion.x;
			xaxis *= 0.9f;
			if (active & 2)
				xaxis = -1.0f;
			if (active & 8)
				xaxis += 1.0f;
			if (xaxis > -1.0f && xaxis < 1.0f)
				xaxis = 0.0f;
			else
				xaxis = Ogre::Math::Sign(xaxis);
			input.motion.x = xaxis;
			if (active & 16)
				input.fast = true;
			else
				input.fast = false;
			if (input.control & 256) {
				/* click */
				std::vector<Ogre::MovableObject *> objects;
				Ogre::Viewport *viewport =
					ECS::get<Camera>()
						.mCamera->getViewport();
				Ogre::Real nx =
					static_cast<Ogre::Real>(
						input.mouse_abs.x) /
					(float)viewport->getActualWidth();
				Ogre::Real ny =
					static_cast<Ogre::Real>(
						input.mouse_abs.y) /
					(float)viewport->getActualHeight();
				Ogre::Ray ray =
					ECS::get<Camera>()
						.mCamera->getCameraToViewportRay(
							nx, ny);

				Ogre::Vector3 position;
				Ogre::Plane horizontalZeroPlane(
					Ogre::Vector3::UNIT_Y, 0.0f);
				std::cout << "ray: " << ray.getOrigin() << " "
					  << ray.getDirection() << std::endl;
				Ogre::RayTestResult ogreResult =
					ray.intersects(horizontalZeroPlane);
				if (ogreResult.first) {
					position =
						ray.getPoint(ogreResult.second);
					Ogre::Vector3 position2;
                    JPH::BodyID id;
					bool hit = PhysicsModule::raycastQuery(
						ray.getOrigin(),
						ray.getPoint(2000.0f),
                        position2, id);
					if (hit) {
						float d1 =
							ray.getOrigin()
								.squaredDistance(
									position);
						float d2 =
							ray.getOrigin()
								.squaredDistance(
									position2);
						if (d2 < d1)
							position = position2;
					}
					std::cout << "HIT!: " << position
						  << std::endl;
					ECS::get<EditorGizmo>()
						.sceneNode->_setDerivedPosition(
							position);
				} else {
                    JPH::BodyID id;
					bool hit = PhysicsModule::raycastQuery(
						ray.getOrigin(),
                        ray.getPoint(2000.0f), position,
                        id);
					if (hit) {
						std::cout
							<< "HIT!: " << position
							<< std::endl;
						ECS::get<EditorGizmo>()
							.sceneNode
							->_setDerivedPosition(
								position);
					}
				}
			}
			if (input.control & 512) {
				if (input.mouse_moved) {
					updateCameraGoal(camera,
							 -0.18f * input.mouse.x,
							 -0.12f * input.mouse.y,
							 0);
					input.mouse_moved = false;
					input.mouse.x = 0;
					input.mouse.y = 0;
					// move the camera smoothly to the goal
					Ogre::Vector3 goalOffset =
						camera.mCameraGoal
							->_getDerivedPosition() -
						camera.mCameraNode
							->getPosition();
					camera.mCameraNode->translate(
						goalOffset * eng.delta * 9.0f);
					camera.mCameraNode->_setDerivedOrientation(
						camera.mCameraGoal
							->_getDerivedOrientation());
				}
				if ((input.control & 0xf)) {
					Ogre::Vector3 forward =
						camera.mCameraPivot
							->_getDerivedOrientation() *
						Ogre::Vector3::UNIT_Z;
					Ogre::Vector3 side =
						camera.mCameraPivot
							->_getDerivedOrientation() *
						Ogre::Vector3::UNIT_X;
					forward.y = 0.0f;
					side.y = 0.0f;
					Ogre::Vector3 translation =
						forward * input.motion.z +
						side * input.motion.x;
					camera.mCameraPivot->translate(
						translation * 20.0f * eng.delta,
						Ogre::Node::TS_WORLD);
				}
			}
			input.control_prev = input.control;
#if 0
			if (input.wheel_moved) {
				updateCameraGoal(camera, 0, 0,
						 -0.15f * input.wheel_y);
			std::cout << "mouse moved: " << input.mouse_moved
				  << " " << std::endl;
				input.wheel_moved = false;
				input.wheel_y = 0;
			}
#endif
			ECS::get().modified<ECS::Input>();
		});
}
}
