#include <iostream>
#include "Components.h"
#include "EventTriggerModule.h"
#include "CharacterModule.h"
#include "PhysicsModule.h"
#include "CharacterAnimationModule.h"
#include "EventModule.h"
#include "TerrainModule.h"
#include "WaterModule.h"
#include "world-build.h"
#include "AnimationSystem.h"
#include <tracy/Tracy.hpp>
namespace ECS
{
CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
{
    ZoneScoped;
	ecs.module<CharacterAnimationModule>();
	ecs.component<AnimationControl>();
	ecs.import <EventModule>();
	ecs.import <TerrainModule>();
	ecs.import <WaterModule>();
	ecs.import <PhysicsModule>();
	ecs.system<const CharacterBase, AnimationControl>("HandleAnimations")
		.kind(flecs::OnUpdate)
		.each([this](flecs::entity e, const CharacterBase &ch,
                 AnimationControl &anim) {
            ZoneScopedN("HandleAnimations");
            if (!anim.configured) {
				int i, j;
				e.set<EventData>({});
                Ogre::SceneNode *n =
                    ECS::get<CharacterModule>()
                        .characterNodes.at(e);
                Ogre::Entity *ent = static_cast<Ogre::Entity *>(
                    n->getAttachedObject(0));
                ent->getSkeleton()->setBlendMode(
                    Ogre::ANIMBLEND_CUMULATIVE);
                Ogre::AnimationStateSet *animStateSet =
                    ent->getAllAnimationStates();
                const Ogre::AnimationStateMap &animMap =
                    animStateSet->getAnimationStates();
                anim.mAnimationSystem =
                    new AnimationSystem::AnimationSystem(
                        false);
                ent->getSkeleton()
                    ->getBone("Root")
                    ->removeAllChildren();
                for (auto it = animMap.begin();
                     it != animMap.end(); it++) {
                    AnimationSystem::Animation *animation =
                        new AnimationSystem::Animation(
                            ent->getSkeleton(),
                            it->second,
                            ent->getSkeleton()
                                ->getAnimation(
                                    it->first));
#ifdef VDEBUG
					std::cout
						<< "animation: " << animNames[i]
						<< std::endl;
#endif
					animation->setLoop(true);
					anim.mAnimationSystem->add_animation(
                        it->first, animation);
				}
				anim.mAnimationSystem
					->builder()
					/* clang-format off */
					->output()
					->state_machine(ANIM_FADE_SPEED, "main")
						->state("locomotion")
							->state_machine(ANIM_FADE_SPEED, "locomotion-state")
								->state("idle")
									->animation("idle")
								->end()
								->state("walking")
									->animation("walking")
								->end()
								->state("running")
									->animation("running")
								->end()
								->state("treading_water")
									->animation("treading_water")
								->end()
								->state("swimming")
									->animation("swimming")
								->end()
								->state("swimming-fast")
									->speed(20.0f)
										->animation("swimming")
									->end()
								->end()
							->end()
						->end()
						->state("actuator")
							->state_machine(ANIM_FADE_SPEED * 10.0f, "actuator-state")
								->state("hanging-idle")
									->animation("hanging-idle")
								->end()
								->state("swimming-hold-edge")
									->animation("swimming-hold-edge")
								->end()
								->state("swimming-edge-climb")
									->animation("swimming-edge-climb")
                                    ->trigger_entity(e, "end_of_climb", 0.99f, "animation:swimming-edge-climb:end")
								->end()
								->state("hanging-climb")
									->animation("hanging-climb")
                                    ->trigger_entity(e, "end_of_climb2", 0.99f, "animation:hanging-climb:end")
								->end()
								->state("idle")
									->animation("idle-act")
								->end()
								->state("pass-character")
									->animation("pass-character")
                                    ->trigger_entity(e, "pass-character", 0.99f, "animation:pass-character:end")
								->end()
								->state("character-talk")
									->animation("character-talk")
								->end()
								->state("sitting")
									->animation("sitting-chair")
								->end()
								->state("sitting-ground")
									->animation("sitting-ground")
								->end()
                                ->state("sitting-chair")
                                    ->animation("sitting-chair")
                                ->end()
                                ->transition_end("swimming-edge-climb", "idle")
								->transition_end("hanging-climb", "idle")
								->transition_end("pass-character", "idle")
							->end()
						->end()
					->end();
				/* clang-format on */

				anim.mAnimationSystem
                    ->get<AnimationSystem::
                              AnimationNodeStateMachine>(
                        "main")
					->setAnimation("locomotion", true);
				anim.mAnimationSystem
                    ->get<AnimationSystem::
                              AnimationNodeStateMachine>(
						"locomotion-state")
					->setAnimation("idle", true);
				anim.configured = true;
			}
		});
#if 0
	ecs.system<CharacterBase>("RootMotionStart")
		.kind(flecs::OnUpdate)
		.each([this](flecs::entity e, CharacterBase &ch) {
			ch.mBoneMotion = Ogre::Vector3::ZERO;
		});
#endif
	ecs.system<const EngineData, CharacterBase, AnimationControl>(
		   "HandleAnimations1")
		.kind(flecs::OnUpdate)
		.each([this](flecs::entity e, const EngineData &eng,
			     CharacterBase &ch, AnimationControl &anim) {
            ZoneScopedN("HandleAnimations1");
            float delta = eng.delta;
			// ch.mBoneMotion = Ogre::Vector3::ZERO;
			if (!anim.mAnimationSystem)
				return;
            bool result = anim.mAnimationSystem->addTime(delta);
            Ogre::Vector3 rootMotion =
                anim.mAnimationSystem->getRootMotionDelta();
            ch.mBonePrevMotion = ch.mBoneMotion;
            ch.mBoneMotion = rootMotion;
#if 0
            {
                ZoneScopedN("Ogre::Entity::_updateAnimation");
                ch.mBodyEnt->_updateAnimation();
            }
            ch.mBodyEnt->getSkeleton()->getBone("Root")->setPosition(
                Ogre::Vector3::ZERO);
#endif
        // The value we get is interpolated value. When result is true it is new step

#undef VDEBUG
#ifdef VDEBUG
			std::cout << "root motion: " << delta << ": "
				  << ch.mBoneMotion << " - "
				  << ch.mRootBone->getPosition()
				  << " result: " << result << std::endl;
#endif
#undef VDEBUG
		});
	ecs.system<const EngineData, CharacterBase, CharacterVelocity>(
		   "HandleRootMotionVelocity")
		.kind(flecs::OnUpdate)
		.with<TerrainReady>()
		.with<WaterReady>()
		.each([this](flecs::entity e, const EngineData &eng,
			     CharacterBase &ch, CharacterVelocity &v) {
            ZoneScopedN("HandleRootMotionVelocity");
            if (eng.delta < 0.0000001f)
				return;
            if (ECS::get<CharacterModule>().characterNodes.find(
                    e) ==
                ECS::get<CharacterModule>().characterNodes.end())
				return;
            const Ogre::SceneNode *n =
                ECS::get<CharacterModule>().characterNodes.at(
                    e);
            Ogre::Quaternion rot = n->getOrientation();
            Ogre::Vector3 pos = n->getPosition();
			Ogre::Vector3 boneMotion = ch.mBoneMotion;
			v.velocity = Ogre::Vector3::ZERO;
            if (eng.delta <= 0.005)
                return;
			float safeDelta =
                Ogre::Math::Clamp(eng.delta, 0.005f, 0.99f);
#if 0
			if (!e.has<CharacterInActuator>()) {
				v.velocity = Ogre::Math::lerp(
					v.velocity,
					rot * boneMotion / safeDelta, 0.99f);
			} else {
				// v.velocity = rot * boneMotion / safeDelta;
				v.velocity = Ogre::Math::lerp(
					v.velocity,
					rot * boneMotion / safeDelta, 0.99f);
			}
#endif
			v.velocity = rot * boneMotion / safeDelta;
			//			if (v.velocity.squaredLength() > 1.4f * 1.4f)
			//				v.velocity = v.velocity.normalisedCopy() * 1.4f;
			// ch.mBoneMotion = Ogre::Vector3::ZERO;
			// safety
			// std::cout << "velocity: " << v.velocity << std::endl;
			v.velocity.x =
				Ogre::Math::Clamp(v.velocity.x, -16.0f, 16.0f);
			v.velocity.z =
				Ogre::Math::Clamp(v.velocity.z, -16.0f, 16.0f);
			v.velocity.y =
				Ogre::Math::Clamp(v.velocity.y, -10.5f, 10.0f);
#if 0
			v.velocity.y = 0.0f;
#endif
            OgreAssert(v.velocity.squaredLength() < 1000.0f,
                   "Bad velocity setting " + Ogre::StringConverter::toString(safeDelta) + " " + Ogre::StringConverter::toString(boneMotion));
		});
	ecs.system<const EngineData, CharacterBase, AnimationControl,
		   CharacterVelocity>("HandleRootMotion")
		.kind(flecs::OnUpdate)
		.each([this](flecs::entity e, const EngineData &eng,
			     CharacterBase &ch, AnimationControl &anim,
			     CharacterVelocity &v) {
            ZoneScopedN("HandleRootMotion");
#if 0
			if (!ch.mBodyNode)
				return;
#endif
			if (eng.delta < 0.0000001f)
				return;
			OgreAssert(eng.delta > 0.0f, "Zero delta");
			int maxPen = 0;
			Ogre::Vector3 colNormal;
			bool is_on_floor = false;
			bool penetration = false;
		});

	ecs.system<const Input, const CharacterBase, AnimationControl>(
		   "HandleNPCAnimations")
		.kind(flecs::OnUpdate)
		.with<Character>()
		.without<Player>()
		.each([](flecs::entity e, const Input &input,
			 const CharacterBase &ch, AnimationControl &anim) {
            ZoneScopedNC("HandleNPCAnimations", 0xFF2020);
            if (!anim.configured)
				return;
            if (!anim.mAnimationSystem)
                return;
            AnimationSystem::AnimationNodeStateMachine
                *state_machine = anim.mAnimationSystem->get<
                    AnimationSystem::
                        AnimationNodeStateMachine>(
                    "locomotion-state");
			Ogre::String current_state =
				state_machine->getCurrentState();
			Ogre::String next_state = "idle";
			if (current_state != "treading_water" &&
                e.has<InWater>())
				next_state = "treading_water";
            if (current_state != "idle" && !e.has<InWater>())
				next_state = "idle";
			state_machine->setAnimation(next_state);
            {
                ZoneScoped;
                ZoneTextF("animation: next_state: %s %d %d",
                      next_state.c_str(),
                      (int)ch.is_submerged,
                      (int)e.has<InWater>());
            }
		});
	ecs.system<const CharacterBase, const CharacterInActuator,
		   AnimationControl>("HandlePlayerAnimationsActuator")
		.kind(flecs::OnUpdate)
		.with<Character>()
		.each([](flecs::entity e, const CharacterBase &ch,
			 const CharacterInActuator &inact,
			 AnimationControl &anim) {
            ZoneScopedN("HandlePlayerAnimationsActuator");
            if (!anim.configured)
				return;
            AnimationSystem::AnimationNodeStateMachine *main_sm =
				anim.mAnimationSystem
                    ->get<AnimationSystem::
                              AnimationNodeStateMachine>(
						"main");
            AnimationSystem::AnimationNodeStateMachine *actuator_sm =
				anim.mAnimationSystem
                    ->get<AnimationSystem::
                              AnimationNodeStateMachine>(
						"actuator-state");
			Ogre::String current_state = main_sm->getCurrentState();
			if (current_state != "actuator")
				main_sm->setAnimation("actuator", true);
			actuator_sm->setAnimation(inact.animationState, true);
		});
	ecs.system<const CharacterBase, AnimationControl>(
		   "HandlePlayerAnimationsNoActuator")
		.kind(flecs::OnUpdate)
		.with<Character>()
		.without<CharacterInActuator>()
        .without<CharacterControlDisable>()
        .each([](flecs::entity e, const CharacterBase &ch,
			 AnimationControl &anim) {
            ZoneScopedN("HandlePlayerAnimationsNoActuator");
            if (!anim.configured)
				return;
			if (!anim.mAnimationSystem)
				return;
            AnimationSystem::AnimationNodeStateMachine *main_sm =
				anim.mAnimationSystem
                    ->get<AnimationSystem::
                              AnimationNodeStateMachine>(
						"main");
			Ogre::String current_state = main_sm->getCurrentState();
			if (current_state != "locomotion")
				main_sm->setAnimation("locomotion", true);
		});
	ecs.system<const Input, const CharacterBase, AnimationControl>(
		   "HandlePlayerAnimations")
		.kind(flecs::OnUpdate)
		.with<Character>()
		.with<Player>()
		.without<CharacterInActuator>()
        .without<CharacterControlDisable>()
		.each([](flecs::entity e, const Input &input,
			 const CharacterBase &ch, AnimationControl &anim) {
            ZoneScopedN("HandlePlayerAnimations");
            if (!anim.configured)
				return;
            AnimationSystem::AnimationNodeStateMachine
                *state_machine = anim.mAnimationSystem->get<
                    AnimationSystem::
                        AnimationNodeStateMachine>(
                    "locomotion-state");
			Ogre::String current_state =
				state_machine->getCurrentState();
			bool controls_idle = input.motion.zeroLength();
			bool anim_is_idle = current_state == "idle" ||
					    current_state == "treading_water";
			bool anim_is_walking = current_state == "walking";
			bool anim_is_running = current_state == "running";
			bool anim_is_swimming_slow = current_state ==
						     "swimming";
			bool anim_is_swimming_fast = current_state ==
						     "swimming-fast";
			bool anim_is_swimming = anim_is_swimming_slow ||
						anim_is_swimming_fast;
			bool anim_is_motion = anim_is_walking ||
					      anim_is_running ||
					      anim_is_swimming;
			bool start_motion = !controls_idle && anim_is_idle;
			bool end_motion = controls_idle && !anim_is_idle;
			Ogre::String next_state = current_state;
			if (controls_idle && anim_is_idle) {
				if (current_state != "treading_water" &&
				    ch.is_submerged)
					next_state = "treading_water";
				else if (current_state != "idle" &&
					 !ch.is_submerged)
					next_state = "idle";
				state_machine->setAnimation(next_state);
			} else if (start_motion) {
				if (ch.is_submerged) {
					if (input.fast)
						next_state = "swimming-fast";
					else
						next_state = "swimming";
				} else {
					if (input.fast)
						next_state = "running";
					else
						next_state = "walking";
				}
				state_machine->setAnimation(next_state, true);
			} else if (end_motion) {
				if (ch.is_submerged)
					state_machine->setAnimation(
						"treading_water");
				else
					state_machine->setAnimation("idle");
			} else {
				if (ch.is_submerged) {
					if (input.fast &&
					    !anim_is_swimming_fast) {
						next_state = "swimming-fast";
					} else if (!input.fast &&
						   !anim_is_swimming_slow) {
						next_state = "swimming";
					}
				} else {
					if (input.fast && !anim_is_running)
						next_state = "running";
					else if (!input.fast &&
						 !anim_is_walking)
						next_state = "walking";
				}
				if (current_state != next_state)
					state_machine->setAnimation(next_state);
			}
		});
	ecs.system<const Input, const CharacterBase, AnimationControl,
		   CharacterInActuator>("HandlePlayerAnimations2")
		.kind(flecs::OnUpdate)
		.with<Character>()
		.with<Player>()
        .without<CharacterControlDisable>()
		.each([](flecs::entity e, const Input &input,
			 const CharacterBase &ch, AnimationControl &anim,
			 CharacterInActuator &act) {
            ZoneScopedN("HandlePlayerAnimations2");
			bool controls_idle = input.motion.zeroLength();
			if (!controls_idle) {
				std::cout << "motion.z: "
					  << Ogre::Math::Abs(input.motion.z -
							     act.prevMotion.z)
					  << std::endl;
				bool trigger_event = false;
				e.each<InTrigger>([&](flecs::entity trig) {
					if (Ogre::Math::Abs(input.motion.z -
							    act.prevMotion.z) >
					    0.001f) {
						if (input.motion.z < 0) {
							trig.get_mut<EventData>()
								.add(e,
								     "actuator_forward",
								     trig, e);
							trig.modified<
								EventData>();
						}
						if (input.motion.z > 0) {
							trig.get_mut<EventData>()
								.add(e,
								     "actuator_backward",
								     trig, e);
							trig.modified<
								EventData>();
						}
					}
					if (input.act_pressed) {
						trig.get_mut<EventData>().add(
							e, "actuator_action",
							trig, e);
						trig.modified<EventData>();
					}
					//					ECS::get_mut<LuaData>().call_handler(
					//						"actuator_update", trig, e);
					trigger_event = true;
				});
				if (!trigger_event) {
					if (Ogre::Math::Abs(input.motion.z -
							    act.prevMotion.z) >
					    0.001f) {
						if (input.motion.z < 0) {
							e.get_mut<EventData>().add(
								e,
								"_in_actuator_forward",
								e, e);
						}
						if (input.motion.z > 0) {
							e.get_mut<EventData>().add(
								e,
								"_in_actuator_backward",
								e, e);
						}
					}
					if (input.act_pressed) {
						e.get_mut<EventData>().add(
							e,
							"_in_actuator_action",
							e, e);
					}
				}
				act.prevMotion.x = input.motion.x;
				act.prevMotion.y = input.motion.y;
				act.prevMotion.z = input.motion.z;
			}

			act.prevMotion = input.motion;
		});
	ecs.system<EventData>("UpdateEvents")
		.kind(flecs::OnUpdate)
		.with<Character>()
		.each([](flecs::entity e, EventData &evt) {
            ZoneScopedN("HandleEvents");
            for (auto ev : evt.events) {
				std::cout << "character event: " << ev.event
					  << std::endl;
				/* parse character events */
				e.each<InTrigger>([&](flecs::entity trig) {
					/* if triggered, dispatch events to trigger */
					trig.get_mut<EventData>().add(
						ev.sender, ev.event,
						trig, // it is easier this way to identify trigger entity
						ev.e2);
				});
			}
			evt.events.clear();
		});

#ifdef VDEBUG
	ecs.system<const CharacterBase>("CharacterGravityStatus")
		.kind(flecs::OnUpdate)
		.with<Character>()
		.with<Player>()
		.each([](flecs::entity e, const CharacterBase &ch) {
			if (e.has<CharacterGravity>())
				std::cout << "gravity\n";
			else
				std::cout << "no gravity\n";
			if (e.has<InWater>())
				std::cout << "in water\n";
			else
				std::cout << "out of water\n";
			std::cout
				<< "h=" << ch.mBodyNode->_getDerivedPosition().y
				<< std::endl;
		});
#endif
	struct AnimationSetCommand : public GameWorld::Command {
		int operator()(const std::vector<GameWorld::Parameter *> &args)
			override
		{
            ZoneScoped;
			GameWorld::ValueParameter<flecs::entity> *param_e =
				static_cast<GameWorld::ValueParameter<
					flecs::entity> *>(args[0]);
			OgreAssert(param_e->get().is_valid(), "bad entity");
			GameWorld::ValueParameter<std::string> *param_node =
				static_cast<GameWorld::ValueParameter<
					std::string> *>(args[1]);
			GameWorld::ValueParameter<std::string> *param_state =
				static_cast<GameWorld::ValueParameter<
					std::string> *>(args[2]);
			if (param_e->get().has<AnimationControl>() &&
			    param_e->get().get<AnimationControl>().configured ==
				    true) {
				const AnimationControl &control =
					param_e->get().get<AnimationControl>();
                AnimationSystem::AnimationNodeStateMachine *sm =
                    control.mAnimationSystem->get<
                        AnimationSystem::
                            AnimationNodeStateMachine>(
                        param_node->get());
				bool reset = false;
				if (args.size() == 4) {
					GameWorld::ValueParameter<bool>
						*param_reset = static_cast<
							GameWorld::ValueParameter<
								bool> *>(
							args[3]);
					reset = param_reset->get();
				}
				sm->setAnimation(param_state->get(), reset);
				std::cout << "animation switch: "
					  << param_node->get() << " "
					  << param_state->get() << std::endl;
			}
			return 0;
		}
	};
	ECS::get_mut<GameWorld>().add_command<AnimationSetCommand>(
        "set_animation_state");
}

}
