using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Networking;
using Networking.Server;
using Networking;
using UnityEngine.SceneManagement;


public class GameManager : MonoBehaviour
{
    #region Inspector Field
    [Header("Settings")]
    [SerializeField]
    private float AnimationTime;
    [SerializeField]
    private GameModeReference CurrentGameMode;


    [Header("References")]
    [SerializeField]
    [Tooltip("Prefab of character for players to play")]
    private Character characterPrefab;

    [SerializeField]
    private ServerObject server;

    [SerializeField]
    private ClientList ClientList;

    #endregion Inspector Field


    #region Private Variables
    private Dictionary<int, PlayerData> playerData;
    #endregion Private Variables

    #region Read Only
    /// <summary>
    /// Easy access to IEnumerable in playerData so we can Enumerate through it
    /// </summary>
    private IEnumerable<PlayerData> playerDataAsArray { get { return playerData.Values; } }

    /// <summary>
    /// Easy access to GameMode value in CurrentGameMode reference
    /// </summary>
    private GameMode gameMode {get { return CurrentGameMode.Value; } }
    #endregion Read Only

	public GameObject levelInfo;
	public blockSpawn bspawn;

    #region Unity Functions
    public void Awake()
    {
        RegisterHandlers();
        SpawnCharacters();
        ClientList.ForEach(p => p.ChangeScene("ClientScene"));
    }

    private void Start()
    {
        StartCoroutine(displayforSeconds(levelInfo, 5.0f));
        gameMode.GameStart(playerDataAsArray.ToArray());
        StartRound();
    }

    private void Update()
    {
        //This is required so that the server can continue to recieve client messages
        //(it is a unity thing)
        server.ServerUpdate();
    }

    private void OnDisable()
    {
        //Let server know to not send messages this way
        UnRegisterHandlers();
    }
    #endregion Unity Functions

    IEnumerator displayforSeconds(GameObject display, float time)
	{
		display.SetActive (true);
		yield return new WaitForSeconds(time);
		display.SetActive (false);
	}

    private void DoRoundRoutine()
    {
        Debug.Log("Starting Round");
        StartCoroutine(RoundRoutine());
    }

    private void StartRound()
    {
        gameMode.RoundStart(playerDataAsArray.ToArray());
        LogicProtocols.FloatMsg RoundTime = new LogicProtocols.FloatMsg( gameMode.GetRoundTime());
        bspawn.Spawn();
        playerDataAsArray.ForEach(p => p.client.conn.Send(LogicProtocols.SendRoundTime, RoundTime));
    }

    private IEnumerator RoundRoutine()
    {
        playerDataAsArray.ForEach(p => p.recievedList = false);

        //Debug.Log("Doing Round Routine");

        while (playerDataAsArray.Any(p => !p.blockReader.Finished))
        {
            //Debug.Log("One Move");
            foreach (PlayerData player in playerDataAsArray)
            {
                Debug.Log(player.client.Name);
                StartCoroutine(RunOnce(player));
                yield return new WaitUntil(() => player.waiting);
            }
                

            //wait until all players have finished
            //yield return new WaitUntil(() => playerArray.All(p => p.waiting));

            gameMode.FinishedMove(playerDataAsArray.ToArray());
            playerDataAsArray.ForEach(p => p.client.SendScore());

        }

        if (gameMode.isGameOver(playerDataAsArray.ToArray()))
        {
            Debug.Log("Game Over");
            SceneManager.LoadScene("ScoreBoards");
        }

        gameMode.RoundEnd(playerDataAsArray.ToArray());

        foreach (PlayerData player in playerDataAsArray)
        {
            player.blockReader.Reset();
            player.waiting = false;
            player.client.SendInventory();
            player.client.ChangeScene("ClientScene");
        }
        //Debug.Log("Finished Moving");

        StartRound();
    }

    private void SpawnCharacters()
    {
        playerData = new Dictionary<int, PlayerData>();
        Block[] SpawnBlocks = FindObjectsOfType<Block>().Where(p => p.isSpawnable).ToArray();

        int spawnIndex = 0;
        foreach (ClientData client in ClientList)
        {
            Character newChar = Instantiate(characterPrefab);
            Block startingBlock = SpawnBlocks[(spawnIndex++ % ClientList.ConnectedClients.Count)];
            newChar.Initialise(startingBlock, client.Inventory, client.characterAnimal);
            newChar.transform.forward = startingBlock.SpawnDirection.ToVector();
            playerData.Add(client.ID, new PlayerData(newChar,client));
            newChar.ClientLink = client;
			client.playerCharacter = newChar;
        }
    }


    #region Networking Functions

    /// <summary>
    /// Registers functions which should deal with incoming messages from clients
    /// </summary>
    private void RegisterHandlers()
    {
        server.server.RegisterHandler(LogicProtocols.SendLogicList, RecieveLogicList);
    }

    /// <summary>
    /// Clears any functions from server register which the manager would deal with
    /// </summary>
    private void UnRegisterHandlers()
    {
        server.server.UnregisterHandler(LogicProtocols.SendLogicList);
    }

    /// <summary>
    /// Called when server recieves moves from client
    /// </summary>
    /// <param name="msg">messages passed by server</param>
    private void RecieveLogicList(NetworkMessage msg)
    {

        //try to read base message as a logic message
        LogicProtocols.LogicMsg logicMsg;
        if (!msg.TryRead(out logicMsg))
            return;

        //Debug that we have recieved it
        Debug.Log("Recieved function from " + ClientList[msg.conn.connectionId].Name);

        //Update player Data with recieved list
        playerData[msg.conn.connectionId].blockReader.LogicChain = new List<LogicBlock>(logicMsg.elements);
        playerData[msg.conn.connectionId].recievedList = true;

        //if we have recieved all moves start round
        if (playerData.All(p => p.Value.recievedList))
            DoRoundRoutine();
    }

    #endregion Networking Functions

    private IEnumerator RunOnce(PlayerData data)
    {
        data.waiting = false;
        bool blockFinished = false;
        float waitTime;
        while (!blockFinished && !data.blockReader.Finished)
        {
            //Debug.Log(data.client + "Moving once");

            if (data.blockReader.CurrentBlock != null && !data.blockReader.CurrentBlock.hasBeenRemoved)
            {
                data.client.Inventory.Remove(data.blockReader.CurrentBlock);
                data.blockReader.CurrentBlock.hasBeenRemoved = true;
            }

            blockFinished = data.blockReader.Read(data.character, AnimationTime,out waitTime);

            //Debug.Log("Waiting: " + waitTime);
            yield return new WaitForSeconds(waitTime);
            gameMode.OnePlayerMoved(data);
        }
        data.waiting = true;
    }
}

public class PlayerData
{
    public Character character;
    public BlockReader blockReader;
    public ClientData client;

    public bool recievedList;
    public bool waiting;

    public PlayerData(Character character, ClientData client)
    {
        this.character = character;
        this.client = client;
        blockReader = new BlockReader();
    }
}
