#include <iostream>
#include <Ogre.h>
#include <OgreMeshManager.h>
#include <pugixml.hpp>
#include "loader.h"
#include "Components.h"
#include "GameData.h"
#include "PhysicsModule.h"
#include "EventModule.h"
#include "EventTriggerModule.h"
#include "CharacterModule.h"
#include "WaterModule.h"
#include "BoatModule.h"

namespace ECS
{

BoatModule::BoatModule(flecs::world &ecs)
{
	ecs.module<BoatModule>();
	ecs.component<BoatBase>();
	ecs.component<BoatType>();
	ecs.component<SpawnBoat>();
	ecs.component<BoatCurrentActuator>();
	ecs.import <EventModule>();
	ecs.import <EventTriggerModule>();
	ecs.import <CharacterModule>();
	ecs.import <WaterModule>();
	ecs.observer<const EngineData, const BoatType>("CreateBoat")
		.event(flecs::OnSet)
		.without<BoatBase>()
		.without<SpawnBoat>()
		.each([&](flecs::entity e, const EngineData &eng,
			  const BoatType &type) {
			e.add<SpawnBoat>();
			e.set<EventData>({});
		});
	ecs.system<const EngineData, const BoatType>("CreateBoatS")
		.kind(flecs::OnUpdate)
		.without<BoatBase>()
		.with<WaterReady>()
		.with<SpawnBoat>()
		.each([&](flecs::entity e, const EngineData &eng,
			  const BoatType &type) {
			if (type.resourceName.find(".scene") !=
			    std::string::npos) {
				BoatBase &boat = e.ensure<BoatBase>();
				int i;
				std::vector<Ogre::SceneNode *> colliderTarget;
				boat.mNode =
					ECS::get<EngineData>()
						.mScnMgr->getRootSceneNode()
						->createChildSceneNode(
							type.position,
							type.orientation);
				auto pnodes = boat.mNode->getChildren();
				for (i = 0; i < pnodes.size(); i++) {
					Ogre::SceneNode *pnode =
						static_cast<Ogre::SceneNode *>(
							pnodes[i]);
					Ogre::Any any =
						pnode->getUserObjectBindings()
							.getUserAny("type");
					if (!any.has_value())
						continue;
					Ogre::String obj_type =
						Ogre::any_cast<Ogre::String>(
							any);
					if (obj_type == "hull-collider") {
						/* FIXME */
					}
				}
				{
					Ogre::SceneNode *attachment =
						eng.mScnMgr->getRootSceneNode()
							->createChildSceneNode();
					Ogre::DataStreamPtr scene =
						Ogre::ResourceGroupManager::getSingleton()
							.openResource(
								type.resourceName,
								Ogre::ResourceGroupManager::
									AUTODETECT_RESOURCE_GROUP_NAME);
					SceneLoader loader;
					loader.load(scene, "General",
						    attachment, e);
					//				attachment->loadChildren(type.resourceName);
					std::vector<Ogre::Node *> v =
						attachment->getChildren();
					OgreAssert(v.size() == 1,
						   "Bad root nodes count in " +
							   type.resourceName);
					Ogre::Any any =
						static_cast<Ogre::SceneNode *>(
							v[0])
							->getUserObjectBindings()
							.getUserAny("type");
					OgreAssert(any.has_value(),
						   "no \"type\" costom prop");
					Ogre::String obj_type =
						Ogre::any_cast<Ogre::String>(
							any);
					std::cout << "type: " << obj_type
						  << std::endl;
					OgreAssert(obj_type == "boat",
						   "not a boat");
					boat.mNode =
						static_cast<Ogre::SceneNode *>(
							v[0]);
					boat.mNode->_setDerivedPosition(
						type.position);
					boat.mNode->_setDerivedOrientation(
						type.orientation);
					boat.mEnt = static_cast<Ogre::Entity *>(
						boat.mNode->getAttachedObject(
							std::to_string(
								(int)e.raw_id()) +
							"/boat"));
					loader.setupPhysicsBody(boat.mNode,
								boat.mEnt, e);
					e.remove<SpawnBoat>();
					e.modified<BoatBase>();
				}
				// BoatBody &body = e.ensure<BoatBody>();
				// e.modified<BoatBase>();
			}
			//			ECS::get_mut<ECS::EngineData>().enableDbgDraw = true;
			//			ECS::modified<ECS::EngineData>();
			// e.modified<BoatBody>();
		});
	ecs.observer<const BoatBase, EventData>("HandleBoatEvents")
		.event(flecs::OnSet)
		.each([](flecs::entity e, const BoatBase &boat,
			 EventData &evt) {
			for (auto ev : evt.events) {
				std::cout << "event: " << ev.event << " "
					  << ev.sender.name() << " "
					  << ev.e1.name() << " " << ev.e2.name()
					  << std::endl;
				if (ev.event == "actuator_created") {
					continue;
				} else if (ev.event == "actuator_enter") {
					if (e.has<BoatCurrentActuator>())
						continue;
					if (ev.sender.has<EventTrigger>()) {
						if (ev.e2.has<Player>())
							PhysicsModule::
								controlPhysics(
									ev.e2,
									false);
						const EventTrigger &trigger =
							ev.sender.get<
								EventTrigger>();
						Ogre::SceneNode *node =
							trigger.node;
						Ogre::Any animationAny =
							node->getUserObjectBindings()
								.getUserAny(
									"trigger_animation");
						if (animationAny.has_value()) {
							Ogre::String animation =
								Ogre::any_cast<
									Ogre::String>(
									animationAny);
							ev.e2.set<
								CharacterVelocity>(
								{ { 0, 0, 0 },
								  { 0, 0,
								    0 } });
							if (animation.length() >
							    0)
								ev.e2.set<
									CharacterInActuator>(
									{ animation,
									  { 0,
									    0,
									    0 } });
							else
								ev.e2.remove<
									CharacterInActuator>();
						}
						Ogre::SceneNode *target_node =
							nullptr;
						Ogre::Any targetAny =
							node->getUserObjectBindings()
								.getUserAny(
									"target");
						if (targetAny.has_value()) {
							OgreAssert(
								targetAny
									.has_value(),
								"need target");
							target_node = Ogre::any_cast<
								Ogre::SceneNode
									*>(
								targetAny);
							Ogre::Vector3 position =
								target_node
									->_getDerivedPosition();
							Ogre::Quaternion orientation =
								target_node
									->_getDerivedOrientation();
							if (ev.e2.has<
								    CharacterBase>()) {
                                Ogre::SceneNode *n =
                                    ECS::get<
                                        CharacterModule>()
                                        .characterNodes
                                        .at(ev.e2);
                                n->_setDerivedPosition(
                                    position);
                                n->_setDerivedOrientation(
                                    orientation);
							}
						}
						e.set<BoatCurrentActuator>(
							{ ev.event,
							  ev.sender });
					}
				} else if (ev.event == "actuator_forward") {
					/* */
					if (e.has<BoatCurrentActuator>()) {
						if (e.get<BoatCurrentActuator>()
							    .actuator ==
						    "actuator_enter")
							ev.e2.set<
								CharacterInActuator>(
								{ "swimming-edge-climb",
								  { 0, 0,
								    0 } });
					}
				} else if (ev.event == "actuator_backward") {
					/* */
				} else if (ev.event ==
					   "animation:swimming-edge-climb:end") {
					ev.e2.remove<CharacterInActuator>();
					PhysicsModule::controlPhysics(ev.e2,
								      true);
					ev.e2.remove<InTrigger>(ev.e1);
					ev.e1.remove<TriggeredBy>(ev.e2);
					ev.e2.add<CharacterGravity>();
					ev.e2.add<CharacterBuoyancy>();
					e.remove<BoatCurrentActuator>();
				} else if (ev.event == "boat_control_enter") {
					Ogre::SceneNode *captainSeat = nullptr;
					std::vector<Ogre::Node *> children =
						boat.mNode->getChildren();
					for (auto child : children) {
						Ogre::Any nameAny =
							child->getUserObjectBindings()
								.getUserAny(
									"name");
						if (nameAny.has_value()) {
							Ogre::String name =
								Ogre::any_cast<
									Ogre::String>(
									nameAny);
							std::cout << "name: "
								  << name
								  << std::endl;
							if (name ==
							    "captain_seat") {
								captainSeat = static_cast<
									Ogre::SceneNode
										*>(
									child);
								break;
							}
						}
					}
					OgreAssert(captainSeat, "seats");
					PhysicsModule::controlPhysics(ev.e2,
								      false);
					ev.e2.set<CharacterVelocity>(
						{ { 0, 0, 0 }, { 0, 0, 0 } });
					ev.e2.set<CharacterInActuator>(
						{ "sitting", { 0, 0, 0 } });
					Ogre::Vector3 position =
						captainSeat
							->_getDerivedPosition();
					Ogre::Quaternion orientation =
						captainSeat
							->_getDerivedOrientation();
					if (ev.e2.has<CharacterBase>()) {
                        Ogre::SceneNode *n =
                            ECS::get<CharacterModule>()
                                .characterNodes
                                .at(ev.e2);
                        n->_setDerivedPosition(
                            position);
                        n->_setDerivedOrientation(
                            orientation);
					}
				} else if (ev.event == "boat_control_exit") {
				} else if (ev.event == "actuator_exit") {
					/* */
				} else
					OgreAssert(false, "event");
			}
			evt.events.clear();
		});
#if 0
	ecs.system<const EngineData, BoatType, Body2Entity>("CreateBoat")
		.kind(flecs::OnUpdate)
		.without<BoatBase>()
		.without<BoatBody>()
		.each([](flecs::entity e, const EngineData &eng, BoatType &type,
			 Body2Entity &b2e) {
			BoatBase &boat = e.ensure<BoatBase>();
			if (type.resourceName.find(".glb") !=
			    std::string::npos) {
				boat.mEnt = ECS::get<EngineData>()
						    .mScnMgr->createEntity(
							    type.resourceName);
				boat.mNode = eng.mScnMgr->getRootSceneNode()
						     ->createChildSceneNode(
							     type.position,
							     type.orientation);
				boat.mNode->attachObject(boat.mEnt);

#if 0
				BoatBody &body = e.ensure<BoatBody>();
				body.body =
					ECS::get<EngineData>()
						.mWorld->addRigidBody(
							0, boat.mEnt,
							Ogre::Bullet::CT_TRIMESH,
							nullptr, 2, 0x7fffffff);
				b2e.entities[body.body] = e;
#endif
			} else if (type.resourceName.find(".scene") !=
				   std::string::npos) {
				int i;
				std::vector<Ogre::SceneNode *> colliderTarget;
				boat.mNode =
					ECS::get<EngineData>()
						.mScnMgr->getRootSceneNode()
						->createChildSceneNode(
							type.position,
							type.orientation);
				auto pnodes = boat.mNode->getChildren();
				for (i = 0; i < pnodes.size(); i++) {
					Ogre::SceneNode *pnode =
						static_cast<Ogre::SceneNode *>(
							pnodes[i]);
					Ogre::Any any =
						pnode->getUserObjectBindings()
							.getUserAny("type");
					if (!any.has_value())
						continue;
					Ogre::String obj_type =
						Ogre::any_cast<Ogre::String>(
							any);
					if (obj_type == "hull-collider") {
						/* FIXME */
					}
				}

				Ogre::SceneNode *attachment =
					eng.mScnMgr->getRootSceneNode()
						->createChildSceneNode();
				Ogre::DataStreamPtr scene =
					Ogre::ResourceGroupManager::getSingleton()
						.openResource(
							type.resourceName,
							Ogre::ResourceGroupManager::
								AUTODETECT_RESOURCE_GROUP_NAME);
				SceneLoader loader;
				loader.load(scene, "General", attachment, e);
				//				attachment->loadChildren(type.resourceName);
				std::vector<Ogre::Node *> v =
					attachment->getChildren();
				OgreAssert(v.size() == 1,
					   "Bad root nodes count in " +
						   type.resourceName);
				Ogre::Any any =
					static_cast<Ogre::SceneNode *>(v[0])
						->getUserObjectBindings()
						.getUserAny("type");
				OgreAssert(any.has_value(),
					   "no \"type\" costom prop");
				Ogre::String obj_type =
					Ogre::any_cast<Ogre::String>(any);
				std::cout << "type: " << obj_type << std::endl;
				OgreAssert(obj_type == "boat", "not a boat");
				boat.mNode =
					static_cast<Ogre::SceneNode *>(v[0]);
				boat.mNode->_setDerivedPosition(type.position);
				boat.mNode->_setDerivedOrientation(
					type.orientation);
				boat.mEnt = static_cast<Ogre::Entity *>(
					boat.mNode->getAttachedObject(
						std::to_string((int)e.raw_id()) +
						"/boat"));
				loader.setupPhysicsBody(boat.mNode, boat.mEnt);
				BoatBody &body = e.ensure<BoatBody>();
#if 0
				Ogre::Any bodyA =
					boat.mNode->getUserObjectBindings()
						.getUserAny("bodyPointer");
				if (bodyA.has_value())
					body.body =
						Ogre::any_cast<btRigidBody *>(
							bodyA);
#if 0
				body.body =
					ECS::get<EngineData>()
						.mWorld->addRigidBody(
							0, boat.mEnt,
							Ogre::Bullet::CT_TRIMESH,
							nullptr, 2, 0x7fffffff);
#endif
				OgreAssert(body.body, "No body :()");
				b2e.entities[body.body] = e;
#endif
				std::vector<Ogre::Node *> slots =
					boat.mNode->getChildren();
				for (i = 0; i < slots.size(); i++) {
					Ogre::Any any =
						static_cast<Ogre::SceneNode *>(
							slots[i])
							->getUserObjectBindings()
							.getUserAny("type");
					if (!any.has_value())
						continue;
					Ogre::String obj_type =
						Ogre::any_cast<Ogre::String>(
							any);
					std::cout << "child type: " << obj_type
						  << std::endl;
				}
				if (slots.size() > 0) {
					ObjectSlots &vs =
						e.ensure<ObjectSlots>();
					for (i = 0; i < slots.size(); i++) {
						Ogre::Any any =
							static_cast<Ogre::SceneNode
									    *>(
								slots[i])
								->getUserObjectBindings()
								.getUserAny(
									"type");
						if (!any.has_value())
							continue;
						Ogre::String obj_type =
							Ogre::any_cast<
								Ogre::String>(
								any);
						any = static_cast<
							      Ogre::SceneNode *>(
							      slots[i])
							      ->getUserObjectBindings()
							      .getUserAny(
								      "name");
						if (!any.has_value())
							continue;
						Ogre::String obj_name =
							Ogre::any_cast<
								Ogre::String>(
								any);
						vs.slots[obj_name] = {
							obj_type,
							static_cast<Ogre::SceneNode
									    *>(
								slots[i])
						};
					}
				}
			}
			//			ECS::get_mut<ECS::EngineData>().enableDbgDraw = true;
			//			ECS::modified<ECS::EngineData>();
			e.modified<BoatBody>();
		});
#endif
}
}
