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

public class Player : Manager<Player>
{
    public float ThrustForce = 10; // in m/s^2   
    public float SteeringFactor = 10.0f;
    public float UprightMultiplier = 10.0f;
    public float DragForce = 0.75f;

    public AnimationCurve SlopeDragCurve;

    public Vector3 horizontalVelocity = Vector2.zero;

    private BoxCollider BenchBoxCollider;
    
    [SerializeField]
    private ParticleSystem thrusterParticleSystemLeft;

    [SerializeField]
    private ParticleSystem thrusterParticleSystemRight;

    public ParticleSystem Explosion;

    [SerializeField]
    private float dynamiteCountdownStart = 3.0f;

    private float dynamiteCountDown;
    
    private bool disableInput = false;

    public float DynamiteCountDown => dynamiteCountDown;

    public float Fuel = 1.0f;
    public float FuelConsumptionPerSecond = 0.01f;
    public float FuelConsumptionPerSecondBase = 0.01f;

    private float score = 0.0f;
    public float Score => score;

    public LayerMask RayLayerMask;


    IEnumerator SecondDelayInterval()
    {
        while(true)
        {

            yield return new WaitForSeconds(1.0f);

            if(dynamiteCountDown > 0)
            {
                if(horizontalVelocity.magnitude < 0.1f)
                {
                    dynamiteCountDown -= 1.0f;
                    AudioManager.Instance.GetAudioSource(AudioClip.CountdownBeep).Play();
                }
                else
                {
                    dynamiteCountDown = dynamiteCountdownStart;
                }
            }
            
            if(dynamiteCountDown <= 0)
            {
                disableInput = true;
                dynamiteCountDown = 0;

                if(GameStateManager.Instance.GameState == GameState.Play)
                {
                    Debug.Log("GameStateChange => Play => GameOver");
                    GameStateManager.Instance.SetGameState(GameState.GameOver);
                }
            }
        }
    }

    private AudioSource audioSourceSledge;
    private AudioSource audioSourceSpin;
    private AudioSource audioSourceRoar;

    protected void Start()
    {
        GameStateManager.Instance.OnGameStateChangedEvent.AddListener(OnGameStateChanged);
        
        BenchBoxCollider = GetComponentInChildren<BoxCollider>();

        dynamiteCountDown = dynamiteCountdownStart;

        audioSourceSpin = AudioManager.Instance.GetAudioSource(AudioClip.TurbineSpin);
        audioSourceRoar = AudioManager.Instance.GetAudioSource(AudioClip.TurbineRoar);
        audioSourceSledge = AudioManager.Instance.GetAudioSource(AudioClip.SnowSlideSledge);
    }

    public void OnGameStateChanged(GameState newState, GameState oldState)
    {
        Debug.Log("OnGameStateChanged "+newState);
        if(newState == GameState.Play)
        {
            StartCoroutine(SecondDelayInterval());
        }
    }

    protected void OnDestroy()
    {
        GameStateManager.Instance.OnGameStateChangedEvent.RemoveListener(OnGameStateChanged);
    }

    void FixedUpdate()
    {
        var gamepad = Gamepad.current;

        float throttle = 0;
        float steering = 0;

        Vector3 localVelocity = this.transform.InverseTransformDirection(horizontalVelocity);

        if(!disableInput)
        {
            throttle += (Keyboard.current.wKey.ReadValue()) + (Keyboard.current.sKey.ReadValue() * -1.0f);
            steering += (Keyboard.current.dKey.ReadValue()) + (Keyboard.current.aKey.ReadValue() * -1.0f);

            if(gamepad != null)
            {
                float throttleR = gamepad.rightTrigger.ReadValue();
                float throttleL = gamepad.leftTrigger.ReadValue() * -1.0f;
                throttle += throttleL + throttleR;
                steering += gamepad.leftStick.ReadValue().x;
                if(Vector3.Dot(Vector3.forward, localVelocity) < -0.1f)
                    steering *= -1.0f;
            }
        }


        audioSourceRoar.volume = Mathf.Abs(throttle) * 0.75f;
        audioSourceRoar.pitch = 1.05f;

        audioSourceSledge.volume = Mathf.Clamp(horizontalVelocity.magnitude, 0.0f, 1.0f);

        Fuel -= (Mathf.Abs(throttle) * FuelConsumptionPerSecond + FuelConsumptionPerSecondBase) * Time.fixedDeltaTime;
        if(Fuel <= 0)
        {
            Fuel = 0;
            disableInput = true;
        }


        if(disableInput)
        {
            audioSourceSpin.volume = 0.0f;
            audioSourceRoar.volume = 0.0f;
        }

        float baseEmission = disableInput ? 0 : 0.1f;
        thrusterParticleSystemLeft.emissionRate = (baseEmission + (1.0f - baseEmission) * (throttle - steering * -1.0f)) * 300.0f;
        thrusterParticleSystemRight.emissionRate = (baseEmission + (1.0f - baseEmission) * (throttle - steering)) * 300.0f;

        this.transform.Rotate(this.transform.up, steering * SteeringFactor * Time.fixedDeltaTime, Space.World);
        Vector3 forward = this.transform.forward;
        Vector3 horizontalForward = Vector3.Scale(new Vector3(1.0f, 0.0f, 1.0f), forward);

        score = Mathf.Round(this.transform.position.z * 1000.0f) / 1000.0f;

        horizontalVelocity += this.transform.forward * throttle * ThrustForce * Time.fixedDeltaTime;
        
        horizontalVelocity -= horizontalVelocity * DragForce * Time.fixedDeltaTime;

        UIManagerGame.Instance.Fuel = Fuel;
        UIManagerGame.Instance.CountdownSeconds = (int)dynamiteCountDown;
        UIManagerGame.Instance.Score = score;

        this.transform.position += horizontalVelocity;

        float OFFSET = 0.5f;
        
        int OFFSET_COUNT_X = 4;
        int OFFSET_COUNT_Y = 4;

        // TODO: Find better way to smoothly slide across the terrain without bump distortions

        int TOTAL_OFFSET_COUNT = OFFSET_COUNT_X * OFFSET_COUNT_Y;

        Vector3[] floorRaycastOffsets = new Vector3[TOTAL_OFFSET_COUNT];

        for(int x=0;x<OFFSET_COUNT_X; x++)
        {
            for(int y=0;y<OFFSET_COUNT_Y; y++)
            {
                float offsetX = ((2.0f / (OFFSET_COUNT_X-1)) * x) - 1.0f;
                float offsetY = ((2.0f / (OFFSET_COUNT_Y-1)) * y) - 1.0f;
                Vector3 offset = Vector3.up * OFFSET + Vector3.Scale((BenchBoxCollider.size / 2), new Vector3(offsetX, 0.0f, offsetY));
                floorRaycastOffsets[x*OFFSET_COUNT_X+y] = offset;
            }
        }

        Vector3[] floorRaycastHitOffsets = new Vector3[TOTAL_OFFSET_COUNT];
        Vector3 averageNormal = Vector3.zero;
        Vector3 averagePosition = Vector3.zero;

        int hitCount = 0;
        for(int n=0;n<floorRaycastHitOffsets.Length;n++)
        {
            Vector3 offset = floorRaycastOffsets[n];
            RaycastHit hitInfo;
            bool raycastHit = Physics.Raycast(this.transform.TransformPoint(offset), -this.transform.up, out hitInfo, 1000.0f, RayLayerMask.value);
            if(raycastHit)
            {
                hitCount++;
                floorRaycastHitOffsets[n] = hitInfo.point;
                averageNormal += hitInfo.normal;
                averagePosition += hitInfo.point;
            }
        }

        averageNormal *= (1.0f / hitCount);
        averagePosition *= (1.0f / hitCount);

        if(hitCount == 0)
        {
            return;
        }

        Vector3 right = Vector3.Cross(averageNormal, this.transform.forward);
        Quaternion rot = Quaternion.AngleAxis(90.0f, right);

        Quaternion newRot = this.transform.rotation;
        newRot.SetLookRotation(rot * averageNormal, averageNormal);
        this.transform.rotation = newRot;
        
        this.transform.position = averagePosition + this.transform.up * 1.0f;

        Vector3 slope = Quaternion.AngleAxis(90.0f, Vector3.Cross(Vector3.up, averageNormal).normalized) * averageNormal;
        float slopeMagnitude = Vector3.Scale(averageNormal, new Vector3(1.0f, 0.0f, 1.0f)).magnitude;
        horizontalVelocity += slope.normalized * Physics.gravity.magnitude * Time.fixedDeltaTime * 0.1f * SlopeDragCurve.Evaluate(slopeMagnitude);


        Debug.DrawRay(this.transform.position, slope, Color.yellow);
        Debug.DrawRay(this.transform.position, horizontalForward, Color.green);
        Debug.DrawRay(this.transform.position, rot * averageNormal, Color.red);
        Debug.DrawRay(this.transform.position, averageNormal, Color.blue);
        Debug.DrawRay(this.transform.position, right, Color.magenta);
    }

    protected void OnTriggerEnter(Collider collider)
    {
        Obstacle o = collider.gameObject.GetComponent<Obstacle>();
        if(o != null)
        {
            o.OnHit(this.gameObject);

            if(o.HitRecoilForce != 0.0f)
            {
                this.horizontalVelocity += -(collider.transform.position - this.transform.position).normalized * o.HitRecoilForce;
            }

            this.Fuel -= o.DamageOnHit;
        }

        PickupItem pu = collider.gameObject.GetComponent<PickupItem>();
        if(pu != null && pu.PickedUp == false)
        {
            this.Fuel += pu.Fuel;

            if(this.Fuel > 1.0f)
                this.Fuel = 1.0f;

            pu.OnPickup();

            if(disableInput)
                disableInput = false;
        }
    }
}
