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

namespace Celeste.Mod.LakeSideCode.Entities {
	internal class Lure : Platform {

		private static readonly float MaxFallSpeed = 400f;
		private static readonly float MaxFloatSpeed = 15f;
		private static readonly float AirDrag = 10f;
		private static readonly float WaterDrag = 40f;
		private static readonly float Gravity = 400f;
		private static readonly float Floatiness = 50f;
		private static readonly float WallBounceAmount = 0.6f;
		private static readonly float WallBounceDampening = 5f;
		private static readonly float FloorBounceAmount = 0.4f;
		private static readonly float FloorBounceDampening = 50f;
		private static readonly float ReelTime = 1f;
		private static readonly float ReelCurveAmount = 45f;

		public Vector2 LineBeginPoint;

		private readonly Collider solidsCollider;
		private readonly Collider waterCollider;
		private readonly float sineX;
		private readonly float sineY;
		private Vector2 velocity;
		private float timeWithoutMoving = 0;
		private bool reelingIn;
		private float reelTimeLeft;
		private SimpleCurve reelCurve;

		public bool InWater { get; private set; } = false;
		public bool FinishedReeling { get; private set; } = false;
		public bool ReadyForCatchSequence => InWater || timeWithoutMoving > 0.2f;
		public float WaterHitX = 0;

		public Vector2 Velocity {
			get => velocity;
			internal set => velocity = value;
		}

		public Lure(Vector2 position, Vector2 initVelocity) : base(position, false) {
			Depth = Depths.Dust;
			Collider = solidsCollider = new Hitbox(6, 6, -3, -3);
			waterCollider = new Hitbox(2, 2, -1, -1);
			Add(new Image(GFX.Game["objects/Phobscodestuff/Lakesidecodestuff/bobber"]) {
				X = -2, Y = -2,
			});
			velocity = initVelocity;
			LineBeginPoint = position;
			Random random = new((int)(position.X * position.Y));
			sineX = random.NextFloat(4f);
			sineY = random.NextFloat(4f);
		}

		public override void Update() {
			base.Update();
			if (!Visible) {
				if (reelingIn) FinishedReeling = true;
				return;  // Don't update when not visible; we're doing UI stuff for the cast.
			}

			if (reelingIn) {
				reelTimeLeft -= Engine.DeltaTime;
				if (reelTimeLeft <= 0) {
					FinishedReeling = true;
					RemoveSelf();
					return;
				}
				Position = reelCurve.GetPoint(reelTimeLeft / ReelTime);
			}
			else {
				// Check whether to use water or air physics and update velocity
				Collider = waterCollider;
				bool inWater = CollideCheck<Water>();
				if (!inWater) {
					velocity = Calc.Approach(velocity, Vector2.Zero, Engine.DeltaTime * AirDrag);
					velocity.Y += Engine.DeltaTime * Gravity;
					velocity.Y = Calc.Min(velocity.Y, MaxFallSpeed);
				}
				else if (!CollideCheck<Water>(Position - Vector2.UnitY)) {
					// prevent it from bobbing fully out of the water
					velocity = Calc.Approach(velocity, Vector2.Zero, Engine.DeltaTime * WaterDrag);
					velocity.Y = Calc.Max(0, velocity.Y);
				}
				else {
					velocity = Calc.Approach(velocity, Vector2.Zero, Engine.DeltaTime * WaterDrag);
					velocity.Y -= Engine.DeltaTime * Floatiness;
					velocity = velocity.Clamp(-MaxFloatSpeed, -MaxFloatSpeed, MaxFloatSpeed, MaxFloatSpeed);
				}
				Collider = solidsCollider;

				// update position
				Vector2 movement = velocity * Engine.DeltaTime;
				MoveHCollideSolidsAndBounds(SceneAs<Level>(), movement.X, false, OnCollideH);
				MoveVCollideSolidsAndBounds(SceneAs<Level>(), movement.Y, false, OnCollideV);
			}

			Water water = CollideFirst<Water>();
			if (water != null && !InWater) {
				Audio.Play("event:/char/madeline/water_in", Position, "deep", 1f);
				water.TopSurface.DoRipple(Position, 1f);
			}
			InWater = CollideCheck<Water>();
			if (velocity.LengthSquared() < 450) timeWithoutMoving += Engine.DeltaTime;
			else timeWithoutMoving = 0f;
		}

		/// <summary>
		/// Used for drawing the trajectory. Simplified version of physics update,
		/// may not be 100% accurate around corners or water and does not process bounces/collisions
		/// </summary>
		/// <param name="simPos">Simulated position. Will be updated to new position.</param>
		/// <param name="simVelocity">Simulated velocity. Will be updated to new velocity.</param>
		/// <returns>true if resulting position collides with either water or a solid</returns>
		public bool GetPositionAfterPhysicsUpdate(ref Vector2 simPos, ref Vector2 simVelocity) {
			simVelocity = Calc.Approach(simVelocity, Vector2.Zero, Engine.DeltaTime * AirDrag);
			simVelocity.Y += Engine.DeltaTime * Gravity;
			simVelocity.Y = Calc.Min(simVelocity.Y, MaxFallSpeed);
			Vector2 movement = simVelocity * Engine.DeltaTime;
			simPos = simPos + movement;
			bool collided;
			try {
				collided = CollideCheck<Water>(simPos) || CollideCheck<Solid>(simPos);
			}
			catch {
				collided = false;
			}
			return collided;
		}

		private void OnCollideH(Vector2 moveDir, Vector2 moveDist, Platform platform) {
			if (Math.Sign(moveDir.X) != Math.Sign(velocity.X)) return;
			velocity.X = Calc.Approach(velocity.X, 0, WallBounceDampening);
			velocity.X *= -WallBounceAmount;
			Audio.Play("event:/char/madeline/footstep", Position, "surface_index", 3);
		}

		private void OnCollideV(Vector2 moveDir, Vector2 moveDist, Platform platform) {
			if (moveDir.Y <= 0) {
				velocity.Y = 0;
				return;
			}
			if (velocity.Y < 0) return;
			if (velocity.Y > 30f) {
				Audio.Play("event:/char/madeline/footstep", Position, "surface_index", 3);
			}
			velocity.Y = Calc.Approach(-velocity.Y, 0, FloorBounceDampening);
			velocity *= FloorBounceAmount;
		}

		public override void MoveHExact(int move) {
			X += move;
		}

		public override void MoveVExact(int move) {
			Y += move;
		}

		public override void Render() {
			base.Render();
			SimpleCurve curve = new(LineBeginPoint, Position, Vector2.Zero);
			Level level = SceneAs<Level>();
			Vector2 vector = new Vector2((float)Math.Sin(sineX + level.WindSineTimer * 2f), (float)Math.Sin(sineY + level.WindSineTimer * 2.8f)) * 8f * level.VisualWind;
			curve.Control = (curve.Begin + curve.End) / 2f + new Vector2(0f, 24f) + vector;
			Vector2 start = curve.Begin;
			for (int i = 1; i <= 16; i++) {
				float percent = (float)i / 16f;
				Vector2 point = curve.GetPoint(percent);
				Draw.Line(start, point, Color.LightGray);
				start = point;
			}
		}

		internal void Nibble() {
			velocity.Y = MaxFloatSpeed;
		}

		internal void Hit() {
			// TODO actual hit animation
		}

		internal void ReelIn(Vector2 to) {
			reelingIn = true;
			reelTimeLeft = ReelTime;
			reelCurve = new(to, Position, (Position + to) / 2f + Vector2.UnitY * -ReelCurveAmount);
			Water water = CollideFirst<Water>();
			if (water != null) {
				Audio.Play("event:/char/madeline/water_out", Position, "deep", 0f);
				water.TopSurface.DoRipple(Position, 1f);
			}
		}

		internal FishingTrigger TriggerCheck() {
			try {
				foreach (Trigger entity in Scene.Tracker.GetEntities<Trigger>().Cast<Trigger>()) {
					if (entity is FishingTrigger ft && CollideCheck(entity)) {
						return ft;
					}
				}
			}
			catch { }
			return null;
		}
	}
}
