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

// Custom class used in Leaderboard functions
public class ButtonInfo
{
    public TextMeshProUGUI name;        // Name text holder on the place button
    public TextMeshProUGUI placeNumber; // Place text holder on the place button
    public Vector3 goalPosition;        // Stores where our button is on screen
    public RectTransform rectTransform; // The anchored position of the button
    public KartController kart;         // Kart associated with this button
    public int position;                // The current stored position of this racer
    public bool active;                 // Whether or not the racer is active
    public bool moving;                 // Whether or not the button is moving
    public bool completed;              // Whether or not the racer has completed the race

    public ButtonInfo(TextMeshProUGUI name, TextMeshProUGUI placeNumber, Vector3 goalPosition, RectTransform rectTransform, KartController kart = null, int position = 0, bool active = false, bool moving = false, bool completed = false)
    {
        this.name = name;
        this.placeNumber = placeNumber;
        this.goalPosition = goalPosition;
        this.rectTransform = rectTransform;
        this.kart = kart;
        this.position = position;
        this.active = active;
        this.moving = moving;
        this.completed = completed;
    }
}

public class LeaderboardDisplay : MonoBehaviour
{
    [Header("Spectator Info")]
    [ReadOnly]
    public SpectatorController spectatorController; // Spectator controller reference

    [Header("Leaderboard Data")]
    [ReadOnly]
    public bool LeaderboardActive = false;          // Is the leaderboard active?
    [ReadOnly]
    public bool RaceActive = false;                 // Is the race active?
    [ReadOnly]
    public List<KartController> kartIndex;
    [ReadOnly]
    public List<Button> buttonIndex;                    // Holds all of our buttons
    Dictionary<Button, ButtonInfo> buttonList = new Dictionary<Button, ButtonInfo>();   // Associates each button with its information class

    [Header("Leaderboard Settings")]
    public bool allowLeaderboardUpdates = true;     // Are we currently allowing the leaderboard to update?
    [Range(0.01f, 5)]
    public float updateFrequency = 0.5f;            // Amount of time between leaderboard updates
    [Range(0.2f, 1)]
    public float buttonMoveTime = 0.2f;             // Amount of time it takes for a button to move into place
    public List<Color> placeColors;
    [ReadOnly]
    public float buttonSpacing = 28f;               // Vertical space between our buttons
    [ReadOnly]
    public float verticalScreenOffset = 140f;        // Minimum button height on the screen
    public float horizontalScreenOffset = 72.4f;


    void Start()
    {
        InitializeButtons();        // Create our dictionary of buttons
        InitializeKartList();
        LeaderboardActive = false;  // The leaderboard is not active at the start of the game
    }

    private void Update()
    {

        // DISABLE AND HIDE BUTTONS WITHOUT A KART //
        for (int i = 0; i < buttonIndex.Count; i++)
        {
            ButtonInfo buttonInfo = buttonList[buttonIndex[i]];

            // DISABLE AND HIDE EMPTY BUTTONS //
            if (!buttonInfo.kart)   // If our button does not have a kart...
            {
                if (buttonInfo.active)  // ...but the button is active...
                {
                    SetButtonToInactive(buttonIndex[i]);    // ...deactivate the button
                }
                else if (!buttonInfo.moving)    // Else if the button is inactive and not moving...
                {
                    //buttonInfo.rectTransform.anchoredPosition = buttonInfo.goalPosition;   // Lock its position above the screen

                }
                continue;   // Move on to the next button
            }
        }
    }

    void OnEnable()
    {
        if (!spectatorController)   // If we don't have a spectator controller...
        {
            // ...try to get one
            spectatorController = SpectatorController.FindObjectOfType<SpectatorController>();

            // If we still can't find one, disable the leaderboard
            if (!spectatorController) { Debug.LogError("Clicked on a button, but can't find the spectator controller!"); return; }
        }

        // Start our Leaderboard Update routine whenever the leaderboard is enabled
        StartCoroutine("UpdateLeaderboard");
    }
    
    void OnDisable()
    {
        StopCoroutine("UpdateLeaderboard");     // Shut down the routine
        LeaderboardActive = false;              // Set the active value to false
    }
    
    // Set up our button list and info
    void InitializeButtons()
    {
        buttonIndex.Clear();    // Clear the list of buttons
        buttonList.Clear();     // Clear the list of button information

        for (int i = 0; i < transform.childCount; i++)                      // Loop through each button in our child list
        {
            //if (i > buttonIndex.Count) { Debug.LogError("Too many buttons!"); return; }    // Alert us if there are too many buttons in the canvas

            buttonIndex.Add(transform.GetChild(i).GetComponent<Button>());  // Add each button to our index

            if (buttonIndex[i])   // If a button was successfully found...
            {
                TextMeshProUGUI name = transform.GetChild(i).Find("Name").GetComponent<TextMeshProUGUI>();          // Get its name field
                TextMeshProUGUI placeNumber = transform.GetChild(i).Find("Place").GetComponent<TextMeshProUGUI>();  // Get its place field
                RectTransform rectTransform = buttonIndex[i].GetComponent<RectTransform>();

                if (name && placeNumber && rectTransform)                   // If we found both fields...
                {
                    name.text = "No Racer";                                 // ...set the default text for the name field
                    placeNumber.text = "-";                                 // Set the default text for the place field

                    ButtonInfo newButtonInfo = new ButtonInfo(name, placeNumber, new Vector3(72.4f, 471f), rectTransform);   // Create a new set of button information for this button

                    buttonList.Add(buttonIndex[i], newButtonInfo);          // Add the new button info to the list, associated with our button
                    buttonIndex[i].interactable = false;                    // Set the button to inactive by default
                }
                else                                                        // If we were missing one or both of the two fields...
                {
                    Debug.LogError("Button #" + i + " failed to initialize. Does it have a Name and Place?");   // Let us know there's a problem
                }
            }
        }
    }
    
    // Assigns a look target to our spectator
    void ButtonClick(ButtonInfo thisButtonInfo)
    {
        if (!spectatorController)                                                               // If we don't have a spectator controller...
        {
            spectatorController = SpectatorController.FindObjectOfType<SpectatorController>();  // ...try to find one

            // If we still don't have one, log an error and return
            if (!spectatorController) { Debug.LogError("Clicked on a button, but can't find the spectator controller!"); return; }
        }

        spectatorController.SetCurrentTarget(thisButtonInfo.kart.transform);                    // Set the current spectator target to the kart
    }

    // Checks our buttons for updated values and positions
    void CheckButtons()
    {
        int noPlaces = 0;                                                                           // Tracks the number of racers with no place

        for (int i = 0; i < buttonIndex.Count; i++)                                                // For each button...
        {
            int buttonNumber = i + 1;                                                               // ...get a button number for debugging purposes
            Button button = buttonIndex[i];                                                         // Get the button
            ButtonInfo buttonInfo = buttonList[button];                                             // Get the button info

            // !!! Change this later to recursively create button information for any buttons missing them
            if (buttonInfo == null) { Debug.LogError("No button info found for button #" + buttonNumber); continue; }


            // DISABLE UPDATES FOR FINISHED RACERS //
            if (buttonInfo.completed) { continue; }                                                 // If this button is marked as having completed the race, skip it!


            // SKIP EMPTY BUTTONS //
            if (!buttonInfo.kart) { continue; }

            // ACTIVATE BUTTON //
            if (!button.interactable) { button.interactable = true; }   // If our button isn't interactable, make it so!

            // MOVE PRE-RACE BUTTONS INTO POSITION //
            if (SceneManager.GetActiveScene().name == "RKR-Lobby" && SceneManager.GetActiveScene().name == "Esports-Lobby") // If we are in a non-racing scene...
            {
                if (!buttonInfo.active)   // ...and the button hasn't been activated yet...
                {
                    SetButtonToDefault(button, i);  // ...set the buttons in a default order
                }
                continue;   // Move on to the next button
            }


            // SET ALL RACERS TO ACTIVE IF WE HAVE A KART AND ARE IN A RACE //
            if (!buttonInfo.active) { buttonInfo.active = true; }


            // MOVE LIVE BUTTONS INTO POSITION //
            if (buttonInfo.position != buttonInfo.kart.positionInRace)  // If our button's position does not match the kart's place value...
            {
                if (buttonInfo.kart.positionInRace < 1) // If our button's position is less than 1...
                {
                    buttonInfo.position = kartIndex.Count - noPlaces++;  // ...set it to the bottom of the list
                }
                else
                {
                    buttonInfo.position = buttonInfo.kart.positionInRace;   // Otherwise, set it to the racer's position
                }

                buttonInfo.goalPosition = GetCorrectScreenPosition(button); // During a race, we derive our button's position from their place in the race

                buttonInfo.placeNumber.text = buttonInfo.position.ToString();   // Update the place text to be our position value
                StartCoroutine(MoveButton(button, buttonInfo.goalPosition));    // Move the button into position
            }
            else
            {
                if (!buttonInfo.moving) // If the button isn't moving...
                {
                    //buttonInfo.rectTransform.anchoredPosition = buttonInfo.goalPosition;    // ...snap the button to its desired position
                }
            }

            if (buttonInfo.kart.complete)                                       // If our kart has completed the race...
            {
                buttonInfo.completed = true;                                    // Lock our button so that it will no longer move
            }
        }
    }

    // Returns where the button should be on our screen given its position
    Vector3 GetCorrectScreenPosition(Button button)
    {
        ButtonInfo buttonInfo = buttonList[button];
        
        float correctVertical;

        // If the button's position is not zero...
        if (buttonInfo.position != 0)
        {
            // Set our goal to be the minimum height, plus the remainder of our racer count and the kart's position multiplied by a spacing value
            correctVertical = 471 - verticalScreenOffset - (buttonInfo.position * buttonSpacing);
        }
        else
        {
            correctVertical = 471;
        }

        return new Vector3(horizontalScreenOffset, correctVertical);
    }

    // Deactivates a button
    void SetButtonToInactive(Button button)
    {
        ButtonInfo buttonInfo = buttonList[button];                                 // Get the button's info

        button.interactable = false;                                                // Make it uninteractable
        buttonInfo.position = 0;                                                    // Set its position to zero
        buttonInfo.name.text = "No Racer";                                          // Change its name text
        buttonInfo.placeNumber.text = "-";                                          // Change its place text
        buttonInfo.active = false;                                                  // Set the button as inactive

        buttonInfo.goalPosition = GetCorrectScreenPosition(button);                 // During a race, we derive our button's position from their place in the race

        StartCoroutine(MoveButton(button, buttonInfo.goalPosition));                // Move the button off-screen
    }

    // Sets a button to a generic value (mostly for offline racers)
    void SetButtonToDefault(Button button, int i)
    {
        ButtonInfo buttonInfo = buttonList[button];                                 // Get the button's info
        buttonInfo.active = true;                                                   // Set the button as active

        if (buttonInfo.position != kartIndex.Count - i)                                      // Reset our button position to its default position
        {
            buttonInfo.position = kartIndex.Count - i;                                       // Set the button position value to the number of racers minus our loop value
            buttonInfo.goalPosition = GetCorrectScreenPosition(button);             // During a race, we derive our button's position from their place in the race

            buttonInfo.placeNumber.text = buttonList[button].position.ToString();   // Update the position text of the button
            StartCoroutine(MoveButton(button, buttonInfo.goalPosition));            // Move the button
        }
    }

    void InitializeKartList()
    {
        kartIndex.Clear();
    }

    // Updates our kart button assignments when the number of racers in the scene changes
    void UpdateKarts()
    {
        int place = 0;  // Track the default places of our racers

        List<KartController> newList = new List<KartController>();

        for (int i = kartIndex.Count - 1 ; i > 0; i--)
        {
            if (!kartIndex[i])
            {
                kartIndex.RemoveAt(i);
                //Debug.Log("Removed missing kart #" + (i + 1));
                continue;
            }
            newList.Add(kartIndex[i]);
        }

        kartIndex = newList;

        for (int i = 0; i < buttonList.Count; i++)  // For each button in the game...
        {

            place++;    // Incriment our default place value
            Button button = buttonIndex[i]; // ...get the button...
            ButtonInfo buttonInfo = buttonList[button]; // ...and its button info
            //Debug.Log("Checking " + button.transform.name + "!");

            if (buttonInfo.kart) { place++; buttonInfo.placeNumber.text = (place).ToString(); continue; }  // If this button still has a kart, skip it

            KartController newKart = FindNewKart(); // Find a new kart

            if (!newKart)   // If a new kart cannot be found...
            {
                //Debug.Log("No new karts to assign! Resetting button values.");
                buttonInfo.kart = null;     // ...set the button to have no kart
                buttonInfo.position = 0;    // Set its place value to 0
                continue;   // Go to the next button
            }

            buttonInfo.kart = newKart;  // Update our button info
            kartIndex.Add(newKart); // Add the kart to our index

            string racerName = "Racer"; // Set the default name for each racer

            if (buttonInfo.kart.photonView) // If our kart is connected to Photon...
            {
                //Debug.Log("Kart is connected to Photon");
                object prop;                                    // ...get the Display Name property of the player
                buttonInfo.kart.photonView.Owner.CustomProperties.TryGetValue("DisplayName", out prop);
                if (prop != null) { racerName = prop as string; }   // If we found one, return the property as a string and overwrite the racer name
            }


            buttonInfo.name.text = racerName;   // Set the button text to the racer's name
            buttonInfo.placeNumber.text = (place).ToString();   // Set their place (by default) to the current racer count

            button.onClick.RemoveAllListeners();    // Remove any existing listener events from the button
            button.onClick.AddListener(() => ButtonClick(buttonInfo));  // Add a new listener using our button's kart
            button.GetComponent<RectTransform>().anchoredPosition = new Vector3(72.4f, 471f);  // Move the button to its default location
        }
    }

    KartController FindNewKart()
    {
        KartController[] currentKarts = KartController.FindObjectsOfType<KartController>(); // Get all of the karts in our scene

        foreach (KartController kart in currentKarts)    // For each kart in our game...
        {
            if (kartIndex.Contains(kart)) { continue; } // If we already have the kart recorded, skip it

            return kart;
        }
        return null;
    }

    // This coroutine updates our leaderboard once every second while active
    IEnumerator UpdateLeaderboard()
    {
        // This makes sure that only one Leaderboard routine is running at any given time
        if (LeaderboardActive) { Debug.LogError("Leaderboard is already active!"); yield break; }
        LeaderboardActive = true;                   // Let us know that the leaderboard is active

        //int count = 0;                              // Keep track of how many times we've looped (for debugging purposes only)

        while (true)                                // Loop forevar
        {
            if (allowLeaderboardUpdates)            // If our leaderboard can update...
            {
                // Print how many times we've looped to the console

                // ...get all karts currently in our scene
                KartController[] kartControllers = KartController.FindObjectsOfType<KartController>();

                if (kartControllers.Length != kartIndex.Count)   // If the number of karts has changed since we last checked...
                {
                    if (kartControllers.Length < kartIndex.Count)
                    {
                        Debug.Log("Player has left the game...");
                    }
                    else
                    {
                        Debug.Log("Player has joined the game!");
                    }

                    UpdateKarts();       // ...update the karts list
                }

                CheckButtons(); // Check if any buttons need to be updated
            }
            yield return new WaitForSeconds(updateFrequency);    // Suspent for the value of our update frequency
        }
    }
    
    // Moves a button to a new position on the leaderboard
    IEnumerator MoveButton(Button button, Vector3 endPosition)
    {
        ButtonInfo buttonInfo = buttonList[button];
        
        if (buttonInfo.moving) { yield break; }                             // Prevent the button from attempting to move if it is already in motion

        buttonInfo.moving = true;                                           // Set ourselves to moving
 
        float currentTime = 0;                                              // Keeps track of the amount of time we've been moving
        Vector3 startPosition = buttonInfo.rectTransform.anchoredPosition;  // Our starting position

        while (currentTime <= buttonMoveTime)                               // While our motion is not completed...
        {
            currentTime += Time.deltaTime;                                  // Add the amount of time between our frames 
            float normalizedValue = currentTime / buttonMoveTime;           // Get the percentage of time that has passed over our max move time
            Mathf.Clamp(normalizedValue, 0f, buttonMoveTime);               // Clamp the completion value between 0 and 1

            buttonInfo.rectTransform.anchoredPosition = Vector3.Lerp(startPosition, endPosition, normalizedValue); // Lerp the position of our button according to our normalized value

            yield return null;                                              // Return to the top of the While loop
        }

        buttonInfo.rectTransform.anchoredPosition = endPosition;            // Snap our button position to the endPosition at the end of its movement to correct for float imperfections
        buttonInfo.moving = false;                                          // Set our button to be no longer moving
        yield return null;
    }

    /*
    // Jigs a button if it is currently selected
    IEnumerator JigPlace (Button button)
    {
        float currentTime = 0f;
        float pulseRate = 64f;
        float sizeMultiplier = 1.5f;

        while (true)
        {
            if (buttonList[button].position < 4)
            {
                currentTime += Time.deltaTime;
                float timeRemaining = (pulseRate / buttonList[button].position) - currentTime;
                if (timeRemaining <= 0)
                {
                    currentTime = -timeRemaining;
                }

                buttonList[button].placeNumber.transform.localScale = Vector3.one + (Vector3.one * sizeMultiplier) * Mathf.Sin(currentTime/( pulseRate / buttonList[button].position));
            }
            else
            {
                currentTime = 0f;
                buttonList[button].placeNumber.transform.localScale = Vector3.one;
            }
        }
    }
    */
}
