use crate::definitions::{
	AnimationHandle, AnimationOverride, AnimationPaused, AnimationPlaybackRate, AnimationSet,
	AnimationStatus, OverrideData, SimpleAnimation, SimpleAnimationStatus, SyncToParent,
};
use crate::directionality::Directionality;
use crate::systems::AnimationCompleted;
use bevy_asset::Assets;
use bevy_ecs::entity::Entity;
use bevy_ecs::hierarchy::ChildOf;
use bevy_ecs::message::MessageWriter;
use bevy_ecs::query::{QueryData, QueryFilter, With, Without};
use bevy_ecs::system::{Commands, Query, Res};
use bevy_sprite::Sprite;
use bevy_time::Time;

#[derive(QueryData)]
#[query_data(mutable)]
pub struct AnimationComponents {
	pub handle: &'static AnimationHandle,
	pub status: &'static mut AnimationStatus,
	pub sprite: &'static mut Sprite,
	pub rate: &'static AnimationPlaybackRate,
}

#[derive(QueryData)]
#[query_data(mutable)]
pub struct DirectionalAnimationComponents {
	pub handle: &'static AnimationHandle,
	pub direction: &'static Directionality,
	pub status: &'static mut AnimationStatus,
	pub sprite: &'static mut Sprite,
	pub rate: &'static AnimationPlaybackRate,
}

#[derive(QueryData)]
#[query_data(mutable)]
pub struct OverrideAnimationComponents {
	pub handle: &'static AnimationHandle,
	pub data: Option<&'static OverrideData>,
	pub status: &'static mut AnimationOverride,
	pub sprite: &'static mut Sprite,
	pub rate: &'static AnimationPlaybackRate,
}

#[derive(QueryData)]
#[query_data(mutable)]
pub struct DirectionalOverrideAnimationComponents {
	pub handle: &'static AnimationHandle,
	pub direction: &'static Directionality,
	pub data: Option<&'static OverrideData>,
	pub status: &'static mut AnimationOverride,
	pub sprite: &'static mut Sprite,
	pub rate: &'static AnimationPlaybackRate,
}

#[derive(QueryData)]
#[query_data(mutable)]
pub struct SimpleAnimationComponents {
	pub anim: &'static SimpleAnimation,
	pub status: &'static mut SimpleAnimationStatus,
	pub sprite: &'static mut Sprite,
}

#[derive(QueryFilter)]
pub struct OnlyAnimations {
	pub _status: With<AnimationStatus>,
	pub _override: Without<AnimationOverride>,
	pub _direction: Without<Directionality>,
	pub _paused: Without<AnimationPaused>,
}

#[derive(QueryFilter)]
pub struct OnlyDirectionalAnimations {
	pub _status: With<AnimationStatus>,
	pub _direction: With<Directionality>,
	pub _override: Without<AnimationOverride>,
	pub _paused: Without<AnimationPaused>,
}

#[derive(QueryFilter)]
pub struct OnlyOverrideAnimations {
	pub _override: With<AnimationOverride>,
	pub _direction: Without<Directionality>,
	pub _paused: Without<AnimationPaused>,
}

#[derive(QueryFilter)]
pub struct OnlyDirectionalOverrideAnimations {
	pub _override: With<AnimationOverride>,
	pub _direction: With<Directionality>,
	pub _paused: Without<AnimationPaused>,
}

macro_rules! get_current_anim {
	($anims: expr, $handle: expr, $name: expr) => {
		match $anims.get($handle.id()) {
			Some(active) => match active.get(&$name) {
				Some(inner) => inner,
				None => continue,
			},
			None => continue,
		}
	};
	($anims: expr, $handle: expr, $name: expr, $($also: expr),+) => {
		match $anims.get($handle.id()) {
			Some(active) => match active.get(&$name)$(.or_else(|| active.get(&$also)))+ {
				Some(inner) => inner,
				None => continue,
			},
			None => continue,
		}
	};
}

macro_rules! tick_animation {
	($delta: expr, $anim: expr, $status: expr, $sprite: expr, $rate: expr) => {{
		if let Some(atlas) = $sprite.texture_atlas.as_mut() {
			let current_frame = atlas.index;
			let mut has_looped = false;

			$status.frame_time += $delta.as_secs_f32() * ::core::ops::Deref::deref($rate);
			while $status.frame_time >= $anim.frame_secs {
				$status.frame_time = ($status.frame_time - $anim.frame_secs).max(0.0);
				let next_frame = $status.active_step.saturating_add(1);
				$status.active_step = if next_frame >= $anim.frames.len() {
					has_looped = true;
					0
				} else {
					next_frame
				};
			}

			if current_frame != $anim.frames[$status.active_step] {
				atlas.index = $anim.frames[$status.active_step];
			}

			has_looped
		} else {
			false
		}
	}};
}

pub fn play_animations(
	time: Res<Time>,
	mut anim_query: Query<AnimationComponents, OnlyAnimations>,
	animations: Res<Assets<AnimationSet>>,
) {
	let delta = time.delta();
	for AnimationComponentsItem {
		mut status,
		handle,
		mut sprite,
		rate,
	} in &mut anim_query
	{
		let anim = get_current_anim!(animations, handle, status.active_name);
		tick_animation!(delta, anim, status, sprite, rate);
	}
}

pub fn play_directional_animations(
	time: Res<Time>,
	mut anim_query: Query<DirectionalAnimationComponents, OnlyDirectionalAnimations>,
	animations: Res<Assets<AnimationSet>>,
) {
	let delta = time.delta();
	for DirectionalAnimationComponentsItem {
		mut status,
		handle,
		mut sprite,
		direction,
		rate,
	} in &mut anim_query
	{
		let anim = get_current_anim!(
			animations,
			handle,
			format!("{}_{}", status.active_name, direction),
			status.active_name
		);

		tick_animation!(delta, anim, status, sprite, rate);
	}
}

pub fn play_override_animation(
	time: Res<Time>,
	mut commands: Commands,
	mut anim_query: Query<(Entity, OverrideAnimationComponents), OnlyOverrideAnimations>,
	animations: Res<Assets<AnimationSet>>,
	mut events: MessageWriter<AnimationCompleted>,
) {
	let delta = time.delta();
	for (
		entity,
		OverrideAnimationComponentsItem {
			mut status,
			handle,
			mut sprite,
			data,
			rate,
		},
	) in &mut anim_query
	{
		let anim = get_current_anim!(animations, handle, status.active_name);
		let looped = tick_animation!(delta, anim, status, sprite, rate);
		if looped {
			commands
				.entity(entity)
				.remove::<(AnimationOverride, OverrideData)>();

			if let Some(data) = data {
				events.write(AnimationCompleted {
					entity,
					user_data: **data,
				});
			}
		}
	}
}

pub fn play_directional_override_animation(
	time: Res<Time>,
	mut commands: Commands,
	mut anim_query: Query<
		(Entity, DirectionalOverrideAnimationComponents),
		OnlyDirectionalOverrideAnimations,
	>,
	animations: Res<Assets<AnimationSet>>,
	mut events: MessageWriter<AnimationCompleted>,
) {
	let delta = time.delta();
	for (
		entity,
		DirectionalOverrideAnimationComponentsItem {
			mut status,
			direction,
			handle,
			mut sprite,
			data,
			rate,
		},
	) in &mut anim_query
	{
		let anim = get_current_anim!(
			animations,
			handle,
			format!("{}_{}", status.active_name, direction),
			status.active_name
		);

		let looped = tick_animation!(delta, anim, status, sprite, rate);
		if looped {
			commands
				.entity(entity)
				.remove::<(AnimationOverride, OverrideData)>();

			if let Some(data) = data {
				events.write(AnimationCompleted {
					entity,
					user_data: **data,
				});
			}
		}
	}
}

pub fn play_simple_animation(
	time: Res<Time>,
	mut anim_query: Query<SimpleAnimationComponents, Without<AnimationPaused>>,
) {
	let delta = time.delta();
	for SimpleAnimationComponentsItem {
		mut status,
		mut sprite,
		anim,
	} in &mut anim_query
	{
		tick_animation!(
			delta,
			*anim,
			status,
			sprite,
			&AnimationPlaybackRate::from(1.0)
		);
	}
}

pub fn sync_child_animation(
	mut children: Query<(&ChildOf, &mut Sprite), With<SyncToParent>>,
	parents: Query<&Sprite, Without<SyncToParent>>,
) {
	for (parent, mut child_sprite) in &mut children {
		if let Ok(parent_sprite) = parents.get(parent.parent()) {
			match (
				parent_sprite.texture_atlas.as_ref(),
				child_sprite.texture_atlas.as_mut(),
			) {
				(Some(parent_value), Some(child_value)) => {
					if child_value.index != parent_value.index {
						*child_value = parent_value.clone();
					}
				}
				(Some(parent_value), None) => {
					child_sprite.texture_atlas = Some(parent_value.clone());
				}
				_ => {}
			}
		}
	}
}
