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

public class TimeDisplayHandler : MonoBehaviour {

    [Header("Local Player")]
	[SerializeField] RacerCore m_localPlayer;
    
    [Header("Track Manager")]
	[SerializeField] TrackManagerScript m_trackManager;

    [Header("Race Again Button")]
	[SerializeField] GameObject m_raceAgainButton;

    [Header("Text Displays")]
    [SerializeField] TextMeshProUGUI currentLapTime;
    [SerializeField] TextMeshProUGUI lastLapTime;
    [SerializeField] TextMeshProUGUI bestLapTime;
    [SerializeField] TextMeshProUGUI totalRaceTime;
    [SerializeField] TextMeshProUGUI lapCount;
    [SerializeField] TextMeshProUGUI racePlacement;

    [Header("Persistent Values")]
    [SerializeField] float startTimer = 3f;
    [SerializeField] float raceStartTime = 0f;
    [SerializeField] float raceTime = 0f;
    [SerializeField] float photonStartTime = 0f;
    [SerializeField] float thisLapTime = 0f;
    [SerializeField] float lastTime = 0f;
    [SerializeField] float bestTime = 0f;
    [SerializeField] float raceCompleteTime = 0f;

    [Header("Customizable Options")]
	[SerializeField] List<Color> placeColors;

    [Header("Debug")]
    [SerializeField] bool m_enableTesting = false;
    [SerializeField] DebugChannelSO m_debugChannel;

    // READABLES //
    #region
    public float PhotonNetworkTime { get { return PhotonNetwork.IsConnected ? (float)PhotonNetwork.Time : 0; } }
    public float RaceTimeElapsed { get { return (PhotonNetwork.IsConnected && photonStartTime != 0 ? PhotonNetworkTime - photonStartTime : raceTime); } }
    public float BestTime { get { return bestTime; } }
    public float FinalRaceTime { get { return raceCompleteTime; } }
    #endregion

    // PRIVATE VALUES //
    #region
    bool m_started = false;
    bool m_complete = false;
    string m_sceneName;
    bool m_initialized = false;
    CountdownDisplay m_countdownDisplay;
    #endregion

    // DELEGATES //
    #region
    public delegate void OnRaceStartedAction();
    public OnRaceStartedAction OnRaceStarted;
    #endregion


    void OnEnable()
    {
        if (m_localPlayer)
        {
            if (m_debugChannel)
                m_debugChannel.Raise(this, "Subscribed to local player! Awaiting lap updates.");

            m_localPlayer.OnRaceCompleted += RaceOver;
            m_localPlayer.OnLapComplete += UpdateLap;
        }
        
        if (m_countdownDisplay)
            m_countdownDisplay.OnCountdownEnd += StartTimer;
    }
    void OnDisable()
    {
        if (m_localPlayer)
        {
            if (m_debugChannel)
                m_debugChannel.Raise(this, "Unsubscribed from local player!");

            m_localPlayer.OnRaceCompleted -= RaceOver;
            m_localPlayer.OnLapComplete -= UpdateLap;
        }
        
        if (m_countdownDisplay)
            m_countdownDisplay.OnCountdownEnd -= StartTimer;
    }
    // Start is called before the first frame update
    void Start ()
    {
        m_sceneName = SceneManager.GetActiveScene().name;
        m_trackManager = FindObjectOfType<TrackManagerScript>();
        m_countdownDisplay = FindObjectOfType<CountdownDisplay>();

        m_started = false;
        m_complete = false;
        
		if (PhotonNetwork.IsConnected && !m_sceneName.Contains("Lobby") && !m_sceneName.Contains("Custom"))
			racePlacement.gameObject.SetActive (true);
        else
			racePlacement.gameObject.SetActive (false);

        raceTime = 0;
        thisLapTime = 0;
        raceStartTime = 0;

        if (m_debugChannel)
            m_debugChannel.Raise(this, "Local start time set to " + raceStartTime);

        photonStartTime = -1;

        if (PhotonNetworkTime != 0)
        {
            photonStartTime = PhotonNetworkTime;

            if (m_debugChannel)
                m_debugChannel.Raise(this, "Photon start time set to " + photonStartTime);
        }

        if (!m_localPlayer)
            StartCoroutine(TryGetPlayer());

        if (m_trackManager)
            UpdateLap(0);

        if (m_countdownDisplay)
            m_countdownDisplay.OnCountdownEnd += StartTimer;
    }

    // Update is called once per frame
    void Update ()
    {
        if (m_enableTesting)
            return;
        else if (m_complete || !m_started)
            return;

        // If we are connected to Photon, photon start time is not initialized yet, and 
        if (PhotonNetwork.IsConnected && photonStartTime == -1 && PhotonNetworkTime != 0)
        {
            photonStartTime = PhotonNetworkTime - raceTime;

            if (m_debugChannel)
                m_debugChannel.Raise(this, "Photon connected! Updated the start time to be " + photonStartTime);
        }

        string _sceneName = SceneManager.GetActiveScene().name;

        if (!_sceneName.Contains("Testing") && !_sceneName.Contains("Lobby") && !_sceneName.Contains("Custom"))
        {
			if (currentLapTime.gameObject.activeInHierarchy)
            {
                // Create a temporary field for the time between this and previous updates
                float _timeDelta = 0;

                // If the photon network is connected
                if (PhotonNetwork.IsConnected)
                {
                    // If the Photon start time has not been logged yet...
                    if (photonStartTime == 0 && (float)PhotonNetwork.Time != 0)
                    {
                        // Get the start time by taking the current photon time and subtracting the amount of time that has passed in the race
                        photonStartTime = PhotonNetworkTime - raceTime;
                    }

                    // Set the time delta as the remainder of the current server time minus the sum of the start and elapsed race time
                    _timeDelta = PhotonNetworkTime - (photonStartTime + raceTime);
                }
                // If we are not on a server, just use deltaTime
                else
                    _timeDelta = Time.deltaTime;

                // Add the update value to the current and total lap time
                thisLapTime += _timeDelta;
                raceTime += _timeDelta;

                currentLapTime.SetText(FormatTime("Current Lap Time:", thisLapTime));

                //if (m_debugChannel && m_debugChannel.EnableDebugMessages)
                //    Debug.Log("[Time Display Handler] Current lap time: " + thisLapTime + ", " + raceTime);
			}
		}
    }

    void StartTimer()
    {
        m_started = true;
    }

    void UpdateLap(int _lapsCompleted)
    {
        if (m_sceneName.Contains("Testing") || m_sceneName.Contains("Lobby") || m_sceneName.Contains("Custom"))
            return;

        if (m_debugChannel)
            m_debugChannel.Raise(this, "Updating lap display.");

        lapCount.SetText(ConvertLapToString(_lapsCompleted + 1));

        if (_lapsCompleted == 0)
            StartCoroutine(FlashTimes());

        HandleLapDisplay(raceTime, m_initialized);

        if (!m_localPlayer)
            return;

        if (m_localPlayer.LapsCompleted >= m_trackManager.LapsInRace)
        {
            raceCompleteTime = raceTime;

            if (FindObjectsOfType<RacerCore>().Length > 1)
                totalRaceTime.SetText(FormatTime("Race Complete Time:", raceCompleteTime));

            if (!m_raceAgainButton.activeInHierarchy)
                m_raceAgainButton.SetActive(true);
        }

        StartCoroutine(FlashTimes());
    }

    void RaceOver()
    {
        if (currentLapTime)
            currentLapTime.gameObject.SetActive(false);
    }

	void HandleLapDisplay (float currentTime, bool _initialized = false)
    {
		if (_initialized)
        {
			lastTime = thisLapTime;
			
			lastLapTime.SetText (FormatTime ("Last Lap Time:", lastTime));
			
			if ((bestTime != 0f && bestTime > thisLapTime) || bestTime == 0f)
            {
				bestLapTime.SetText (FormatTime ("Best Lap Time:", lastTime));
				bestTime = thisLapTime;
			}
		}
        else if (bestTime != 0)
				bestLapTime.SetText (FormatTime ("Best Lap Time:", bestTime));

        if (currentLapTime.gameObject.activeInHierarchy && m_started)
            currentLapTime.SetText("0:00:00");

        thisLapTime = 0;
    }

	string ConvertLapToString (int _currentLap)
    {
        if (!m_trackManager)
        {
            if (m_debugChannel)
                m_debugChannel.Raise(this, "Player has started lap #" + _currentLap + ". Updating UI to match.");

            return "Lap " + _currentLap + "/#";
        }
        
        
		if (_currentLap > m_trackManager.LapsInRace)
        {
            _currentLap = m_trackManager.LapsInRace;

            if (m_debugChannel)
                m_debugChannel.Raise(this, "Player has completed lap #" + _currentLap + " out of " + m_trackManager.LapsInRace);
        }
        else if (m_debugChannel)
            m_debugChannel.Raise(this, "Player has started lap #" + _currentLap + " out of " + m_trackManager.LapsInRace);

        string _finalDisplay = "Lap " + _currentLap.ToString() + "/" + m_trackManager.LapsInRace.ToString();

        if (m_debugChannel)
            m_debugChannel.Raise(this, _finalDisplay);
        
        return _finalDisplay;
	}

	string FormatTime (string timeStampText, float time) 
    {
		int intTime = (int)time;
		int minutes = (int)time / 60;
		int seconds = (int)time % 60;
		int milliseconds = (int)(time * 1000) % 1000;
		string timeText = timeStampText + "\n" + string.Format ("{0:00}:{1:00}:{2:000}", minutes, seconds, milliseconds);
		return timeText;
	}
    // COROUTINES //
    #region
    IEnumerator TryGetPlayer()
    {
        while (!m_localPlayer)
        {
            m_localPlayer = RacerCore.LocalRacer;

            yield return null;
        }

        if (m_debugChannel)
            m_debugChannel.Raise(this, "Subscribed to local player! Awaiting lap updates.");

        m_localPlayer.OnRaceCompleted += RaceOver;
        m_localPlayer.OnLapComplete += UpdateLap;

        m_initialized = true;
    }
    IEnumerator FlashTimes()
    {
        for (float t = 0.5f; t < 1; t += Time.deltaTime)
        {
            currentLapTime.color = new Color(currentLapTime.color.r, currentLapTime.color.g, currentLapTime.color.b, t);
            if (lastLapTime.gameObject.activeInHierarchy)
                lastLapTime.color = new Color(lastLapTime.color.r, lastLapTime.color.g, lastLapTime.color.b, t);
            if (bestLapTime.gameObject.activeInHierarchy)
                bestLapTime.color = new Color(bestLapTime.color.r, bestLapTime.color.g, bestLapTime.color.b, t);
            yield return null;
        }

        currentLapTime.color = new Color(currentLapTime.color.r, currentLapTime.color.g, currentLapTime.color.b, 1);
        if (lastLapTime.gameObject.activeInHierarchy)
            lastLapTime.color = new Color(lastLapTime.color.r, lastLapTime.color.g, lastLapTime.color.b, 1);
        if (bestLapTime.gameObject.activeInHierarchy)
            bestLapTime.color = new Color(bestLapTime.color.r, bestLapTime.color.g, bestLapTime.color.b, 1);

        yield return new WaitForSeconds(2);

        for (float t = 1; t > 0.5f; t -= Time.deltaTime)
        {
            currentLapTime.color = new Color(currentLapTime.color.r, currentLapTime.color.g, currentLapTime.color.b, t);
            if (lastLapTime.gameObject.activeInHierarchy)
                lastLapTime.color = new Color(lastLapTime.color.r, lastLapTime.color.g, lastLapTime.color.b, t);
            if (bestLapTime.gameObject.activeInHierarchy)
                bestLapTime.color = new Color(bestLapTime.color.r, bestLapTime.color.g, bestLapTime.color.b, t);
            yield return null;
        }

        currentLapTime.color = new Color(currentLapTime.color.r, currentLapTime.color.g, currentLapTime.color.b, 0.5f);
        if (lastLapTime.gameObject.activeInHierarchy)
            lastLapTime.color = new Color(lastLapTime.color.r, lastLapTime.color.g, lastLapTime.color.b, 0.5f);
        if (bestLapTime.gameObject.activeInHierarchy)
            bestLapTime.color = new Color(bestLapTime.color.r, bestLapTime.color.g, bestLapTime.color.b, 0.5f);

        yield break;
    }
    #endregion
}
