using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Threading.Tasks;
using Godot;
using static ld48.Extensions;

public class Hero : KinematicBody2D {
    private static readonly float runSpeed = 10f;
    private static readonly float jumpSpeed = 150f;
    public static readonly float swingSpeed = 2.5f;
    private static readonly Vector2 gravity = new Vector2(0f, 12f);
    private static readonly Vector2 minSpeed = new Vector2(-1600f, -1600f);
    private static readonly Vector2 maxSpeed = new Vector2(1600f, 1600f);
    private static readonly Vector2 groundFriction = new Vector2(0.1f, 0.02f);
    private static readonly Vector2 airFriction = new Vector2(0.0001f, 0.0001f);

    private static readonly float healSpeed = 0.2f;
    private static readonly float healDelay = 4f;

    public static Hero instance;

    private float healPause;

    public static readonly float climbSpeed = 40f;
    public static readonly float maxClimbVelocity = 20f;

    public static readonly float maxRopeLength = 1500f;

    private static readonly Vector2 snap = new Vector2(0f, 16f);

    public static float hp = 3f;
    public bool inSave;
    public Mushrooms lastSave;

    public Vector2 velocity = Vector2.Zero;
    private bool facingLeft;

    private Color hitColor;
    private float hitIntensity;

    private Camera2D camera;
    private Sprite sprite;
    private ColorRect post;

    private RopeController leftRope;
    private RopeController rightRope;
    public RopeController currentRope;

    private Node2D draw;
    private AnimationPlayer feetAnim;
    public AnimationPlayer handAnim;
    private Sprite rightHand;

    private bool grabbing;

    private float screenFade = 1f;

    public bool falling;
    private float fallingRot;
    public bool done;

    private PackedScene messageScene;
    public AudioStreamPlayer2D player;
    public AudioStreamPlayer2D hurtPlayer;

    public override void _Ready() {
        base._Ready();

        instance = this;

        player = GetNode<AudioStreamPlayer2D>("Player");
        hurtPlayer = GetNode<AudioStreamPlayer2D>("HurtPlayer");
        
        messageScene = ResourceLoader.Load<PackedScene>($"res://scene/{nameof(Message)}.tscn");

        camera = GetTree().Root.GetNode("Game").GetNode<PixelCamera>("PixelCamera");
        post = GetTree().Root.GetNode("Game").GetNode("Over").GetNode<ColorRect>("Post");

        leftRope = GetNode<RopeController>("LeftRope");
        rightRope = GetNode<RopeController>("RightRope");
        currentRope = leftRope;

        leftRope.hero = this;
        rightRope.hero = this;

        draw = GetNode<Node2D>("Draw");
        feetAnim = GetNode<AnimationPlayer>("FeetAnim");
        handAnim = GetNode<AnimationPlayer>("HandAnim");
        sprite = draw.GetNode<Sprite>("Sprite");
        rightHand = draw.GetNode<Sprite>("RightHand");
    }

    public override void _Process(float delta) {
        base._Process(delta);

        hitIntensity *= 0.9f;
        sprite.Modulate = Colors.White.Mix(hitColor, hitIntensity);

        // if (Input.IsActionJustPressed("leftHand")) {
            // currentRope = leftRope;
        // }
        // if (Input.IsActionJustPressed("rightHand")) {
            // currentRope = rightRope;
        // }
        
        if (Input.IsActionJustPressed("drop")) {
            currentRope.Retract();
        }

        var maxHp = Mathf.Ceil(hp);

        if (inSave) {
            hp += delta * healSpeed * 3f;
            if (hp > 3f) {
                hp = 3f;
            }
            healPause = 0f;
        } else {
            if (healPause > 0f) {
                healPause -= delta;
                if (healPause <= 0f) healPause = 0f;
            } else {
                hp += delta * healSpeed;
                if (hp > maxHp) hp = maxHp;
            }
        }

        if (done) {
            screenFade *= 0.95f;
        } else {
            if (hp <= 0f) {
                screenFade *= 0.95f;
                if (screenFade <= 0.05f) {
                    if (lastSave != null) {
                        Position = lastSave.Position;
                    } else {
                        Position = Vector2.Zero;
                    }

                    leftRope.DropRope();
                    rightRope.DropRope();
                    hp = 3f;
                    screenFade = 1f;
                    GD.Print("Resurrect");

                    Toast("WAKE UP", true);
                    // player.Play();
                }
            }
        }

        PixelCamera.fadeColor = (hitColor * hitIntensity * screenFade).Alpha(Mathf.Max(1f-screenFade, hitIntensity*0.25f).Clamp(0f, 1f));
    }

    public void Toast(String text, bool flash, MessageArea receiver = null) {
        var message = messageScene.Instance<Message>();
        message.message = text;
        message.simple = !flash;
        message.receiver = receiver;
        GetTree().Root.GetNode("Game").GetNode("UI").AddChild(message);
        player.Play();
    }

    public override void _PhysicsProcess(float delta) {
        base._PhysicsProcess(delta);

        var jumping = false;
        if (IsOnFloor()) {
            draw.Rotation += ((Mathf.Pi/2f + GetFloorNormal().Angle().Clamp(-Mathf.Pi/2f-0.3f, -Mathf.Pi/2f+0.3f) - draw.Rotation) / 30f).Clamp(-10f, 10f);

            handAnim.CurrentAnimation = "Walk";
            feetAnim.CurrentAnimation = "Walk";
            if (Input.IsActionPressed("moveRight")) {
                velocity += V2(runSpeed, 0f); // TODO: cap contribution if at max run speed
                facingLeft = false;
                feetAnim.Play();
                handAnim.Play();
            }
            if (Input.IsActionPressed("moveLeft")) {
                velocity -= V2(runSpeed, 0f);
                facingLeft = true;
                feetAnim.Play();
                handAnim.Play();
            }
            feetAnim.PlaybackSpeed = (Mathf.Abs(velocity.x) / maxSpeed.x) * 20f;
            handAnim.PlaybackSpeed = (Mathf.Abs(velocity.x) / maxSpeed.x) * 20f;

            if (feetAnim.PlaybackSpeed < 1f) {
                if (feetAnim.CurrentAnimationPosition < 1f) {
                    feetAnim.Seek(feetAnim.CurrentAnimationPosition*0.9f);
                } else {
                    feetAnim.Seek(feetAnim.CurrentAnimationPosition + (1f - feetAnim.CurrentAnimationPosition) / 10f);
                }
                if (handAnim.CurrentAnimationPosition < 1f) {
                    handAnim.Seek(handAnim.CurrentAnimationPosition*0.9f);
                } else {
                    handAnim.Seek(handAnim.CurrentAnimationPosition + (1f - handAnim.CurrentAnimationPosition) / 10f);
                }
            }
            
            if (currentRope.RopeExists()) {
                if (Input.IsActionPressed("climb")) {
                    currentRope.climbVelocity += climbSpeed;
                    if (currentRope.climbVelocity >= maxClimbVelocity) {
                        currentRope.climbVelocity = maxClimbVelocity;
                    }
                }
            }
            
            if (Input.IsActionJustPressed("climb")) {
                velocity -= V2(0f, jumpSpeed).Rotated(Rotation);
                jumping = true;
            }
            grabbing = false;
            
            rightHand.ZIndex = 1;
        } else {
            feetAnim.CurrentAnimation = "Walk";
            feetAnim.Seek(feetAnim.CurrentAnimationPosition*0.9f);
            
            if (currentRope.RopeExists()) {
                rightHand.ZIndex = 0;
                
                if (handAnim.CurrentAnimation != "Grab" && !grabbing) {
                    grabbing = true;
                    handAnim.CurrentAnimation = "Grab";
                    handAnim.PlaybackSpeed = 5f;
                    handAnim.Play();
                }
                
                draw.Rotation += ((currentRope.EndSegment().Direction().Angle().Clamp(Mathf.Pi/2f-0.3f, Mathf.Pi/2f+0.3f) - Mathf.Pi/2f - draw.Rotation) / 45f).Clamp(-5f, 5f);
            } else {
                grabbing = false;
                handAnim.CurrentAnimation = "Walk";
                handAnim.Seek(handAnim.CurrentAnimationPosition*0.9f);
                
                draw.Rotation += ((0f - draw.Rotation) / 45f).Clamp(-5f, 5f);
            }
            velocity += gravity;

            currentRope.RopeControls();
        }

        draw.Scale = facingLeft ? V2(-1f, 1f) : Vector2.One;

        if (IsOnFloor()) {
            velocity *= V2(1f) - groundFriction;
        } else {
            velocity *= V2(1f) - airFriction;
        }
        
        velocity = velocity.Clamp(minSpeed, maxSpeed);
        
        if (!jumping) {
            var div = 60f;
            var travelVelocity = velocity.Copy();
            var tV = velocity / div;
            for (var d = 0; d < (int) div; d++) {
                tV = MoveAndSlideWithSnap(tV, snap, Vector2.Up, true, 4, 0.9f);
                
                var slides = GetSlideCount();
                for (var i=0; i<slides; i++) {
                    var collision = GetSlideCollision(i);
                    if (travelVelocity.Length() > 10f) {
                        var collisionPower = (travelVelocity.Normalized().Dot(-collision.Normal) + 0.5f) * travelVelocity.Length();
                        if (collisionPower > 500f) {
                            Hit((collisionPower - 500f) / 100f);
                        }
                    }
                }
                
                leftRope.UpdateRope();
                rightRope.UpdateRope();
            }
            velocity = tV * div;
        } else {
            velocity = MoveAndSlideWithSnap(velocity, Vector2.Zero, Vector2.Up, true);
        }

        var depth = Position.y.InvLerp(0f, 20000);

        camera.Position = Position;
        (post.Material as ShaderMaterial)?.SetShaderParam("lightPosition", Position);
        (post.Material as ShaderMaterial)?.SetShaderParam("depth", depth);
        (post.Material as ShaderMaterial)?.SetShaderParam("tintColor", Color.FromHsv(depth, 0.2f*depth, 1f));

        if (falling) {
            fallingRot += delta*0.2f;
            draw.Rotation = fallingRot;
        }
    }

    public void Hit(float power) {
        if (power < 0.3f) return;
        
        hurtPlayer.Play();
        
        if (hp > 0f) {
            hp -= power*0.5f;
            if (hp <= 0f) hp = -1f;

            healPause = power * 0.5f * healDelay;
            
            hitIntensity = 1f;
            hitColor = Colors.Red;
        }
    }

    public void Lava(Node body) {
        if (body == this) {
            hurtPlayer.Play();
            hp = 0f;
            velocity = V2(velocity.x, -200f);
        }
    }
}

