using Celeste.Mod.LakeSideCode.FishDefs;
using Celeste.Mod.LakeSideCode.Triggers;
using Microsoft.Xna.Framework;
using Monocle;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static Celeste.TrackSpinner;

namespace Celeste.Mod.LakeSideCode.Entities {
	public class Fish : Actor {

		// private const float HitboxWidth = 14f;
		// private const float HitboxHeight = 10f;
		private const float BombTime = 1f;

		public FishType FishType { get; private set; }
		public bool NewCatch { get; set; } = true;


		private readonly Image image;
		private Entity following = null;
		private Vector2 followOffset;
		private Vector2 speed = Vector2.Zero;
		private float noGravityTimer;
		private Vector2 prevLiftSpeed;
		private bool dead = false;
		private bool inventoryOnPickup = false;
		private bool inventoryOnNormalState = false;
		private bool triedInFire = false;
		private float bombTimer = -1f;


		public Holdable HoldableComponent { get; }

		public bool CanPickUp {
			get => HoldableComponent.Active;
			set { HoldableComponent.Active = value; }
		}


		// HERE (it was (14, 10) before)
		public static Dictionary<FishType, Vector2> fishHitboxSizes = new () {
			{ FishType.Coin,       new Vector2(8, 14)},
			{ FishType.Bass,       new Vector2(8, 10)},
			{ FishType.Trout,      new Vector2(8, 10)},
			{ FishType.Spring,     new Vector2(8, 10)},
			{ FishType.Stone,      new Vector2(8, 10)},
			{ FishType.StoneEater, new Vector2(8, 10)},
			{ FishType.Blahaj,     new Vector2(8, 10)},
			{ FishType.Bomb,       new Vector2(8, 10)},
			{ FishType.Leaf,       new Vector2(8, 10)},
			{ FishType.Angel,      new Vector2(8, 10)},
			{ FishType.Devil,      new Vector2(8, 10)},
			{ FishType.Cooked,     new Vector2(8, 10)},
			{ FishType.Mythic,     new Vector2(8, 20)},
		};

		public Fish(Vector2 position, FishType type) : base(position) {
			Depth = Depths.TheoCrystal;

			float hitboxWidth = fishHitboxSizes[type].X;
			float hitboxHeight = fishHitboxSizes[type].Y;

			Collider = new Hitbox(hitboxWidth, hitboxHeight, -hitboxWidth / 2f, -hitboxHeight / 2f);
			Add(image = new(GFX.Game[FishTypeUtil.TexPath(type, null, true)]));
			image.Position = Vector2.One * -8;
			this.FishType = type;
			HoldableComponent = new Holdable {
				Active = false,
				OnPickup = OnPickup,
				OnRelease = OnRelease,
				OnHitSpring = HitSpring,
				SpeedGetter = () => speed
			};
			Add(HoldableComponent);
		}

		public override void Update() {
			base.Update();
			Level level = SceneAs<Level>();
			if (level == null) return;
            if (following != null) {
				Position = following.Position + followOffset;
				prevLiftSpeed = Vector2.Zero;
				return;
			}
			if (bombTimer > 0f) {
				bombTimer -= Engine.DeltaTime;
				if (bombTimer <= 0f) {
					Explode();
					return;
				}
			}
			if (HoldableComponent.IsHeld) {
				prevLiftSpeed = Vector2.Zero;
				if (inventoryOnNormalState && LakeSideCodeModule.Session.Inventory == FishType.Nothing) {
					Player p = Scene.Tracker.GetEntity<Player>();
                    if (p?.StateMachine?.State == Player.StNormal) {
						if (NewCatch) LakeSideCodeModule.Session.FishWasCaught(FishType, level);
						else LakeSideCodeModule.Session.PickUpFish(FishType);
						RemoveSelf();
                    }
                }
				return;
			}
			else if (OnGround()) {
				inventoryOnPickup = true;
				float target = (!OnGround(Position + Vector2.UnitX * 3f)) ? 20f : (OnGround(Position - Vector2.UnitX * 3f) ? 0f : (-20f));
				speed.X = Calc.Approach(speed.X, target, 800f * Engine.DeltaTime);
				Vector2 liftSpeed = LiftSpeed;
				if (liftSpeed == Vector2.Zero && prevLiftSpeed != Vector2.Zero) {
					speed = prevLiftSpeed;
					prevLiftSpeed = Vector2.Zero;
					speed.Y = Math.Min(speed.Y * 0.6f, 0f);
					if (speed.X != 0f && speed.Y == 0f) {
						speed.Y = -60f;
					}
					if (speed.Y < 0f) {
						noGravityTimer = 0.15f;
					}
				}
				else {
					prevLiftSpeed = liftSpeed;
					if (liftSpeed.Y < 0f && speed.Y < 0f) {
						speed.Y = 0f;
					}
				}
			}
			else if (HoldableComponent.ShouldHaveGravity) {
				float num = 800f;
				if (Math.Abs(speed.Y) <= 30f) {
					num *= 0.5f;
				}
				float num2 = 350f;
				if (speed.Y < 0f) {
					num2 *= 0.5f;
				}
				speed.X = Calc.Approach(speed.X, 0f, num2 * Engine.DeltaTime);
				if (noGravityTimer > 0f) {
					noGravityTimer -= Engine.DeltaTime;
				}
				else {
					speed.Y = Calc.Approach(speed.Y, 200f, num * Engine.DeltaTime);
				}
			}
			MoveH(speed.X * Engine.DeltaTime, OnCollideH);
			MoveV(speed.Y * Engine.DeltaTime, OnCollideV);
			if (Center.X > level.Bounds.Right) {
				MoveH(32f * Engine.DeltaTime);
				if (Left - 8f > level.Bounds.Right) {
					Die();
				}
			}
			else if (Center.X < level.Bounds.Left) {
				MoveH(-32f * Engine.DeltaTime);
				if (Left - 8f > level.Bounds.Left) {
					Die();
				}
			}
			else if (Top < (level.Bounds.Top - 4)) {
				Top = level.Bounds.Top + 4;
				speed.Y = 0f;
			}
			else if (Top > level.Bounds.Bottom) {
				Die();
			}
			if (!dead) {
				HoldableComponent.CheckAgainstColliders();
				Collider coll = Collider;
				// Check if we're in water
				Collider = new Hitbox(2, 2, -1, -coll.Height / 2f);
				if (CollideCheck<Water>()) {
					foreach (Trigger tr in Scene.Tracker.GetEntities<Trigger>().Cast<Trigger>()) {
						if (tr is FishingTrigger ft && CollideCheck(tr)) {
							Audio.Play("event:/char/madeline/water_in", Position, "deep", 1f);
							ft.CollideFirst<Water>()?.TopSurface.DoRipple(Position, 1.5f);
							ft.Baited(FishType);
							Die();
							break;
						}
					}
				}
				// Check if we're in a cook trigger
				Collider = new Hitbox(2, 2, -1, -1);
				HandleCookTrigger();
				// Restore original hitbox
				Collider = coll;
			}
		}

		public override void Render() {
			base.Render();
			if (bombTimer > 0) {
				float pct = bombTimer / BombTime;
				float maxAngle = (float)Math.PI * pct;
				for (int i = -5; i < 5; i++) {
					float startAngle = i / 5f * maxAngle;
					float endAngle = (i + 1) / 5f * maxAngle;
					Vector2 start = Position + new Vector2((float)Math.Sin(startAngle), -(float)Math.Cos(startAngle)) * 16;
					Vector2 end = Position + new Vector2((float)Math.Sin(endAngle), -(float)Math.Cos(endAngle)) * 16;

					float phase = (float)Math.Floor(10f/Math.Pow(bombTimer + 0.75f, 2f) % 2f);

					Draw.Line(start, end, Color.Lerp(Color.Red, Color.White, phase), 2f);
				}
			}
		}

		public void Collect(Level level) {
			LakeSideCodeModule.Session?.FishWasCaught(FishType, level);
			RemoveSelf();
		}

		public void FollowLure(Entity entity, Vector2 offset) {
			following = entity;
			followOffset = offset;
		}

		public void Die() {
			dead = true;
			RemoveSelf();
		}

		private void OnPickup() {
			speed = Vector2.Zero;
			AddTag(Tags.Persistent);
			if (inventoryOnPickup) inventoryOnNormalState = true;
		}

		private void OnRelease(Vector2 force) {
			RemoveTag(Tags.Persistent);
			if (force.X != 0f && force.Y == 0f) {
				force.Y = -0.4f;
			}
			speed = force * 200f;
			if (speed != Vector2.Zero) {
				noGravityTimer = 0.1f;
			}
			inventoryOnPickup = true;
		}

		private bool HitSpring(Spring spring) {
			if (!HoldableComponent.IsHeld) {
				if (spring.Orientation == Spring.Orientations.Floor && speed.Y >= 0f) {
					speed.X *= 0.5f;
					speed.Y = -160f;
					noGravityTimer = 0.15f;
					return true;
				}
				if (spring.Orientation == Spring.Orientations.WallLeft && speed.X <= 0f) {
					MoveTowardsY(spring.CenterY + 5f, 4f);
					speed.X = 220f;
					speed.Y = -80f;
					noGravityTimer = 0.1f;
					return true;
				}
				if (spring.Orientation == Spring.Orientations.WallRight && speed.X >= 0f) {
					MoveTowardsY(spring.CenterY + 5f, 4f);
					speed.X = -220f;
					speed.Y = -80f;
					noGravityTimer = 0.1f;
					return true;
				}
			}
			return false;
		}

		private void OnCollideH(CollisionData data) {
			if (data.Hit is DashSwitch) {
				(data.Hit as DashSwitch).OnDashCollide(null, Vector2.UnitX * Math.Sign(speed.X));
			}
			//Audio.Play("event:/game/05_mirror_temple/crystaltheo_hit_side", Position);
			if (Math.Abs(speed.X) > 100f) {
				ImpactParticles(data.Direction);
			}
			speed.X *= -0.4f;
		}

		private void OnCollideV(CollisionData data) {
			if (data.Hit is DashSwitch) {
				(data.Hit as DashSwitch).OnDashCollide(null, Vector2.UnitY * Math.Sign(speed.Y));
			}
			Audio.Play("event:/char/madeline/water_in", Position);
			if (speed.Y > 160f) {
				ImpactParticles(data.Direction);
			}
			if (speed.Y > 140f && data.Hit is not SwapBlock && data.Hit is not DashSwitch) {
				speed.Y *= -0.6f;
			}
			else if (FishType == FishType.Spring) {
				if(data.Direction.Y < 0) {
					speed.Y = 0;

					return;
				}

				Spring spring = new(Position + Vector2.UnitY * 5, Spring.Orientations.Floor, true);
				Scene.Add(spring);
				Audio.Play("event:/game/03_resort/forcefield_vanish", Position);
				RemoveSelf();
			}
			else {
				speed.Y = 0f;
				if (FishType == FishType.Bomb && bombTimer < 0) {
					bombTimer = BombTime;

					Audio.Play("event:/Lakeside_Fuze", Position);
				}
			}
		}

		private void ImpactParticles(Vector2 dir) {
			float direction;
			Vector2 position;
			Vector2 positionRange;
			if (dir.X > 0f) {
				direction = (float)Math.PI;
				position = new Vector2(Right, Y - 4f);
				positionRange = Vector2.UnitY * 6f;
			}
			else if (dir.X < 0f) {
				direction = 0f;
				position = new Vector2(Left, Y - 4f);
				positionRange = Vector2.UnitY * 6f;
			}
			else if (dir.Y > 0f) {
				direction = -(float)Math.PI / 2f;
				position = new Vector2(X, Bottom);
				positionRange = Vector2.UnitX * 6f;
			}
			else {
				direction = (float)Math.PI / 2f;
				position = new Vector2(X, Top);
				positionRange = Vector2.UnitX * 6f;
			}
			SceneAs<Level>().Particles.Emit(TheoCrystal.P_Impact, 12, position, positionRange, direction);
		}

		private void Explode() {
			(Scene as Level).Displacement.AddBurst(Center, 0.4f, 8f, 64f, 0.5f, Ease.QuadOut, Ease.QuadOut);
			Seeker.RecoverBlast.Spawn(Position);
			Collider = new Circle(40f);
			Player player = CollideFirst<Player>();
			if (player != null && !Scene.CollideCheck<Solid>(Position, player.Center)) {
				player.ExplodeLaunch(Position, false, false);
			}
			Audio.Play("event:/new_content/game/10_farewell/puffer_splode", Position);
			//foreach (TempleCrackedBlock entity in Scene.Tracker.GetEntities<TempleCrackedBlock>()) {
			//	if (CollideCheck(entity)) {
			//		entity.Break(Position);
			//	}
			//}
			//foreach (TouchSwitch entity2 in Scene.Tracker.GetEntities<TouchSwitch>()) {
			//	if (CollideCheck(entity2)) {
			//		entity2.TurnOn();
			//	}
			//}
			foreach (DashBlock entity3 in Scene.Tracker.GetEntities<DashBlock>().Cast<DashBlock>()) {
				if (CollideCheck(entity3)) {
					entity3.Break(Position, entity3.Center - Position, true, true);
				}
			}
			RemoveSelf();
		}

		private static IEnumerator CookedFishSequence(CookTrigger ct, Entity e, FishType origType) {
			bool failed = origType == FishType.Cooked || !(origType == FishType.Trout || origType == FishType.Bass) || !ct.FlagCheck;

			
			if(failed) {
				Audio.Play("event:/Lakeside_CookedFishSequenceFail");
			} else {
				Audio.Play("event:/Lakeside_CookedFishSequence");
			}

			yield return 2;

			Player p = e.Scene.Tracker.GetEntity<Player>();

			Fish fish = new(ct.Center + new Vector2(0, -5), failed ? origType : FishType.Cooked);

			fish.CanPickUp = true;
			fish.speed = new(-135, -200);
			if(fish.FishType != FishType.Cooked) fish.NewCatch = false;
			e.Scene.Add(fish);
			e.RemoveSelf();
		}


		private void HandleCookTrigger() {
			if (speed.Length() != 0 || TagCheck(Tags.Persistent)) return;
			
			foreach (Trigger entity in Scene.Tracker.GetEntities<Trigger>().Cast<Trigger>()) {
				if (entity is CookTrigger ct && CollideCheck(ct)) {
					if (!triedInFire) {
						triedInFire = true;
					} else {
						return;
					}

					// dummy entity to add the coroutine to because we need to delete the fish
					Entity e = new();
					Scene.Add(e);
					e.Add(new Coroutine(CookedFishSequence(ct, e, FishType)));

					RemoveSelf();
				}
			}
		}

	}
}
