using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

public enum GearState
{
    Neutral,
    Running,
    CheckChange,
    Changing
}

public class Car : MonoBehaviour
{
    [Header("Values")]
    public float motorPower;
    public float weight;
    public Transform centerOfMass;

    public bool RWD;
    public bool FWD;

    [Header("Nerd Shit (v metrax)")]
    public float wheelBase;
    public float rearTrack;
    public float turnRadius;

    [Header("Wheels")]
    public List<Suspension> wheels;

    [Header("Drift specs")]
    public float driftMultiplier = 1f;
    public float driftThreshold = 0.5f;

    [Header("Transmission")]
    public float RPM;
    public float redLine;
    public float idleRPM;
    public AnimationCurve powerCurve;
    [Space]
    public float differentialRatio;
    public GearState gearState;
    public float increaseGearRPM;
    public float decreaseGearRPM;
    [Space]
    public int currentGear;
    public float[] gearRatios;

    [Header("Debug")]
    public TextMeshProUGUI infoText;
    [Space]
    [SerializeField] private Transform rpmNeedle;
    [SerializeField] private float minNeedleAngle;
    [SerializeField] private float maxNeedleAngle;

    // only editable through other scripts
    [HideInInspector] public float horInput, verInput;
    [HideInInspector] public bool grounded, drifting;
    [HideInInspector] public float num, num2, driftFactor2, speed;

    private Rigidbody rb;
    private float dir, startForce, startRadius;
    private float ackermannAngleLeft, ackermannAngleRight;
    private float currentTorque, clutch, wheelRPM;
    private int wheelIndex;

    private Vector3 XZVector(Vector3 v) { return new Vector3(v.x, 0f, v.z); }

    void Start()
    {
        rb = GetComponent<Rigidbody>();
        rb.centerOfMass = centerOfMass.position;
        rb.mass = weight;

        startForce  = motorPower;
        startRadius = turnRadius;
    }

    void Update()
    {
        HudInit();

        if (gearState != GearState.Changing)
        {
            if (gearState == GearState.Neutral)
            {
                clutch = 0;
                if (verInput > 0) gearState = GearState.Running;
            }
            else clutch = Input.GetKey(KeyCode.LeftShift) ? 0 : Mathf.Lerp(clutch, 1, Time.deltaTime);
        }
        
        if(verInput > 0) gearState = GearState.Running;
        //var slipAngle = Vector3.Angle(transform.forward, rb.velocity - transform.forward);

        if (GameObject.Find("MobileControls") == null)
        {
            horInput = Input.GetAxisRaw("Horizontal");
            verInput = Input.GetAxis("Vertical");
        }

        Steering();
        CheckGrounded();
        WheelInit();

        currentGear = Mathf.Clamp(currentGear, 0, gearRatios.Length - 1);
    }

    private void FixedUpdate()
    {
        Movement();
    }

    private void Movement()
    {
        drifting = false;
        if (RWD && FWD) motorPower = startForce / 2;
        else motorPower = startForce;

        Vector3 vector = XZVector(rb.velocity);
        Vector3 vector2 = transform.InverseTransformDirection(XZVector(rb.velocity));
        dir = Mathf.Sign(transform.InverseTransformDirection(vector).z);
        speed = vector.magnitude * 3.6f * dir;
        float num = Mathf.Abs(rb.angularVelocity.y);
        foreach (Suspension suspension in wheels)
        {
            wheelIndex = wheels.IndexOf(suspension);
            currentTorque = CalculateTorque();

            if (suspension.grounded)
            {
                Vector3 pointVel = XZVector(rb.GetPointVelocity(suspension.hit.point));
                transform.InverseTransformDirection(pointVel);
                Vector3 proj = Vector3.Project(pointVel, suspension.transform.right);
                float num2 = 1f;
                float f = Mathf.Atan2(vector2.x, vector2.z);

                float driftThreshold = this.driftThreshold;
                if (num > 1f)
                {
                    driftThreshold -= 0.2f;
                }

                if (Mathf.Abs(f) > driftThreshold)
                {
                    float num4 = Mathf.Clamp(Mathf.Abs(f) * 2.4f - driftThreshold, 0f, 1f);
                    num2 = Mathf.Clamp(1f - num4, 0.05f, 1f);
                    float magnitude = rb.velocity.magnitude;
                    drifting = true;
                    if (magnitude < 8f)
                    {
                        num2 += (8f - magnitude) / 8f;
                    }
                    if (num < 0.6f)
                    {
                        float num5 = (0.6f - num) / 0.6f;
                        num2 += num5 * 0.15f;
                    }
                    if (Mathf.Abs(verInput) < 0.3f)
                    {
                        num2 += 0.1f;
                    }
                    num2 = Mathf.Clamp(num2, 0f, 1f);
                }

                float driftApplier = 1f;
                if (drifting) driftApplier = driftMultiplier;

                if (RWD)
                {
                    if (suspension.wheel == Suspension.Wheel.rearLeft || suspension.wheel == Suspension.Wheel.rearRight)
                        rb.AddForceAtPosition(suspension.transform.forward * verInput * currentTorque * driftApplier, suspension.hit.point);

                    if (drifting && !FWD) turnRadius = 5f; else turnRadius = startRadius;
                }

                if (FWD)
                {
                    if (suspension.wheel == Suspension.Wheel.frontLeft || suspension.wheel == Suspension.Wheel.frontRight)
                        rb.AddForceAtPosition(suspension.transform.forward * verInput * currentTorque * driftApplier, suspension.hit.point);
                }

                Vector3 a2 = proj * rb.mass * num2;
                rb.AddForceAtPosition(-a2, suspension.hit.point);
                rb.AddForceAtPosition(suspension.transform.forward * a2.magnitude * 0.25f, suspension.hit.point);

                float traction = Mathf.Clamp(1f - num2, 0f, 1f);
                if (Mathf.Sign(dir) != Mathf.Sign(verInput) && speed > 2f)
                {
                    traction = Mathf.Clamp(traction + 0.5f, 0f, 1f);
                }
                suspension.traction = traction;

                Vector3 dragForce = -3.5f * vector; //-3.5f is drag
                rb.AddForce(dragForce);
                Vector3 rollForce = -105f * vector; //-105 is rollfriction
                rb.AddForce(rollForce);
            }
        }
        StandStill();
    }

    float CalculateTorque()
    {
        float torque = 0;

        if (RPM < idleRPM + 200 && verInput == 0 && currentGear == 0)
        {
            gearState = GearState.Neutral;
        }
        if (gearState == GearState.Running && clutch > 0)
        {
            if (RPM > increaseGearRPM)
            {
                StartCoroutine(ChangeGear(1));
            }
            else if (RPM < decreaseGearRPM)
            {
                StartCoroutine(ChangeGear(-1));
            }
        }
        
        if (clutch < 0.1f)
        {
            RPM = Mathf.Lerp(RPM, Mathf.Max(idleRPM, redLine * verInput) + Random.Range(-50, 50), Time.deltaTime);
        }
        else
        {
            wheelRPM = Mathf.Abs(wheels[wheelIndex].wheelRPM * gearRatios[currentGear] * differentialRatio);
            RPM = Mathf.Lerp(RPM, Mathf.Max(idleRPM - 100, wheelRPM), Time.deltaTime * 3f);
            torque = (powerCurve.Evaluate(RPM / redLine) * motorPower / RPM) * gearRatios[currentGear] * differentialRatio * 5252f * clutch;
        }
        return torque;
    }

    private void CheckGrounded()
    {
        grounded = false;
        foreach (Suspension sus in wheels)
        {
            if (sus.grounded) grounded = true; return;
        }
    }

    void Steering()
    {
        foreach (Suspension sus in wheels)
        {
            if (horInput > 0)
            {
                ackermannAngleLeft = Mathf.Rad2Deg * Mathf.Atan(wheelBase / (turnRadius + (rearTrack / 2))) * horInput;
                ackermannAngleRight = Mathf.Rad2Deg * Mathf.Atan(wheelBase / (turnRadius - (rearTrack / 2))) * horInput;
            }
            else if (horInput < 0)
            {
                ackermannAngleLeft = Mathf.Rad2Deg * Mathf.Atan(wheelBase / (turnRadius - (rearTrack / 2))) * horInput;
                ackermannAngleRight = Mathf.Rad2Deg * Mathf.Atan(wheelBase / (turnRadius + (rearTrack / 2))) * horInput;
            }
            else
            {
                ackermannAngleLeft = 0;
                ackermannAngleRight = 0;
            }

            if (sus.wheel == Suspension.Wheel.frontLeft)
                sus.steerAngle = ackermannAngleLeft;
            if (sus.wheel == Suspension.Wheel.frontRight)
                sus.steerAngle = ackermannAngleRight;
        }
    }

    void WheelInit()
    {
        foreach (Suspension suspension in wheels)
        {
            float num = suspension.springLength;
            float hitHeight = suspension.hit.distance;

            float offset;
            if (suspension.grounded) offset = 0; else offset = -1;

            float y = Mathf.Lerp(suspension.visualWheel.transform.localPosition.y, -hitHeight + num, Time.deltaTime * 20f);
            suspension.visualWheel.transform.localPosition = new Vector3(suspension.transform.localPosition.x * transform.localScale.x, -suspension.hit.distance + suspension.wheelRadius + offset, suspension.transform.localPosition.z * transform.localScale.z);
            suspension.visualWheel.GetChild(0).Rotate(-Vector3.forward, XZVector(rb.velocity).magnitude * dir);

            suspension.visualWheel.localScale = Vector3.one * (suspension.springLength * 2f);
            suspension.visualWheel.transform.localScale = new Vector3(suspension.wheelRadius * 2, suspension.wheelRadius * 2, suspension.wheelRadius * 2);

            suspension.transform.localRotation = Quaternion.Euler(suspension.transform.localRotation.x, suspension.wheelAngle, suspension.transform.localRotation.z);
            suspension.visualWheel.transform.localRotation = Quaternion.Euler(0, suspension.wheelAngle, 0);
        }
    }

    void StandStill()
    {
        if (Mathf.Abs(speed) >= 1f || !grounded || verInput != 0f)
        {
            rb.drag = 0f;
            return;
        }
        bool flag = true;
        for (int i = 0; i < wheels.Count; i++)
        {
            if (Vector3.Angle(wheels[i].hit.normal, Vector3.up) > 1f)
            {
                flag = false;
                break;
            }
        }
        if (flag)
        {
            rb.drag = (1f - Mathf.Abs(speed)) * 30f;
            return;
        }
        rb.drag = 0f;
    }

    IEnumerator ChangeGear(int gearChange)
    {
        print("change gear");

        gearState = GearState.CheckChange;
        if (currentGear + gearChange >= 0)
        {
            if (gearChange > 0)
            {
                yield return new WaitForSeconds(0.7f);
                if (RPM < increaseGearRPM || currentGear >= gearRatios.Length - 1)
                {
                    gearState = GearState.Running;
                    yield break;
                }
            }
            if (gearChange < 0)
            {
                yield return new WaitForSeconds(0.1f);
                if (RPM > decreaseGearRPM || currentGear <= 0)
                {
                    gearState = GearState.Running;
                    yield break;
                }
            }
            gearState = GearState.Changing;
            yield return new WaitForSeconds(0.5f);
            currentGear += gearChange;
        }

        if (gearState != GearState.Neutral)
            gearState = GearState.Running;
    }

    void HudInit()
    {
        rpmNeedle.rotation = Quaternion.Euler(0, 0, Mathf.Lerp(minNeedleAngle, maxNeedleAngle, RPM/(redLine * 1.1f)));
        infoText.text = (int)speed +"km/h \n" + (int)RPM + "RPM \n" + ((gearState == GearState.Neutral) ? "Neutral Gear" : ((currentGear + 1) + "th Gear"));
    }
}