using System;
using System.Linq;
using Coroutine;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using MLEM.Animations;
using MLEM.Extended.Extensions;
using MLEM.Extended.Tiled;
using MLEM.Extensions;
using MLEM.Input;
using MLEM.Misc;
using MLEM.Startup;
using MLEM.Textures;
using MonoGame.Extended;
using MonoGame.Extended.Tiled;
using RectangleF = MLEM.Misc.RectangleF;

namespace GreatSpringGameJam {
    public class Player : Entity {

        private static readonly UniformTextureAtlas PlayerTexture = new(MlemGame.LoadContent<Texture2D>("Textures/Player"), 4, 3);
        private static readonly SoundEffectInstance[] HeldSounds = EnumHelper.GetValues<HeldItem>().Select(h => MlemGame.LoadContent<SoundEffect>("Sounds/" + h).CreateInstance(isLooped: true)).ToArray();

        public RectangleF Bounds => new(this.Position + new Vector2(4 / 16F, 0), new Vector2(9 / 16F, 1));

        private readonly SpriteAnimationGroup animations;
        private bool walking;
        private bool onGround;
        private Vector2 velocity;
        private TimeSpan jumpTime;
        private bool facingRight;
        private HeldItem heldItem;
        private float currJumpHeight;
        private bool climbingVine;
        private bool invisible;

        public Player(Map map, Vector2 position) : base(map, position) {
            this.animations = new SpriteAnimationGroup();
            // walking animations
            this.animations.Add(new SpriteAnimation(1, PlayerTexture[0]), () => !this.walking);
            this.animations.Add(new SpriteAnimation(0.15F, PlayerTexture[1], PlayerTexture[2], PlayerTexture[3], PlayerTexture[0]), () => this.walking);
            // jumping animation
            this.animations.Add(new SpriteAnimation(1, PlayerTexture[0, 1]), () => this.jumpTime > TimeSpan.Zero || !this.onGround, 1);
            // climbing animations
            this.animations.Add(new SpriteAnimation(1, PlayerTexture[0, 2]), () => this.climbingVine && !this.walking, 2);
            this.animations.Add(new SpriteAnimation(0.18F, PlayerTexture[1, 2], PlayerTexture[2, 2], PlayerTexture[3, 2], PlayerTexture[0, 2]), () => this.climbingVine && this.walking, 2);
            this.facingRight = true;
        }

        public override void Update(GameTime time) {
            base.Update(time);
            if (!GameImpl.Instance.IsInCutscene) {
                var jumpHeight = 0.15F;
                var (_, tileBelow) = this.GetTileBelow();
                if (tileBelow != null && tileBelow.Properties.GetBool("JumpPad"))
                    jumpHeight = 0.25F;

                // input
                var move = Vector2.Zero;
                if (MlemGame.Input.IsAnyDown(Keys.A, Keys.Left)) {
                    move.X -= 0.04F;
                    this.facingRight = false;
                }
                if (MlemGame.Input.IsAnyDown(Keys.D, Keys.Right)) {
                    move.X += 0.04F;
                    this.facingRight = true;
                }

                // climbing
                var (posInside, tileInside) = this.GetTileBelow(Vector2.Zero);
                if (tileInside != null && tileInside.Properties.GetBool("Vine")) {
                    if (MlemGame.Input.IsAnyDown(Keys.W, Keys.Up)) {
                        this.climbingVine = true;
                        move.Y -= 0.03F;
                    }
                    if (MlemGame.Input.IsAnyDown(Keys.S, Keys.Down)) {
                        this.climbingVine = true;
                        move.Y += 0.03F;
                    }
                } else {
                    this.climbingVine = false;
                }

                // entering doors
                if (MlemGame.Input.IsAnyPressed(Keys.W, Keys.Up) && this.onGround) {
                    if (tileInside != null && tileInside.Objects.Any(o => o.Properties.GetBool("LevelExit") && this.Map.GetArea(o, posInside.ToVector2()).Intersects(this.Bounds))) {
                        this.invisible = true;
                        GameImpl.Instance.Finish();
                    } else {
                        var door = this.Map.GetLevelEntrances().FirstOrDefault(e => this.Map.GetArea(e).Intersects(this.Bounds));
                        if (door != null) {
                            this.invisible = true;
                            GameImpl.Instance.StartLevel(door.Name, Color.White);
                        }
                    }
                }

                this.walking = move != Vector2.Zero;
                this.velocity += move;

                if (MlemGame.Input.IsDown(Keys.Space)) {
                    // only start jumping if we just started pressing the buttons
                    if ((this.onGround || this.climbingVine) && MlemGame.Input.IsPressed(Keys.Space)) {
                        this.jumpTime = TimeSpan.FromSeconds(0.3F);
                        this.currJumpHeight = jumpHeight;
                        this.climbingVine = false;
                    }
                    this.jumpTime -= time.ElapsedGameTime;
                    if (this.jumpTime > TimeSpan.Zero)
                        this.velocity.Y = -this.currJumpHeight;
                } else {
                    this.jumpTime = TimeSpan.Zero;
                }

                // restart if we fell
                if (this.Position.Y > this.Map.Size.Y)
                    GameImpl.Instance.StartLevel(this.Map.Name, Color.Black);
            }

            // movement and collisions
            this.Position += this.velocity;
            this.Collide(() => this.Bounds, ref this.velocity, ref this.onGround);
            this.velocity *= new Vector2(this.onGround ? 0.5F : 0.6F, this.climbingVine ? 0.5F : 0.95F);
            if (!this.climbingVine)
                this.velocity.Y += 0.015F;

            // item usage
            HeldItem? usingItem = null;
            var rot = this.GetHeldRotation();
            var rotVec = new Vector2(MathF.Cos(rot), MathF.Sin(rot));
            if (MlemGame.Input.IsDown(MouseButton.Left)) {
                usingItem = this.heldItem = HeldItem.SnowBlower;
                Random.NextUnitVector(out var vel);
                vel = vel * 0.03F + rotVec * 0.13F;
                this.Map.AddEntity(new SnowBlowerWind(this.Map, this.Position + Vector2.One / 2 + rotVec * 0.75F, vel));
            } else if (MlemGame.Input.IsDown(MouseButton.Right)) {
                usingItem = this.heldItem = HeldItem.WateringCan;
                if (Random.NextSingle() <= 0.45F) {
                    Random.NextUnitVector(out var vel);
                    vel = vel * 0.015F + rotVec * 0.03F;
                    this.Map.AddEntity(new WaterDrop(this.Map, this.Position + Vector2.One / 2 + rotVec * 0.65F, vel));
                }
            }
            for (var i = 0; i < HeldSounds.Length; i++) {
                if (usingItem == (HeldItem) i) {
                    HeldSounds[i].Play();
                } else {
                    HeldSounds[i].Pause();
                }
            }

            this.animations.Update(time);
        }

        public override void Draw(GameTime time, SpriteBatch batch) {
            if (this.invisible)
                return;
            var pos = this.Position * this.Map.TileSize;

            void DrawHeldItem() {
                var tex = this.heldItem switch {
                    HeldItem.SnowBlower => StuffTexture[0, 0],
                    HeldItem.WateringCan => StuffTexture[2, 0],
                    _ => null
                };
                var origin = new Vector2(tex.Width * 0.25F, tex.Height / 2);
                var rot = this.GetHeldRotation();
                var flip = rot >= MathHelper.PiOver2 && rot <= MathHelper.Pi * 1.5F ? SpriteEffects.FlipVertically : SpriteEffects.None;
                batch.Draw(tex, pos + this.Map.TileSize / 2, Color.White, rot, origin, 1, flip, 0);
            }

            if (this.climbingVine)
                DrawHeldItem();

            // draw self
            var effects = this.facingRight ? SpriteEffects.FlipHorizontally : SpriteEffects.None;
            batch.Draw(this.animations.CurrentRegion, pos, Color.White, 0, Vector2.Zero, 1, effects, 0);

            if (!this.climbingVine)
                DrawHeldItem();
        }

        private float GetHeldRotation() {
            var myPos = GameImpl.Instance.Camera.ToCameraPos((this.Position + Vector2.One / 2) * this.Map.TileSize);
            var mousePos = MlemGame.Input.MousePosition.ToVector2();
            var (x, y) = myPos - mousePos;
            return MathF.Atan2(y, x) + MathHelper.Pi;
        }

        private enum HeldItem {

            SnowBlower,
            WateringCan

        }

    }
}