using Photon.Pun;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

/// <summary>
/// The Core is in charge of holding references to all 
/// components of the racer for easy access
/// </summary>

// COMPONENT REQUIREMENTS //
#region
[RequireComponent(typeof(RacerController))]
[RequireComponent(typeof(RacerItems))]
[RequireComponent(typeof(RacerMotor))]
[RequireComponent(typeof(RacerAnimator))]
[RequireComponent(typeof(SphereCollider))]
[RequireComponent(typeof(Rigidbody))]
#endregion

public class RacerCore : MonoBehaviourPunCallbacks, IPunObservable, IHittable
{
    [Header("Core Feature Components")]
    #region
    [Tooltip("Manages inputs and state information for use in other kart components")]
    [ReadOnly]
    public RacerController Controller;

    [Tooltip("Manages item acquisition and use")]
    [ReadOnly]
    public RacerItems Items;

    [Tooltip("Controls kart movement and physics")]
    [ReadOnly]
    public RacerMotor Motor;

    [Tooltip("Updates and manages kart animations and particle FX")]
    [ReadOnly]
    public RacerAnimator Animator;
    #endregion

    [Header("Other Core Components")]
    #region
    [Tooltip("Collider used for functional collisions, such as floors and walls")]
    [ReadOnly]
    public SphereCollider SphereCol;

    [Tooltip("Rigidbody used for force calculations")]
    [ReadOnly]
    public Rigidbody RigidBD;
    #endregion

    [Header("Interface Values")]
    #region
    [Tooltip("Whether or not the kart is ready to begin the race")]
    [ReadOnly]
    public bool Ready;

    [Tooltip("Whethor or not the kart can be a target of tracking items")]
    [ReadOnly]
    public bool canBeTarget;

    [Header("Scene References")]
    [SerializeField] SceneField m_lobbyScene = null;
    [SerializeField] SceneField m_customizerScene = null;
    #endregion

    [Header("Player Stats")]
    [SerializeField] PlayerStats m_playerStats;

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

    // PRIVATE FIELDS //
    #region
    Checkpoint[] m_allTrackPieces = new Checkpoint[0];
    List<Checkpoint> m_passedTracks = new List<Checkpoint>();
    int m_remoteCheckpointsPassed = 0;
    Vector3 m_lastCheckpoint = new Vector3();
    bool m_raceComplete = false;
    int m_racersCompleted = 0;
    int m_positionInRace = -1;
    #endregion

    // READABLES //
    #region
    public bool IsLocal
    {
        get
        {
            if (!PhotonNetwork.IsConnected)
                return true;
            if (photonView.IsMine)
                return true;
            return false;
        }
    }
    public bool CanBeTarget { get { return canBeTarget; } set { canBeTarget = value; } }
    public int CheckpointsInTrack { get { return m_allTrackPieces.Length; } }
    public int CheckpointsPassed { get { return IsLocal ? m_passedTracks.Count : m_remoteCheckpointsPassed; } }
    public int PositionInRace { get { return m_positionInRace; } }
    public int LapsCompleted { get; private set; }
    public bool RaceCompleted { get { return m_raceComplete; } }
    public static RacerCore LocalRacer
    {
        get
        {
            foreach (RacerCore _player in FindObjectsOfType<RacerCore>())
                if (_player.IsLocal)
                    return _player;

            return null;
        }
    }
    #endregion

    // DELEGATES //
    #region
    public delegate void OnLapCompleteAction(int _lapsCompleted);
    public OnLapCompleteAction OnLapComplete;

    public delegate void OnFinalLapStartedAction();
    public OnFinalLapStartedAction OnFinalLapStarted;

    public delegate void OnRaceCompletedAction();
    public OnRaceCompletedAction OnRaceCompleted;

    public delegate void OnPlacementChangedAction(int _newPlace);
    public OnPlacementChangedAction OnPlacementChanged;

    public delegate void OnPlacementFinalizedAction(int _place);
    public OnPlacementFinalizedAction OnPlacementFinalized;
    #endregion

    // DEFAULT METHODS //
    #region
    new void OnEnable()
    {
        base.OnEnable();

        OnRaceCompleted += FinishRace;
    }
    new void OnDisable()
    {
        base.OnEnable();

        OnRaceCompleted -= FinishRace;
    }
    void OnValidate()
    {
        AssignDefaults();
    }
    void Awake()
    {
        AssignDefaults();
    }
    void Start()
    {
        //Controller.Disabled = true;

        LapsCompleted = 0;

        Scene _currentScene = SceneManager.GetActiveScene();
        
        if (_currentScene.name != m_lobbyScene.SceneName && _currentScene.name != m_customizerScene.SceneName)
            GetAllTrackPieces();
    }
    void Update()
    {
        if (m_raceComplete)
            return;

        RacerCore[] _racers = FindObjectsOfType<RacerCore>();

        // We assume we are in the highest position (1) by default, plus however many karts have reported finishing the race
        int _positionInRace = 1 + m_racersCompleted;

        float _distanceFromCheckpoint = Vector3.Distance(m_lastCheckpoint, transform.position);

        foreach (RacerCore _racer in _racers)
        {
            // Skip ourselves and any racers that have completed the race
            if (_racer == this || _racer.RaceCompleted)
                continue;

            if (_racer.CheckpointsPassed > CheckpointsPassed || _racer.LapsCompleted > LapsCompleted)
                _positionInRace++;
            else if (_racer.CheckpointsPassed == CheckpointsPassed && _racer.LapsCompleted == LapsCompleted)
                if (_distanceFromCheckpoint > Vector3.Distance(m_lastCheckpoint, _racer.transform.position))
                    _positionInRace++;
        }

        if (m_positionInRace != _positionInRace && OnPlacementChanged != null)
            OnPlacementChanged.Invoke(_positionInRace);

        m_positionInRace = _positionInRace;
    }
    #endregion
    
    // GETTER METHODS //
    #region
    public static RacerCore GetRacerByID(int _id)
    {
        foreach (RacerCore _racer in FindObjectsOfType<RacerCore>())
            if (_racer.photonView.ViewID == _id)
                return _racer;

        return null;
    }
    #endregion

    // EXECUTABLE METHODS //
    #region
    /// <summary>
    /// Sets references to the default components for the RacerCore
    /// </summary>
    void AssignDefaults()
    {
        Controller = GetComponent<RacerController>();
        Items = GetComponent<RacerItems>();
        Motor = GetComponent<RacerMotor>();
        Animator = GetComponent<RacerAnimator>();
        SphereCol = GetComponent<SphereCollider>();
        RigidBD = GetComponent<Rigidbody>();
    }
    /// <summary>
    /// Gets all modular track instances from the scene and populates the appropriate lists
    /// </summary>
    void GetAllTrackPieces()
    {
        m_allTrackPieces = FindObjectsOfType<Checkpoint>();
        m_passedTracks = new List<Checkpoint>();
    }
    /// <summary>
    /// Attempts to pass a given modular track piece. Returns true if this is a new checkpoint pass.
    /// </summary>
    /// <param name="_checkpoint"></param>
    public bool PassCheckpoint(Checkpoint _checkpoint)
    {
        if (m_passedTracks.Contains(_checkpoint))
            return false;

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

        m_passedTracks.Add(_checkpoint);

        if (m_debugChannel)
            m_debugChannel.Raise(this, "Passed checkpoint #" + CheckpointsPassed);

        return true;
    }
    /// <summary>
    /// Checks if the player has a sufficient number of checkpoints to complete a lap. Returns true if a lap has been completed.
    /// </summary>
    /// <returns></returns>
    public bool CheckLap()
    {
        if (CheckpointsPassed < CheckpointsInTrack)
            return false;

        LapsCompleted++;

        if (m_debugChannel)
            m_debugChannel.Raise(this, "Racer has completed " + LapsCompleted + " laps.");

        m_passedTracks.Clear();

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

        if (OnLapComplete != null)
            OnLapComplete.Invoke(LapsCompleted);

        TrackManagerScript _tm = FindObjectOfType<TrackManagerScript>();

        if (!_tm)
            return true;

        if (_tm.LapsInRace == LapsCompleted + 1 && OnFinalLapStarted != null)
        {
            if (m_debugChannel)
                m_debugChannel.Raise(this, "Final Lap!");
                
            OnFinalLapStarted.Invoke();
        }

        if (_tm.LapsInRace <= LapsCompleted && OnRaceCompleted != null)
            OnRaceCompleted.Invoke();

        return true;
    }
    /// <summary>
    /// Finishes the race for this player
    /// </summary>
    void FinishRace()
    {
        Controller.DisableKart();
        m_raceComplete = true;

        if (m_debugChannel)
            m_debugChannel.Raise(this, "Local racer has finished the race!");

        TimeDisplayHandler _tdm = FindObjectOfType<TimeDisplayHandler>();

        if (m_playerStats && _tdm)
            m_playerStats.ComparePlayerTimes(SceneManager.GetActiveScene().name, _tdm.BestTime, _tdm.FinalRaceTime);

        if (PhotonNetwork.IsConnected)
        {
            if (m_debugChannel)
                m_debugChannel.Raise(this, "Local racer finished in " + m_positionInRace + " place!");


            PlayfabCloudScriptAPI.SetRaceComplete();

            switch (m_positionInRace)
            {
                case (1):
                    PlayfabCloudScriptAPI.SetRaceStats("1st Place Wins");
                    break;
                case (2):
                    PlayfabCloudScriptAPI.SetRaceStats("2nd Place Wins");
                    break;
                case (3):
                    PlayfabCloudScriptAPI.SetRaceStats("3rd Place Wins");
                    break;
            }

            if (OnPlacementFinalized != null)
                OnPlacementFinalized.Invoke(m_positionInRace);

            photonView.RPC("RacerComplete", RpcTarget.Others, photonView.ViewID);
        }
        else if (OnPlacementFinalized != null)
            OnPlacementFinalized.Invoke(-1);
    }
    public void AddFinished()
    {
        m_raceComplete = true;
        m_racersCompleted++;
    }
    #endregion

    // RPC / PHOTON //
    #region
    /// <summary>
    /// Serializes the Ready value over the network
    /// </summary>
    /// <param name="stream"></param>
    /// <param name="info"></param>
    public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
    {
        if (stream.IsWriting)
        {
            if (photonView && photonView.IsMine)
            {
                stream.SendNext(Ready);
                stream.SendNext(m_positionInRace);
            }
        }
        else
        {
            if (photonView && !photonView.IsMine)
            {
                Ready = (bool)stream.ReceiveNext();
                m_positionInRace = (int)stream.ReceiveNext();
            }
        }
    }
    [PunRPC]
    void IncreaseCheckpoint()
    {
        m_remoteCheckpointsPassed++;

        if (m_debugChannel)
            m_debugChannel.Raise(this, "Player " + photonView.ViewID + " has passed " + m_remoteCheckpointsPassed + " checkpoints.");
    }
    [PunRPC]
    void IncreaseLap()
    {
        LapsCompleted++;

        m_remoteCheckpointsPassed = 0;

        if (m_debugChannel)
            m_debugChannel.Raise(this, "Player " + photonView.ViewID + " has completed " + LapsCompleted + " laps.");

    }
    /// <summary>
    /// Alerts other players when this player has completed the race
    /// </summary>
    /// <param name="_playerID"></param>
    [PunRPC]
    public void RacerComplete(int _playerID)
    {
        if (LocalRacer)
            LocalRacer.AddFinished();
    }
    #endregion
}
