using System;
using System.Collections.Generic;
using System.Linq;
using Godot;
using static ld48.Extensions;

public class RopeController : Node2D {
    
    private static readonly float MinRopeFriction = 0.025f;
    private static readonly float MaxRopeFriction = 0.9f;

    private static readonly float MaxThrowSpeed = 20f;
    
    public float climbVelocity;
    private float allowedLength;
    private float ropeFriction;
    
    private List<RopeSegment> ropeSegments = new List<RopeSegment>();

    public Hook hook;

    private PackedScene ropeScene;
    private PackedScene hookScene;

    private RayCast2D ropeRay;

    public Hero hero;

    private Node world;

    private float ropeCharge;
    
    private AudioStreamPlayer2D firePlayer;
    private AudioStreamPlayer2D hitPlayer;

    public override void _Ready() {
        base._Ready();
        
        firePlayer = GetNode<AudioStreamPlayer2D>("ThrowPlayer");
        hitPlayer = GetNode<AudioStreamPlayer2D>("HitPlayer");
        
        ropeRay = GetNode<RayCast2D>("RopeRay");
        
        ropeScene = ResourceLoader.Load<PackedScene>($"res://scene/{nameof(RopeSegment)}.tscn");
        hookScene = ResourceLoader.Load<PackedScene>($"res://scene/{nameof(Hook)}.tscn");

        world = GetParent().GetParent();
    }

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

        if (hero.currentRope == this) {
            if (!RopeExists()) {
                if (Input.IsActionPressed("rope")) {
                    ropeCharge = Mathf.Min(ropeCharge + delta, 1f);
                } else if (ropeCharge > 0f) {
                    hook = hookScene.Instance<Hook>();
                    firePlayer.Play();
                    hook.Position = hero.Position;
                    hook.velocity = GetLocalMousePosition().Normalized() * MaxThrowSpeed * ropeCharge;
                    hook.rope = this;
                    BackAffixRope(hero.Position, Vector2.Up);
                    world.AddChild(hook);
                    ropeCharge = 0f;
                }
            } else {
            }
        }
        
        if (!Input.IsActionPressed("slide")) {
            ropeFriction += Mathf.Min(0.01f, (MaxRopeFriction - ropeFriction) / 10f);
        } else {
            var minimum = hero.IsOnFloor() ? 0f : MinRopeFriction;
            ropeFriction += (minimum - ropeFriction) / 5f;
        }
        
        Update();
    }

    public override void _PhysicsProcess(float delta) {
        base._PhysicsProcess(delta);
        
        if (RopeExists()) {
            climbVelocity *= 0.9f;
        
            var ropeNormal = -EndSegment().Direction();
            var targetFriction = ropeFriction;
            
            var totalRope = RopeLength();
                
            if (climbVelocity > 0.1f && hook != null && !hook.retracting) {
                targetFriction = 1f;
                var climbTarget = (ropeNormal * climbVelocity).Project(Vector2.Up).Length() * ropeNormal;
                var result = hero.MoveAndSlide(climbTarget, Vector2.Up);
                hero.MoveAndSlide(Vector2.Up * (climbTarget.Length() - result.Length()) * 3f, Vector2.Up);
                allowedLength -= climbTarget.Length();
                EndSegment().b = hero.Position;
            }
            
            if (allowedLength > totalRope) {
                allowedLength = Mathf.Max(totalRope, 100f);
            }
            
            if (allowedLength < totalRope) {
                var offset = totalRope - allowedLength;
                if (hook.affixed) {
                    if (Hero.maxRopeLength < totalRope) targetFriction = 1f;
                    hero.velocity -= hero.velocity.Project(ropeNormal) * targetFriction;
                    hero.MoveAndSlide(ropeNormal * offset * ropeFriction, Vector2.Up);

                    var slideOut = offset * (1f - ropeFriction);
                    allowedLength += slideOut;
                } else {
                    if (Hero.maxRopeLength < totalRope) {
                        RetractStart(totalRope - Hero.maxRopeLength);
                    }
                }
            }
        }
    }

    public void UpdateRope() {
        if (RopeExists()) {
            EndSegment().b = hero.Position;
            
            if (ropeSegments.Count > 1) {
                var cross = RopeEndAnchor().Direction().Cross(EndSegment().Direction());
                if (Mathf.Abs(Mathf.Sign(cross) - RopeEndAnchor().slipSign) < float.Epsilon) {
                    SlipRopeEnd();
                }
            }
            
            if (ropeSegments.Count > 1) {
                var cross = -RopeStartAnchor().Direction().Cross(StartSegment().Direction());
                if (Mathf.Abs(Mathf.Sign(cross) - RopeStartAnchor().slipSign) < float.Epsilon) {
                    SlipRopeStart();
                }
            }
            
            ropeRay.CastTo = EndSegment().a - hero.Position;
            ropeRay.ForceRaycastUpdate();

            if (ropeRay.IsColliding()) {
                AffixRope(ropeRay.GetCollisionPoint(), ropeRay.GetCollisionNormal());
            }
        }
    }

    public void RopeControls() {
        if (!RopeExists() || (!hook.affixed && !hook.retracting)) {
            if (Input.IsActionPressed("moveRight")) {
                hero.velocity += V2(Hero.swingSpeed, 0f); // TODO: cap contribution if at max run speed
            }
            if (Input.IsActionPressed("moveLeft")) {
                hero.velocity -= V2(Hero.swingSpeed, 0f);
            }
        } else {
            if (Input.IsActionPressed("moveRight")) {
                hero.velocity += EndSegment().Direction().Rotated(-90f) * Hero.swingSpeed; // TODO: cap contribution if at max run speed
                hero.velocity += V2(Hero.swingSpeed/2f, 0f); // TODO: cap contribution if at max run speed
            }
            if (Input.IsActionPressed("moveLeft")) {
                hero.velocity += EndSegment().Direction().Rotated(90f) * Hero.swingSpeed; // TODO: cap contribution if at max run speed
                hero.velocity -= V2(Hero.swingSpeed/2f, 0f);
            }
            if (Input.IsActionPressed("climb")) {
                if (hero.handAnim.CurrentAnimation != "Climb") {
                    hero.handAnim.CurrentAnimation = "Climb";
                    hero.handAnim.PlaybackSpeed = 1.5f;
                    hero.handAnim.Play();
                }
                climbVelocity += Hero.climbSpeed;
                if (climbVelocity >= Hero.maxClimbVelocity) {
                    climbVelocity = Hero.maxClimbVelocity;
                }
            } else {
                if (hero.handAnim.CurrentAnimation == "Climb") {
                    hero.handAnim.CurrentAnimation = "Grab";
                    hero.handAnim.Seek(0f);
                }
            }
        }
    }
    
    public bool RopeExists() {
        return ropeSegments.Any();
    }

    public Vector2 RopeStart() {
        if (RopeExists()) {
            return ropeSegments[0].a;
        }

        return hero.Position;
    }

    public RopeSegment StartSegment() {
        if (RopeExists()) {
            return ropeSegments.First();
        }

        return null;
    }

    public RopeSegment RopeStartAnchor() {
        if (ropeSegments.Count > 1) {
            return ropeSegments[1];
        }

        return null;
    }

    public RopeSegment EndSegment() {
        if (RopeExists()) {
            return ropeSegments.Last();
        }

        return null;
    }

    public RopeSegment RopeEndAnchor() {
        if (ropeSegments.Count > 1) {
            return ropeSegments[ropeSegments.Count-2];
        }

        return null;
    }

    public float RopeLength() {
        return ropeSegments.Sum(s => s.Length());
    }

    public void RetractStart(float amount) {
        if (RopeExists()) {
            if (amount < StartSegment().Length()) {
                StartSegment().a += StartSegment().Direction() * amount;
            } else {
                var reduced = StartSegment().Length();
                SlipRopeStart();
                RetractStart(amount - reduced);
            }
        }
    }

    public RopeSegment BackAffixRope(Vector2 anchor, Vector2 normal) {
        if (hook == null) return null;
        
        var point = anchor;
        point += normal * 2.5f;
        if (RopeExists()) {
            var active = StartSegment();
            // if (active.a.DistanceTo(anchor) < 1f) return null;
            active.freeze = true;
            active.slippable = true;
            active.a = point;
            active.slipSign = Mathf.Sign(active.Direction().Cross(active.b + normal*0.2f - point));
            if (active.slipSign == 0f) active.slipSign = 1f;
        } else {
            allowedLength = Mathf.Max(RopeLength(), 100f);
        }
        var segment = ropeScene.Instance<RopeSegment>();
        segment.a = hook.Position;
        segment.b = point;
        ropeSegments.Insert(0, segment);
        world.CallDeferred("add_child", segment);
        return segment;
    }
    
    private RopeSegment AffixRope(Vector2 anchor, Vector2 normal) {
        var point = anchor;
        point += normal * 2.5f;
        if (RopeExists()) {
            var active = EndSegment();
            // if (active.b.DistanceTo(anchor) < 1f) return null;
            active.freeze = true;
            active.slippable = true;
            active.b = point;
            active.slipSign = Mathf.Sign(active.Direction().Cross(point + normal*0.2f - active.a));
            if (active.slipSign == 0f) active.slipSign = 1f;
        }
        var segment = ropeScene.Instance<RopeSegment>();
        segment.a = point;
        segment.b = hero.Position;
        ropeSegments.Add(segment);
        world.CallDeferred("add_child", segment);
        return segment;
    }

    private void SlipRopeStart() {
        StartSegment().QueueFree();
        ropeSegments.RemoveAt(0);
        if (RopeExists()) {
            var active = StartSegment();
            active.freeze = false;
            // active.slippable = false;
        } else {
            DropRope();
        }
    }

    private void SlipRopeEnd() {
        EndSegment().QueueFree();
        ropeSegments.RemoveAt(ropeSegments.Count-1);
        var active = EndSegment();
        active.freeze = false;
        // active.slippable = false;
    }

    public void DropRope() {
        hook?.QueueFree();
        hook = null;
        ropeSegments.ForEach(s => s.QueueFree());
        ropeSegments.Clear();
        allowedLength = 0f;
    }

    public void HookHit(Hook hook, Vector2 collisionPosition, Vector2 collisionNormal) {
        BackAffixRope(collisionPosition, collisionNormal);
        allowedLength = Hero.maxRopeLength;
        this.hook.affixed = true;
        hitPlayer.Play();
    }

    public void Retract() {
        if (hook != null) {
            hook.retracting = true;
        }
    }
    
    public override void _Draw() {
        base._Draw();

        var mousePos = GetLocalMousePosition();
        var mouseDir = mousePos.Normalized();
        if (ropeCharge > 0f) {
            var start = mouseDir * 20f;
            var pointAt = start + mouseDir * (ropeCharge + 0.5f) * 40f;
            DrawLine(start, pointAt, Colors.Black, 4f);
            DrawLine(pointAt + mouseDir * 4f, pointAt + mouseDir.Rotated(35f) * (15f + 5f*ropeCharge), Colors.Black, 5f);
            DrawLine(pointAt + mouseDir * 4f, pointAt + mouseDir.Rotated(-35f) * (15f + 5f*ropeCharge), Colors.Black, 5f);
            DrawLine(start, pointAt, C(0.8f, 0.8f, 1f), 2f);
            DrawLine(pointAt + mouseDir * 4f, pointAt + mouseDir.Rotated(35f) * (15f + 5f*ropeCharge), C(0.8f, 0.8f, 1f), 3f);
            DrawLine(pointAt + mouseDir * 4f, pointAt + mouseDir.Rotated(-35f) * (15f + 5f*ropeCharge), C(0.8f, 0.8f, 1f), 3f);
        }
    }
}

