using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.InputSystem;

public class GrappleHook : MonoBehaviour
{

    [Header("Grapple settings")]
    [SerializeField]
    private float maxGrappleDist = 200;
    [SerializeField]
    private float grappleExtensionSpeed = 10;
    [SerializeField]
    private float grappleStrength = 4.5f * 5f;
    [SerializeField]
    private float grappleDamper = 7f;
    [SerializeField]
    private float grappleBoostSpeed = 50f;
    [SerializeField]
    private float grappleDrawingSpeed = 20;
    [SerializeField]
    private LayerMask grappleableLayer;
    [SerializeField]
    private float aimSpeed = 50f;

    [Header("References")]
    [SerializeField]
    private Transform gunPos;
    [SerializeField]
    private RectTransform canvasRect;
    [SerializeField]
    private RectTransform hitMarkerRect;

    private PlayerInput playerInput;

    private Vector3 hitPosLocal, aimDir, aimDirTarget;
    private SpringJoint joint;
    private LineRenderer lr;
    RaycastHit hit;
    private bool grappled = false, grappling = false, boosted = false, changingLength = false, isStunned = false;
    float changingLengthDir = 0;
    private Vector3 currGrappleEndPos;
    private GameObject hookedTo; // The obj we are hooked to
    public GameObject HookedTo{get => hookedTo; set => hookedTo = value;}

    public bool isGrappled()
    {
        return grappled;
    }
    
#region private methods
    void Start()
    {
        lr = gameObject.GetComponentInChildren<LineRenderer>();
        lr.enabled = false;
        hitMarkerRect.gameObject.SetActive(false);
        playerInput = gameObject.GetComponent<PlayerInput>();
    }


    void Update()
    {
        if(isStunned){
            return;
        }
        
        if(grappled){
            UpdateRope(Time.deltaTime);
            
            if(boosted){
                joint.maxDistance -= grappleBoostSpeed * Time.deltaTime;
            }
            if(changingLength){
                ChangeGrappleLength(changingLengthDir * grappleExtensionSpeed * Time.deltaTime);
            }
            return; 
        }
        
        Aim();

        
    }

    private void Aim(){
        aimDir = Vector3.Lerp(aimDir, aimDirTarget, Time.deltaTime * aimSpeed);
        if(!hitMarkerRect.gameObject.activeSelf)hitMarkerRect.gameObject.SetActive(true);
        hitMarkerRect.anchoredPosition = WorldToUI(gameObject.transform.position + aimDir * maxGrappleDist);

        if(Physics.Raycast(gameObject.transform.position, aimDir, out hit, maxGrappleDist, grappleableLayer)){
            hitMarkerRect.anchoredPosition = WorldToUI(hit.point);
            hitMarkerRect.gameObject.GetComponent<Image>().color = Color.green;
            if(grappling){
                StartGrapple(hit);
            }
        }else{
            hitMarkerRect.gameObject.GetComponent<Image>().color = Color.red;
        }
    }

    private void UpdateRope(float deltaTime){
        // TODO animate a wiggle on the rope
        if(!grappled)return;
        lr.SetPosition(0, gunPos.position);
        Vector3 endPoint;
        endPoint = hookedTo.transform.TransformPoint(hitPosLocal);
        currGrappleEndPos = Vector3.Lerp(currGrappleEndPos, endPoint, deltaTime * grappleDrawingSpeed);
        lr.SetPosition(1, currGrappleEndPos);
        if(!lr.enabled)lr.enabled = true;

        if(hit.rigidbody != null){
            joint.connectedAnchor = hitPosLocal;
        }else{
            joint.connectedAnchor = endPoint;
        }
        
    }
    private void StartGrapple(RaycastHit hit){
        grappled = true;
        grappling = false;

        //Display 
        hitMarkerRect.gameObject.SetActive(false);

        // Rope phys
        joint = gameObject.AddComponent<SpringJoint>();
        joint.anchor = transform.InverseTransformPoint(gunPos.position);
        joint.autoConfigureConnectedAnchor = false;
        joint.maxDistance = hit.distance * 0.9f;
        hitPosLocal = hit.transform.InverseTransformPoint(hit.point);
        if(hit.rigidbody != null){
            Rigidbody hitRb = hit.rigidbody;
            joint.connectedBody = hitRb;
            joint.connectedAnchor = hitPosLocal;
            joint.enableCollision = true;
            
        }else{
            joint.connectedAnchor = hit.point;
        }
        joint.spring = grappleStrength;
        joint.damper = grappleDamper;
        joint.massScale = 4.5f;

        if(hit.transform.tag.Equals("Vanishing")){
            hit.transform.gameObject.GetComponent<Vanisher>().Begin(this);
        }
        hookedTo = hit.transform.gameObject;
        currGrappleEndPos = transform.position;
    }

    private void EndGrapple(){
        grappled = false;
        grappling = false;
        lr.enabled = false;
        boosted = false;
        Destroy(joint);
        hookedTo = this.gameObject;
    }

    private Vector2 WorldToUI(Vector3 point){
        Vector2 viewPortPos = Camera.main.WorldToViewportPoint(point);
        Vector2 pointScreenPos = new Vector2(((viewPortPos.x*canvasRect.sizeDelta.x)-(canvasRect.sizeDelta.x*0.5f)), ((viewPortPos.y*canvasRect.sizeDelta.y)-(canvasRect.sizeDelta.y*0.5f)));
        return pointScreenPos;
    }

    private void ChangeGrappleLength(float value){
        if(joint != null){
            joint.maxDistance += value;
        }
        
    }

    private void UnStun(){
        isStunned = false;
    }
#endregion

#region public methods

public void Stun(float duration){
    isStunned = true;
    EndGrapple();
    Invoke("UnStun", duration);
}

public void Unhook(GameObject hookedObj){
    if(hookedObj.Equals(hookedTo))
        EndGrapple();
}
#endregion

#region InputActions
    public void Grapple(InputAction.CallbackContext context){
        if(grappling && grappled)return;
        if(context.performed){
            grappling = true;
        }else if(context.canceled){
            EndGrapple();
        }
    }

    public void BoostToAnchor(InputAction.CallbackContext context){
        if(context.performed && grappled){
            boosted = true;
        }
    }

    public void AimInput(InputAction.CallbackContext context){
        Vector2 value = context.ReadValue<Vector2>();
        
        if(playerInput.currentControlScheme.Equals("Controller")){
            if(value.magnitude == 0)return;
            aimDirTarget = new Vector3(value.x, value.y, 0).normalized;
        }else if(playerInput.currentControlScheme.Equals("KbMouse")){
            var screenToWoldPoint = Camera.main.ScreenToWorldPoint(new Vector3(value.x, value.y, Mathf.Abs(Camera.main.transform.position.z)));
            var aimDirTemp = (new Vector3(screenToWoldPoint.x-transform.position.x, screenToWoldPoint.y-transform.position.y, 0)).normalized;
            if(aimDirTemp.magnitude == 0)return;
            aimDirTarget = aimDirTemp;
        }
    }

    public void RaiseLowerGrapple(InputAction.CallbackContext context){
        if(context.started){
            //IsMoving and value should be 1 or -1
            changingLength = true;
            changingLengthDir = -context.ReadValue<Vector2>().y;
        }else if(context.canceled){
            //is not moving and shoud be 0
            changingLength = false;
            changingLengthDir = context.ReadValue<Vector2>().y;
        }
            
    }
#endregion
}
