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;
    private GameMode gamemode;
    #endregion Private Variables

    #region Read Only
    private IEnumerable<PlayerData> playerArray { get { return playerData.Values; } }
    #endregion Read Only

	public GameObject levelInfo;
	public blockSpawn bspawn;

    public void Awake()
    {
        gamemode = CurrentGameMode.Value;
        RegisterHandlers();
        SpawnCharacters();
        ClientList.ForEach(p => p.ChangeScene("ClientScene"));
    }

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

    private void Update()
    {
        server.ServerUpdate();
    }

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

    private void RecieveLogicList(NetworkMessage msg)
    {
        LogicProtocols.LogicMsg logicMsg;
        if (!msg.TryRead(out logicMsg))
            return;

        Debug.Log("Recieved function from " + msg.conn.connectionId);

        playerData[msg.conn.connectionId].blockReader.LogicChain = new List<LogicBlock>(logicMsg.elements);
        playerData[msg.conn.connectionId].recievedList = true;

        if (playerData.All(p => p.Value.recievedList))
            DoRoundRoutine();
    }

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

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

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

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

        while (playerArray.Any(p => !p.blockReader.Finished))
        {
            //Debug.Log("One Move");
            foreach (PlayerData player in playerArray)
                StartCoroutine(RunOnce(player));

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

            gamemode.FinishedMove(playerArray.ToArray());
            playerArray.ForEach(p => p.client.SendScore());

        }

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

        gamemode.RoundEnd(playerArray.ToArray());

        foreach (PlayerData player in playerArray)
        {
            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;
        }
    }

    private void RegisterHandlers()
    {
        server.server.RegisterHandler(LogicProtocols.SendLogicList, RecieveLogicList);
    }  

    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();
    }
}