using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
using UnityEngine.Rendering.PostProcessing;

public class RacerAnimator : RacerCoreFeature
{
    [Header("Kart Components")]
    #region
    [Tooltip("Mesh Root for smoothed kart rotation")]
    [SerializeField] Transform m_kart = null;
    public Transform Kart { get { return m_kart; } }

    [SerializeField] SkinnedMeshRenderer m_kartMeshRenderer;

    private Vector3 m_lastKartPosition = new Vector3();
    private Quaternion m_lastKartRotation = new Quaternion();

    [Tooltip("The location that the player will snap to when instantiated")]
    [SerializeField] Transform m_playerSocket = null;

    [Tooltip("A reference to the transform that represents the steering wheel.")]
    [SerializeField] Transform m_steeringWheel = null;

    [Tooltip("Thrusters")]
    [SerializeField] List<Transform> m_thrusters = new List<Transform>();
    #endregion
    
    [Header("Kart Animation")]
    #region
    [Tooltip("The Animator used for the kart.")]
    public Animator KartAnimator;
    
    [Tooltip("A smoothing factor that controls how quickly the kart mesh can rotate to match the angle of the checker.")]
    [Range(0.1f, 10)]
    public float KartRotationSpeed;

    [Tooltip("The current smoothed height value of the kart.")]
    [ReadOnly]
    public float SmoothKartHeight;
    [SerializeField] float m_defaultHeight;
    [SerializeField] float m_driftingHeight;

    [Tooltip("The maximum angle in degrees that the steering wheel can be turned away from its default position, when the Steering input is either 1 or -1.")]
    [Range(0, 180)]
    public float MaxSteeringWheelAngle = 90f;

    [Tooltip("The axis, local to the steering wheel, around which the steering wheel should turn when steering.")]
    [ReadOnly]
    public Vector3 SteeringWheelRotationAxis;

    [Tooltip("The current smoothed rotation value of the steering wheel.")]
    [ReadOnly]
    public float SmoothSteeringAngle;

    [Tooltip("The damping for the appearance of steering compared to the input.  The higher the number the less damping.")]
    [Range(0, 30)]
    public float SteeringAnimationDamping = 10f;

    public Color[] NormalRocketFXColors = new Color[4];

    public Color[] BoostRocketFXColors = new Color[4];
    #endregion

    [Header("Kart SFX")]
    #region
    [SerializeField] AudioSource AudioRacerMove;
    [SerializeField] AudioSource AudioDriftStart;
    private bool noDriftStartRepeat;
    [SerializeField] AudioSource AudioChargeStart;
    [SerializeField] AudioSource AudioCharge;
    [SerializeField] AudioSource AudioChargeComplete;
    [SerializeField] AudioSource AudioBoostStart;
    [SerializeField] AudioSource AudioBoostContinue;
    #endregion

    [Header("Player Components")]
    #region
    [Tooltip("The player prefab included by default")]
    [SerializeField] Transform m_player;
    public Transform Player { get { return m_player; } }
    [SerializeField] SkinnedMeshRenderer m_playerMeshRenderer;
    [SerializeField] MeshFilter m_hairMeshFilter;
    [SerializeField] MeshRenderer m_hairMeshRenderer;
    #endregion

    [Header("Player Animation")]
    #region
    [Tooltip("The Animator used for the player.")]
    public Animator PlayerAnimator;

    [Tooltip("Hash code of the Player Animator's Steering property")]
    [ReadOnly]
    public static readonly int KASteeringHash = Animator.StringToHash("Steering");

    [Tooltip("Hash code of the Player Animator's Steering property")]
    [ReadOnly]
    public static readonly int KAGroundedHash = Animator.StringToHash("Grounded");

    [Tooltip("A list of available emotes")]
    public List<AnimationClip> EmoteList = new List<AnimationClip>();
    #endregion

    [Header("Player SFX")]
    #region
    public AudioSource PlayerAudio;
    public List<AudioClip> AwClips;
    public List<AudioClip> LaughClips;
    public List<AudioClip> SurpriseClips;
    public List<AudioClip> WailClips;
    public List<AudioClip> WheeClips;
    #endregion

    [Header("Inverse Kinematics")]
    #region
    [Tooltip("Enables the use of Inverse Kinematics")]
    public bool EnableIK = false;

    [Tooltip("If IK is enabled, defines a location that the player's head will attempt to look at.")]
    public Transform IKLookTarget;

    [Tooltip("The percentage of weight given to the head's IK head versus baked animation poses")]
    [Range(0, 1)]
    public float LookTightness;

    [Tooltip("If IK is enabled, defines a location that the player's right hand will attempt to grip at.")]
    public Transform RHandIKTarget;

    [Tooltip("The percentage of weight given to the right hand's IK position versus baked animation position")]
    [Range(0, 1)]
    public float RHandPosTightness;

    [Tooltip("The percentage of weight given to the right hand's IK rotation versus baked animation rotation")]
    [Range(0, 1)]
    public float RHandRotTightness;

    [Tooltip("If IK is enabled, defines a location that the player's left hand will attempt to grip at.")]
    public Transform LHandIKTarget;

    [Tooltip("The percentage of weight given to the left hand's IK position versus baked animation position")]
    [Range(0, 1)]
    public float LHandPosTightness;

    [Tooltip("The percentage of weight given to the left hand's IK position versus baked animation position")]
    [Range(0, 1)]
    public float LHandRotTightness;
    #endregion

    [Header("Settings")]
    [SerializeField] [Range(0, 50)] float m_fxParticleRate = 10;
    [SerializeField] [Range(0, 50)] float m_fxParticleSize = 10;


    [Header("Debug")]
    [SerializeField] DebugChannelSO m_debugChannel;

    bool m_spinning = false;
    Vector2 m_inputDir = new Vector2();
    float HorizontalDir { get { return m_inputDir.x; } }
    float VerticalDir { get { return m_inputDir.y; } }

    // COROUTINES //
    #region
    IEnumerator co_boostBlur, co_matchChargePitch, co_spin, co_evenOut;
    #endregion

    // DELEGATES //
    #region
    public delegate void OnSpinStartAction();
    public OnSpinStartAction OnSpinStart;

    public delegate void OnSpinEndAction();
    public OnSpinEndAction OnSpinEnd;
    #endregion

    new void OnEnable()
    {
        base.OnEnable();

        Core.Controller.OnDriftStart += StartDriftFX;
        Core.Controller.OnDriftEnd += EndDriftFX;

        Core.Controller.OnBoostStart += StartBoostFX;
        Core.Controller.OnBoostEnd += EndBoostFX;

        Core.Controller.OnInputUpdate += SetThrusterInputs;

        Core.Controller.OnEmote += PlayEmote;

        Core.Motor.OnHitHazard += StartSpinOut;
    }
    new void OnDisable()
    {
        base.OnEnable();

        Core.Controller.OnDriftStart -= StartDriftFX;
        Core.Controller.OnDriftEnd -= EndDriftFX;

        Core.Controller.OnBoostStart -= StartBoostFX;
        Core.Controller.OnBoostEnd -= EndBoostFX;

        Core.Controller.OnInputUpdate -= SetThrusterInputs;

        Core.Controller.OnEmote -= PlayEmote;

        Core.Motor.OnHitHazard -= StartSpinOut;
    }

    void Start()
    {
        SteeringWheelRotationAxis = m_steeringWheel.right;
        m_lastKartPosition = transform.localPosition;

        InitializeThrusters();

        SnapPlayerToSocket();

        LoadPlayerPreference();
    }

    private void SnapPlayerToSocket()
    {
        if (!Player)
            return;

        if (!m_playerSocket)
            return;

        if (Player.parent != m_playerSocket.parent)
        {
            Player.parent = m_playerSocket;
            Player.localPosition = Vector3.zero;
        }
    }

    void LateUpdate()
    {
        UpdateKart();
        UpdateKartSFX();
        UpdatePlayerAnimationData();
        UpdateSteeringWheelRotation();
    }

    // DRIFT METHODS
    #region
    /// <summary>
    /// Starts drifting audio
    /// </summary>
    void StartDriftFX(bool _dir)
    {
        AudioChargeStart.Play();
        AudioCharge.Play();

        if (co_matchChargePitch == null)
        {
            co_matchChargePitch = MatchChargePitch();

            StartCoroutine(co_matchChargePitch);
        }

    }
    /// <summary>
    /// Ends drifting audio
    /// </summary>
    void EndDriftFX()
    {
        if (co_matchChargePitch != null)
        {
            StopCoroutine(co_matchChargePitch);

            co_matchChargePitch = null;
        }

        AudioCharge.Stop();
    }
    /// <summary>
    /// Matches the pitch of the drift audio to the charge level.
    /// </summary>
    /// <returns></returns>
    IEnumerator MatchChargePitch()
    {
        // While the motor's charge percentage is still below 100% and we are still drifting...
        while (Core.Motor.ChargePerc < 1 && Core.Controller.Drifting)
        {
            // Set the pitch to the percentage plus 1
            AudioCharge.pitch = 2 + Core.Motor.ChargePerc * 2;

            yield return null;
        }

        // Stop the charge audio
        AudioCharge.Stop();

        // If we reached 100%, play the completion audio
        if (Core.Motor.ChargePerc == 1)
            AudioChargeComplete.Play();

        co_matchChargePitch = null;

        yield break;
    }
    #endregion

    // BOOST METHODS
    #region
    /// <summary>
    /// Starts boosting audio and FX
    /// </summary>
    void StartBoostFX()
    {
        AudioBoostStart.Play();
        AudioBoostContinue.Play();
    }
    /// <summary>
    /// Ends boosting audio and FX
    /// </summary>
    void EndBoostFX()
    {
        AudioBoostContinue.Stop();
    }
    #endregion

    /// <summary>
    /// Updates the appearance of the kart's rotation and position relative to the checker.
    /// </summary>
    void UpdateKart()
    {
        if (!m_spinning)
        {
            m_kart.rotation = Quaternion.Slerp(m_kart.rotation, transform.rotation, KartRotationSpeed * Time.deltaTime);
            m_lastKartRotation = m_kart.rotation;
        }
        
        SmoothKartHeight = Mathf.MoveTowards(SmoothKartHeight, Core.Controller.Drifting ? m_driftingHeight : m_defaultHeight, 5 * Time.deltaTime);
        m_kart.localPosition = Vector3.Lerp(Vector3.zero, -Vector3.up, SmoothKartHeight);
    }

    void UpdateKartSFX()
    {
        if (AudioRacerMove)
        {
            float _volumeScalar = 0.1f;
            float _basePitch = 0.2f;
            float _movementSpeed = Core.RigidBD.velocity.magnitude;

            AudioRacerMove.volume = Mathf.Clamp((_movementSpeed * _volumeScalar) / 10, 0, 0.1f);
            AudioRacerMove.pitch = _basePitch + ((_movementSpeed * _basePitch) / (_basePitch * 10));
        }
    }

    /// <summary>
    /// Updates steering animation depending on controller inputs
    /// </summary>
    void UpdatePlayerAnimationData()
    {
        if (!PlayerAnimator.GetCurrentAnimatorStateInfo(0).IsName("Idle")) { return; }

        SmoothSteeringAngle = Core.Controller.Disabled ? 0 : Mathf.MoveTowards(SmoothSteeringAngle, Core.Controller.HorizontalInput, SteeringAnimationDamping * Time.deltaTime);

        PlayerAnimator.SetFloat(KASteeringHash, SmoothSteeringAngle);
        PlayerAnimator.SetBool(KAGroundedHash, Core.Motor.Grounded);
    }
    
    /// <summary>
    /// Handles Inverse Kinematic adjustments
    /// </summary>
    void OnAnimatorIK()
    {
        if (!PlayerAnimator.GetCurrentAnimatorStateInfo(0).IsName("Idle")) { return; }

        //if the IK is active, set the position and rotation directly to the goal. 
        if (EnableIK)
        {
            // Set the look target position, if one has been assigned
            if (IKLookTarget != null)
            {
                PlayerAnimator.SetLookAtWeight(LookTightness);
                PlayerAnimator.SetLookAtPosition(IKLookTarget.position);
            }
            else
            {
                PlayerAnimator.SetLookAtWeight(0);
            }

            // Set the right hand target position and rotation, if one has been assigned
            if (RHandIKTarget != null)
            {
                PlayerAnimator.SetIKPositionWeight(AvatarIKGoal.RightHand, RHandPosTightness);
                PlayerAnimator.SetIKRotationWeight(AvatarIKGoal.RightHand, RHandRotTightness);
                PlayerAnimator.SetIKPosition(AvatarIKGoal.RightHand, RHandIKTarget.position);
                PlayerAnimator.SetIKRotation(AvatarIKGoal.RightHand, RHandIKTarget.rotation);
            }
            else
            {
                PlayerAnimator.SetIKPositionWeight(AvatarIKGoal.RightHand, 0);
                PlayerAnimator.SetIKRotationWeight(AvatarIKGoal.RightHand, 0);
            }

            // Set the left hand target position and rotation, if one has been assigned
            if (LHandIKTarget != null)
            {
                PlayerAnimator.SetIKPositionWeight(AvatarIKGoal.LeftHand, LHandPosTightness);
                PlayerAnimator.SetIKRotationWeight(AvatarIKGoal.LeftHand, LHandRotTightness);
                PlayerAnimator.SetIKPosition(AvatarIKGoal.LeftHand, LHandIKTarget.position);
                PlayerAnimator.SetIKRotation(AvatarIKGoal.LeftHand, LHandIKTarget.rotation);
            }
            else
            {
                PlayerAnimator.SetIKPositionWeight(AvatarIKGoal.LeftHand, 0);
                PlayerAnimator.SetIKRotationWeight(AvatarIKGoal.LeftHand, 0);
            }
        }
        //if the IK is not active, set the position and rotation of the hand and head back to the original position
        else
        {
            if (RHandIKTarget)
            {
                PlayerAnimator.SetIKPositionWeight(AvatarIKGoal.RightHand, 0);
                PlayerAnimator.SetIKRotationWeight(AvatarIKGoal.RightHand, 0);
            }
            if (LHandIKTarget)
            {
                PlayerAnimator.SetIKPositionWeight(AvatarIKGoal.LeftHand, 0);
                PlayerAnimator.SetIKRotationWeight(AvatarIKGoal.LeftHand, 0);
            }
            if (IKLookTarget)
            {
                PlayerAnimator.SetLookAtWeight(0);
            }
        }
    }
    
    /// <summary>
    /// Updates the steering wheel's angle according to smoothed inputs
    /// </summary>
    void UpdateSteeringWheelRotation()
    {
        m_steeringWheel.localRotation = Quaternion.Euler(0, 0, -45) * Quaternion.Euler(-MaxSteeringWheelAngle * SmoothSteeringAngle, 0, 0);
    }


    // THRUSTER FX METHODS //
    #region
    void InitializeThrusters()
    {
        RecursivelyCheckForThrusters(transform);

        SetThrusterInputs(Vector2.zero, false);
    }
    void RecursivelyCheckForThrusters(Transform parent)
    {
        foreach (Transform child in parent)
        {
            if (child.childCount > 0)
                RecursivelyCheckForThrusters(child);

            if (IsThruster(child) && !m_thrusters.Contains(child))
                m_thrusters.Add(child);
        }
    }
    bool IsThruster(Transform _transform)
    {
        if (transform.CompareTag("LeftThruster"))
            return true;

        if (transform.CompareTag("RightThruster"))
            return true;

        if (transform.CompareTag("ForwardThruster"))
            return true;

        if (transform.CompareTag("BoostThruster"))
            return true;

        return false;
    }

    /// <summary>
    /// Handles Thruster particle systems
    /// </summary>
    void SetThrusterInputs(Vector2 _newInputDirs, bool _boosting)
    {
        if (m_spinning)
            return;

        //if (m_debugChannel)
        //    m_debugChannel.Raise(this, "Update thruster FX: " + _newInputDirs.ToString() + ", " + _boosting.ToString(), DebugChannelSO.Severity.Log);

        if (PhotonNetwork.IsConnected && Core.IsLocal)
            photonView.RPC("SetRemoteFX", RpcTarget.Others, _newInputDirs, _boosting);

        foreach (Transform thruster in m_thrusters)
        {
            ParticleSystem[] systems = thruster.GetComponentsInChildren<ParticleSystem>();

            switch (thruster.tag)
            {
                case ("ForwardThruster"):
                    foreach(ParticleSystem system in systems)
                    {
                        var main = system.main;
                        float _size = 1;

                        switch (system.transform.name)
                        {
                            case ("SmallSparks"):
                                _size = 3;
                                break;
                            case ("LargeSparks"):
                                _size = 1;
                                break;
                            case ("Glow"):
                                _size = 0.25f;
                                break;
                        }

                        main.startSize = m_fxParticleSize * _size;

                        var emission = system.emission;
                        emission.rateOverTime = m_fxParticleRate * (system.transform.name == "Glow" ? 0.5f : 1) * Mathf.Clamp01(_newInputDirs.y);
                        
                        if (emission.rateOverTime.constant > 0)
                            UpdateEmissionColors(system, _boosting);
                    }
                    break;
                case ("LeftThruster"):
                    foreach (ParticleSystem system in systems)
                    {
                        var main = system.main;
                        float _size = 1;

                        switch (system.transform.name)
                        {
                            case ("SmallSparks"):
                                _size = 3;
                                break;
                            case ("LargeSparks"):
                                _size = 1;
                                break;
                            case ("Glow"):
                                _size = 0.25f;
                                break;
                        }

                        main.startSize = m_fxParticleSize * _size;

                        var emission = system.emission;
                        emission.rateOverTime =  m_fxParticleRate * (system.transform.name == "Glow" ? 0.5f : 1) * (_boosting || _newInputDirs.x > 0 ? 1 : 0);

                        if (emission.rateOverTime.constant > 0)
                            UpdateEmissionColors(system, _boosting);
                    }
                    break;
                case ("RightThruster"):
                    foreach (ParticleSystem system in systems)
                    {
                        var main = system.main;
                        float _size = 1;

                        switch (system.transform.name)
                        {
                            case ("SmallSparks"):
                                _size = 3;
                                break;
                            case ("LargeSparks"):
                                _size = 1;
                                break;
                            case ("Glow"):
                                _size = 0.25f;
                                break;
                        }

                        main.startSize = m_fxParticleSize * _size;

                        var emission = system.emission;
                        emission.rateOverTime = m_fxParticleRate * (system.transform.name == "Glow" ? 0.5f : 1) * (_boosting || _newInputDirs.x < 0 ? 1 : 0);

                        if (emission.rateOverTime.constant > 0)
                            UpdateEmissionColors(system, _boosting);
                    }
                    break;
                case ("BoostThruster"):
                    foreach (ParticleSystem system in systems)
                    {
                        var main = system.main;
                        float _size = 1;

                        switch (system.transform.name)
                        {
                            case ("SmallSparks"):
                                _size = 3;
                                break;
                            case ("LargeSparks"):
                                _size = 1;
                                break;
                            case ("Glow"):
                                _size = 0.25f;
                                break;
                        }

                        main.startSize = m_fxParticleSize * _size;

                        var emission = system.emission;
                        emission.rateOverTime = _boosting ? m_fxParticleRate * (system.transform.name == "Glow" ? 0.5f : 1) : 0;

                        if (emission.rateOverTime.constant > 0)
                            UpdateEmissionColors(system, _boosting);
                    }
                    break;
            }
        }
    }
    /// <summary>
    /// Disables all Thrust particle systems
    /// </summary>
    void DisableThrusterFX()
    {
        foreach (Transform thruster in m_thrusters)
        {
            ParticleSystem[] systems = thruster.GetComponentsInChildren<ParticleSystem>();

            switch (thruster.tag)
            {
                case ("ForwardThruster"):
                    foreach (ParticleSystem system in systems)
                    {
                        var emission = system.emission;
                        emission.rateOverTime = 0;
                    }
                    break;
                case ("LeftThruster"):
                    foreach (ParticleSystem system in systems)
                    {
                        var emission = system.emission;
                        emission.rateOverTime = 0;
                    }
                    break;
                case ("RightThruster"):
                    foreach (ParticleSystem system in systems)
                    {
                        var emission = system.emission;
                        emission.rateOverTime = 0;
                    }
                    break;
                case ("BoostThruster"):
                    foreach (ParticleSystem system in systems)
                    {
                        var emission = system.emission;
                        emission.rateOverTime = 0;
                    }
                    break;
            }
        }
    }
    /// <summary>
    /// Handles the colors of the thrusters
    /// </summary>
    /// <param name="system"></param>
    void UpdateEmissionColors(ParticleSystem system, bool _boosting)
    {
        var main = system.main;

        if (system.transform.name == "Glow")
        {
            main.startColor = _boosting ? BoostRocketFXColors[3] : NormalRocketFXColors[3];

            return;
        }

        main.startColor = _boosting ? BoostRocketFXColors[0] : NormalRocketFXColors[0];

        var colorOverLifetime = system.colorOverLifetime;

        Gradient grad = new Gradient();
        if (_boosting)
        {
            grad.SetKeys(new GradientColorKey[] { new GradientColorKey(BoostRocketFXColors[1], 0.0f), new GradientColorKey(BoostRocketFXColors[2], 1.0f) }, new GradientAlphaKey[] { new GradientAlphaKey(1.0f, 0.0f), new GradientAlphaKey(.0f, 1.0f) });
        }
        else
        {
            grad.SetKeys(new GradientColorKey[] { new GradientColorKey(NormalRocketFXColors[1], 0.0f), new GradientColorKey(NormalRocketFXColors[2], 1.0f) }, new GradientAlphaKey[] { new GradientAlphaKey(1.0f, 0.0f), new GradientAlphaKey(1.0f, 1.0f) });
        }

        colorOverLifetime.color = grad;
    }
    #endregion

    // EMOTE METHODS //
    #region
    /// <summary>
    /// Randomly plays a player audio clip from a list
    /// </summary>
    /// <param name="clips"></param>
    public void PlayPlayerAudioFromList(List<AudioClip> clips)
    {
        AudioClip clip = clips[(int)Random.Range(0, clips.Count - 1)];
        PlayPlayerAudio(clip);
    }
    #endregion

    // SPIN OUT METHODS //
    #region
    /// <summary>
    /// Starts the spinout coroutine
    /// </summary>
    void StartSpinOut()
    {
        if (co_spin != null)
            return;

        if (PhotonNetwork.IsConnected && Core.IsLocal)
            photonView.RPC("ReportSpin", RpcTarget.Others);

        if (OnSpinStart != null)
            OnSpinStart.Invoke();

        DisableThrusterFX();

        if (co_evenOut != null)
        {
            StopCoroutine(co_evenOut);
            co_evenOut = null;
        }

        co_spin = SpinOut();
        StartCoroutine(co_spin);
    }
    /// <summary>
    /// Causes players to spin out and slow down
    /// </summary>
    /// <param name="_duration"></param>
    /// <returns></returns>
    IEnumerator SpinOut()
    {
        // Create a timer
        float t = 0;
        m_spinning = true;

        // While the timer is below the given duration...
        while (t < 1.5f)
        {
            // Get the spin of the player this frame in degrees
            float offset = 360 * t;
            
            // Set the yaw of the kart to a default of -90 plus the offset
            m_kart.transform.localRotation = Quaternion.Euler(0f, -90 + offset, 0f);

            // Increment the timer
            t += Time.deltaTime;

            // Wait until the next frame
            yield return null;
        }

        ReturnToFrontRotation();
    }
    void ReturnToFrontRotation()
    {
        if (OnSpinEnd != null)
            OnSpinEnd.Invoke();

        if (co_evenOut != null)
            return;

        co_evenOut = EvenOut();
        StartCoroutine(co_evenOut);
    }
    IEnumerator EvenOut()
    {
        while (Quaternion.Angle(m_kart.localRotation, transform.rotation) > Time.deltaTime)
        {
            m_kart.rotation = Quaternion.Slerp(m_kart.rotation, transform.rotation, KartRotationSpeed * Time.deltaTime);
            yield return null;
        }

        m_spinning = false;

        yield break;
    }
    #endregion

    // PLAYER CUSTOMIZATION METHODS //
    #region
    /// <summary>
    /// Loads the previously saved character and send an RPC to all players to adjust the racer's appearence
    /// </summary>
    void LoadPlayerPreference()
    {
        if (photonView && !photonView.IsMine && PhotonNetwork.IsConnected)
            return;

        Vector3 _hairColors = new Vector3();
        Vector3 _kartColors = new Vector3();

        if (m_hairMeshFilter)
        {
            Mesh hairMesh = (Mesh)Resources.Load("Hair Models/" + PlayerPrefs.GetString("HairStyle"));

            m_hairMeshFilter.mesh = hairMesh;

            m_hairMeshFilter.mesh = (Mesh)Resources.Load("Hair Models/" + PlayerPrefs.GetString("HairStyle"));

            float hr = PlayerPrefs.GetFloat("HairColorR");
            float hg = PlayerPrefs.GetFloat("HairColorG");
            float hb = PlayerPrefs.GetFloat("HairColorB");

            m_hairMeshRenderer.material.color = new Color(hr, hg, hb);

            _hairColors = new Vector3(hr, hg, hb);
        }
        

        if (m_playerMeshRenderer)
        {
            m_playerMeshRenderer.material.mainTexture = (Texture2D)Resources.Load("Player Textures/" + PlayerPrefs.GetString("PlayerTexture"));
        }

        if (m_kartMeshRenderer)
        {
            //PlayerPrefs.GetString("KartTexture");
            float kr = PlayerPrefs.GetFloat("KartColorR");
            float kg = PlayerPrefs.GetFloat("KartColorG");
            float kb = PlayerPrefs.GetFloat("KartColorB");

            m_kartMeshRenderer.materials[0].mainTexture = (Texture2D)Resources.Load("Kart Textures/" + PlayerPrefs.GetString("KartTexture"));

            m_kartMeshRenderer.materials[0].color = new Color(kr, kg, kb);
            m_kartMeshRenderer.materials[4].color = new Color(kr, kg, kb);

            _kartColors = new Vector3(kr, kg, kb);
        }

        string _playerString = "Player Textures/" + PlayerPrefs.GetString("PlayerTexture");
        string _hairString = "Hair Models/" + PlayerPrefs.GetString("HairStyle");
        string _kartString = "Kart Textures/" + PlayerPrefs.GetString("KartTexture");

        if (Core.IsLocal && PhotonNetwork.IsConnected)
            photonView.RPC("AssignRacerDefaults", RpcTarget.OthersBuffered, photonView.ViewID, _playerString, _hairString, _kartString, _hairColors, _kartColors);
    }
    #endregion

    // RPC / PHOTON //
    #region
    new public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
    {
        throw new System.NotImplementedException();
    }
    /// <summary>
    /// Plays an emote
    /// </summary>
    /// <param name="selection"></param>
    [PunRPC]
    void PlayEmote(int selection)
    {
        if (Core.photonView.IsMine)
        {
            Core.photonView.RPC("PlayEmote", RpcTarget.Others, selection);

            if (!PlayerAnimator.GetCurrentAnimatorStateInfo(0).IsName("Idle") || selection > EmoteList.Count)
                return;
        }
        
        PlayerAnimator.Play(EmoteList[selection - 1].name.Replace("Emote", ""));
        PlayPlayerAudioFromList(Core.Animator.WheeClips);
    }
    [PunRPC]
    /// <summary>
    /// Plays a player audio clip
    /// </summary>
    /// <param name="clip"></param>
    public void PlayPlayerAudio(AudioClip clip)
    {
        PlayerAudio.Stop();
        PlayerAudio.clip = clip;
        PlayerAudio.Play();
    }
    /// <summary>
    /// Handles setup of other players via their appearance
    /// </summary>
    /// <param name="_id"></param>
    /// <param name="_playerSkin"></param>
    /// <param name="_hairStyle"></param>
    /// <param name="_hairColors"></param>
    /// <param name="_kartColors"></param>
    [PunRPC]
    public void AssignRacerDefaults(int _id, string _playerSkin, string _hairStyle, string _kartStyle, Vector3 _hairColors, Vector3 _kartColors /*, Vector3 _thrusterColors*/)
    {
        // AssignRacerDefaults( photonView.ViewID, _playerSkin, _hairSkin, _kartSkin, _hairColors, _kartColors);
        // If this is a locally hosted racer, we do not have to load their appearance
        if (Core.IsLocal)
            return;

        // If we have a player mesh renderer...
        if (m_playerMeshRenderer)
        {
            Texture2D _newTexture;

            // If we can load the given skin file...
            if (Resources.Load(_playerSkin))
            {
                // Load it
                _newTexture = (Texture2D)Resources.Load(_playerSkin);

                if (m_debugChannel)
                    m_debugChannel.Raise(this, "Received player skin " + _playerSkin);
            }
            // Otherwise...
            else
            {
                // Load a random skin
                Texture2D[] localSkins = (Texture2D[])Resources.LoadAll("/PlayerTextures");

                _newTexture = localSkins[Random.Range(0, localSkins.Length - 1)];

                if (m_debugChannel)
                    m_debugChannel.Raise(this, new string[]{ 
                        "Received player skin " + _hairStyle + " could not be found.", 
                        "Selecting random player skin " + _newTexture.ToString() });
            }

            m_playerMeshRenderer.material.mainTexture = _newTexture;

            if (m_debugChannel)
                m_debugChannel.Raise(this, "Loaded texture " + _newTexture.name);
        }

        // If we have a hair mesh filter...
        if (m_hairMeshFilter)
        {
            Mesh _newMesh;

            // Loads the player hairstyle
            if (Resources.Load(_hairStyle))
            {
                // Load the given mesh if it exists in the local context
                _newMesh = (Mesh)Resources.Load(_hairStyle);

                if (m_debugChannel)
                    m_debugChannel.Raise(this, "Received hair mesh " + _hairStyle + " with color " + _hairColors.ToString());
            }
            else
            {
                // Load a random style if the specified skin is missing
                Mesh[] localStyles = (Mesh[])Resources.LoadAll("/HairStyles");

                _newMesh = localStyles[Random.Range(0, localStyles.Length - 1)];

                if (m_debugChannel)
                    m_debugChannel.Raise(this, new string[] {
                    "Received hair mesh " + _hairStyle + " could not be found.",
                    "Selecting random hair mesh " + _newMesh.name + " with color " + _hairColors.ToString() });
            }

            m_hairMeshFilter.mesh = _newMesh;

            Color _newColor = new Color(_hairColors.x, _hairColors.y, _hairColors.z);

            // Set the hair renderer's color
            m_hairMeshFilter.GetComponent<MeshRenderer>().material.color = _newColor;

            if (m_debugChannel)
                m_debugChannel.Raise(this, "Loaded hair mesh " + _newMesh.name + " with color " + _newColor.ToString());
        }

        // If we have a kart mesh renderer...
        if (m_kartMeshRenderer)
        {
            Texture2D _newTexture = default(Texture2D);

            // Loads the kart texture
            if (Resources.Load(_kartStyle))
            {
                // Load the given texture to the primary material if it exists in the local context
                _newTexture = (Texture2D)Resources.Load(_kartStyle);
            
                m_kartMeshRenderer.materials[0].mainTexture = _newTexture;

                if (m_debugChannel)
                    m_debugChannel.Raise(this, "Received kart texture " + _newTexture.ToString() + " with color " + _kartColors.ToString());
            }
            else
            {
                if (m_debugChannel)
                    m_debugChannel.Raise(this,  "Received hair mesh " + _newTexture.ToString() + " could not be found.");
            }

            Color _newColor = new Color(_kartColors.x, _kartColors.y, _kartColors.z);

            // Set the 1st and 4th material in the kart's renderer (primary & decal)
            m_kartMeshRenderer.materials[0].color = _newColor;
            m_kartMeshRenderer.materials[3].color = _newColor;

            if (m_debugChannel)
                m_debugChannel.Raise(this, "Loaded kart  " + (_newTexture != default(Texture2D) ? "texture " + _newTexture.name : " no texture ") + " with color " + _newColor.ToString());
        }

        // Sets the kart's thruster colors
        //for (int i = 0; i < NormalRocketFXColors.Length; i++)
        //{
        //    NormalRocketFXColors[i] = new Color(thrusterColors[i].x, thrusterColors[i].y, thrusterColors[i].z);
        //}

        foreach (Transform _thruster in m_thrusters)
        {
            ParticleSystem[] _systems = _thruster.GetComponentsInChildren<ParticleSystem>();

            foreach (ParticleSystem _system in _systems)
            {
                UpdateEmissionColors(_system, false);
            }
        }
    }
    /// <summary>
    /// Matches up thruster FX across multiplayer instances
    /// </summary>
    /// <param name="_inputDirs"></param>
    /// <param name="_boosting"></param>
    [PunRPC]
    private void SetRemoteFX(Vector2 _inputDirs, bool _boosting)
    {
        SetThrusterInputs(_inputDirs, _boosting);
    }
    [PunRPC]
    void ReportSpin()
    {
        SpinOut();
    }
    #endregion
}
