using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public enum PlayerTypes
{
    Player1 = 0,
    Player2 = 1,
    Player3 = 2,
    Player4 = 3
}

[System.Serializable]
public class PlayerGameData
{
    public PlayerTypes playerType;
    public GameObject playerCornerEntity;
    public int startIndex;
    public int endIndex;
    public Transform playersParent;
    public List<PlayerPawn> playerPawnsDict;
    public int totalPawnsFinished = 0;
}

public class GameplayManager : MonoBehaviour, IBase, IBootLoader, IDataLoader
{
    [SerializeField] private Transform pointerDebug;
    [SerializeField] private MeshRenderer pointerMeshRend;
    [SerializeField] private Material turnMat;
    [SerializeField] private Material selectMat;

    [SerializeField] private PlayerGameData[] playerGameDatas;

    private PlayerTypes currentPlayerTypeTurn;
    private int currentPlayerTurnIndex = 0;
    private List<PlayerTypes> playerTypes = new List<PlayerTypes>();

    public List<PlayerTypes> PlayerTypesCollection => playerTypes;

    // private Dictionary<PlayerTypes, > playerPawnsDict = new Dictionary<PlayerTypes, List<PlayerPawn>>();
    private Dictionary<PlayerTypes, PlayerGameData> playerGameDatasDict = new Dictionary<PlayerTypes, PlayerGameData>();

    private TilesManager tilesManager;

    private int diceRolledValue;
    public bool CanRollDice
    {
        get; private set;
    }

    private bool CanRollDiceAgain = false; // used for when you get a 6 or when you reach the finish point

    public void Initialize()
    {
        InterfaceManager.Instance?.RegisterInterface<GameplayManager>(this);
    }

    public void InitializeData()
    {
        tilesManager = InterfaceManager.Instance.GetInterfaceInstance<TilesManager>();

        CanRollDice = true;
        InitCurrentGamePlayerInfo(
            new List<PlayerTypes> { PlayerTypes.Player1, PlayerTypes.Player2, PlayerTypes.Player3, PlayerTypes.Player4 });
    }

    // TODO :: Call based on 2P/3P/4P
    public void InitCurrentGamePlayerInfo(List<PlayerTypes> playerTypes)
    {
        this.playerTypes = playerTypes;    
        currentPlayerTypeTurn = playerTypes[currentPlayerTurnIndex];

        // initialize the board based on the player types
        foreach (PlayerGameData playerGameData in playerGameDatas)
        {
            if (!playerTypes.Contains(playerGameData.playerType))
            {
                playerGameData.playerCornerEntity.SetActive(false);
            }
            else
            {
                playerGameDatasDict.Add(playerGameData.playerType, playerGameData);
                playerGameDatasDict[playerGameData.playerType].playerPawnsDict = new List<PlayerPawn>();
                PlayerPawn playerPawn = null;
                int indexer = 0;

                foreach (Transform playerPawnChild in playerGameData.playersParent)
                {
                    if (!playerPawnChild.gameObject.activeInHierarchy) continue;
                    
                    playerPawn = playerPawnChild.GetComponent<PlayerPawn>();
                    indexer++;
                    playerPawn.InitId(indexer);
                    playerGameDatasDict[playerGameData.playerType].playerPawnsDict.Add(playerPawn);
                }
            }
        }
    }

    public void EnablePlayerSelectionStates(bool state)
    {
        foreach (var playerPawn in playerGameDatasDict[currentPlayerTypeTurn].playerPawnsDict)
        {
            if (playerPawn.GetPlayerState() == PlayerState.InFinishingPath) continue;

            playerPawn.SetPlayerSelectionState(state);
        }
    }

    public void OnDiceRolled(int rolledVal)
    {
        CanRollDice = false;

        // add core dice logic here
        Debug.Log($"Tile Index :: LUDO :: rolledVal: {rolledVal} :: {currentPlayerTypeTurn}");
        diceRolledValue = rolledVal;
        
        if (rolledVal == Ludo_3D_Constants.Max_Dice_Rolls)
        {
            // provide option to select a pawn from the list
            // also play a simple animation before selecting
            EnablePlayerSelectionStates(true);

            CanRollDiceAgain = true;

            bool AreAllPawnsInFinishingPath = false;
            foreach (var pawn in playerGameDatasDict[currentPlayerTypeTurn].playerPawnsDict)
            {
                if (pawn.GetPlayerState() == PlayerState.InFinishingPath && pawn.GetPlayerState() != PlayerState.HasFinished)
                {
                    AreAllPawnsInFinishingPath = true;
                    continue;
                }
                
                AreAllPawnsInFinishingPath = false;
                break;
            }

            if (AreAllPawnsInFinishingPath)
                CanRollDice = true;

            pointerMeshRend.material = selectMat;
            // pointerMeshRend.materials[0] = selectMat;
        }
        else // if there are any other pawns that are in safe or moving state
        {
            // for player's logic
            IEnumerable<PlayerPawn> availPlayers = playerGameDatasDict[currentPlayerTypeTurn].playerPawnsDict.Select(pawn => pawn)
                                                                                .Where(pawn => pawn.GetPlayerState() == PlayerState.InSafeZone || 
                                                                                                pawn.GetPlayerState() == PlayerState.Moving || 
                                                                                                pawn.GetPlayerState() == PlayerState.InFinishingPath);
            int customAvailPlayers = availPlayers.Count();

            foreach (PlayerPawn playerPawn in availPlayers)
            {
                Debug.Log($"## playerPawn.GetPlayerState(): {playerPawn.GetPlayerState()}");
                if (playerPawn.GetPlayerState() == PlayerState.InFinishingPath)
                {
                    if (diceRolledValue <= tilesManager.GetFinishingTileDataLength(currentPlayerTypeTurn) - (playerPawn.CurrentTileIndex + 1))
                    {
                        playerPawn.SetPlayerSelectionState(true);
                    }   
                    else
                    {
                        customAvailPlayers--;
                    }

                    continue;
                }

                playerPawn.SetPlayerSelectionState(true);
            }

            // if (availPlayers.Count() < 1)
            Debug.Log($"CustomAvailablePlayers: {customAvailPlayers}");
            if (customAvailPlayers < 1)
            {
                SwitchPlayer();
                CanRollDice = true;
            }

            CanRollDiceAgain = false;
        }
    }

    public void OnPawnSelected(PlayerPawn playerPawn)
    {
        EnablePlayerSelectionStates(false);

        PlayerGameData playerGameData = playerGameDatasDict[currentPlayerTypeTurn];

        if (playerPawn.GetPlayerState() == PlayerState.InHome)
        {
            Tile targetTile = tilesManager.RetrieveTileBasedOnIndex(playerGameData.startIndex);

            playerPawn.MoveToTile(
                GetAndInitPositionInsideSafeZone(playerPawn, targetTile),
                onComplete: () =>
                {
                    CanRollDice = true;
                    playerPawn.SetPlayerState(PlayerState.InSafeZone);
                }, playerGameData.startIndex);

            return;
        }
        else if (playerPawn.GetPlayerState() == PlayerState.InFinishingPath 
                || playerPawn.CurrentTileIndex == playerGameDatasDict[currentPlayerTypeTurn].endIndex)
        {
            int finishingPathIndex = GetNextFinishingTileIndex(playerPawn);
            int targetIdx = finishingPathIndex + diceRolledValue > tilesManager.GetFinishingTileDataLength(currentPlayerTypeTurn) - 1 ? 
                                tilesManager.GetFinishingTileDataLength(currentPlayerTypeTurn) - 1 : finishingPathIndex + diceRolledValue;

            Debug.Log($"TargetIdx: {targetIdx}");
            MoveThroughFinishingPath(playerPawn, finishingPathIndex, targetIdx);
        }
        else if (playerPawn.GetPlayerState() == PlayerState.InSafeZone || playerPawn.GetPlayerState() == PlayerState.Moving)
        {
            // move based on the dice value
            Debug.Log($"Tile Index :: currentTileIndex: {playerPawn.CurrentTileIndex}");
            int nextTileIdx = GetNextGeneralTileIndex(playerPawn);
            int targetIdx = playerPawn.CurrentTileIndex + diceRolledValue;

            if (nextTileIdx == 0)
                targetIdx = targetIdx - playerPawn.CurrentTileIndex;
            
            Tile currentSittingTile = tilesManager.RetrieveTileBasedOnIndex(playerPawn.CurrentTileIndex);
            if (currentSittingTile.IsSafeZone)
            {
                SafeTile safeTile = (SafeTile)currentSittingTile;
                safeTile.UpdateSafeZonePlayerData(currentPlayerTypeTurn, playerPawn);

                if (safeTile.PlayerTypesCount == 1)
                {
                    PlayerTypes playerType = safeTile.GetFirstPlayerType();
                    
                    var playerPawns = safeTile.GetPlayerPawns(playerType);
                    foreach (var pawn in playerPawns)
                        pawn.MoveToCustomTilePosition(safeTile.CenterPlacementPosition);
                }
            }

            MoveThroughTiles(playerPawn, nextTileIdx, targetIndex: targetIdx);
        }
    }

    private int GetNextGeneralTileIndex(PlayerPawn playerPawn)
    {
        return playerPawn.CurrentTileIndex == tilesManager.GetGeneralTilesLength() - 1 ? 0 : playerPawn.CurrentTileIndex + 1;
    }

    private int GetNextFinishingTileIndex(PlayerPawn playerPawn)
    {
        return playerPawn.CurrentTileIndex > tilesManager.GetFinishingTileDataLength(currentPlayerTypeTurn) - 1 ? 0 : playerPawn.CurrentTileIndex + 1;
    }

    private void SwitchPlayer(PlayerPawn playerPawn = null)
    {
        if (!CanRollDiceAgain)
        {
            Debug.Log($"currentPlayerTurn: {currentPlayerTypeTurn}");
            Debug.Log($"currentPlayerTurnIndex: {currentPlayerTurnIndex}");

            if (playerTypes.Count == 1)
            {
                Debug.LogError($"GAME IS OVER");
                return;
            }

            if (currentPlayerTypeTurn == playerTypes[playerTypes.Count - 1])
            {
                currentPlayerTurnIndex = 0;
                currentPlayerTypeTurn = playerTypes[currentPlayerTurnIndex];
            }
            else
            {
                currentPlayerTurnIndex++;
                currentPlayerTypeTurn = playerTypes[currentPlayerTurnIndex];
            }
        }

        var tempPos = playerGameDatasDict[currentPlayerTypeTurn].playerCornerEntity.transform.position;
        pointerDebug.position = new Vector3(tempPos.x, 3f, tempPos.z);

        pointerMeshRend.material = turnMat;
        // pointerMeshRend.materials[0] = turnMat;

        if (playerPawn)
            playerPawn.SetPlayerState(tilesManager.RetrieveTileBasedOnIndex(playerPawn.CurrentTileIndex).IsSafeZone ? PlayerState.InSafeZone : PlayerState.Moving);
    }

    private void MoveThroughTiles(PlayerPawn playerPawn, int index, int targetIndex)
    {
        Tile nextTile = tilesManager.RetrieveTileBasedOnIndex(index);
        Vector3 targetPosition = nextTile.CenterPlacementPosition;

        Debug.Log($"Tile Index :: nextIndex: {index}, targetIndex: {targetIndex}, nextTileName: {nextTile.name}");

        if (index == targetIndex) // if the target index is the safe zone only then apply the logic for rearranging pawns
        {
            Tile targetTile = tilesManager.RetrieveTileBasedOnIndex(targetIndex);
            if (targetTile.IsSafeZone)
            {
                targetPosition = GetAndInitPositionInsideSafeZone(playerPawn, targetTile);
            }
        }

        Debug.Log($"tile targetPosition: {targetPosition}");

        playerPawn.MoveToTile(
            targetPosition,
            onComplete: () =>
            {
                diceRolledValue--;
                Debug.Log($"DiceRolledValue: {diceRolledValue}");
                if (diceRolledValue > 0)
                {
                    int nextTileIndex = GetNextGeneralTileIndex(playerPawn);
                    Debug.Log($"currentTileIndex: {playerPawn.CurrentTileIndex}, nextTileIndex: {nextTileIndex}, targetIndex: {targetIndex}");

                    if (playerPawn.GetPlayerState() == PlayerState.InFinishingPath || index == playerGameDatasDict[currentPlayerTypeTurn].endIndex)
                    {
                        // MoveThroughTiles(playerPawn, index, targetIndex);
                        Debug.Log($"TargetIdx: {targetIndex - index}");
                        MoveThroughFinishingPath(playerPawn, 0, targetIndex - index);
                    }
                    else if (nextTileIndex <= targetIndex)
                    {
                        if (nextTileIndex == 0)
                            targetIndex = (targetIndex - playerPawn.CurrentTileIndex) - 1;

                        MoveThroughTiles(playerPawn, nextTileIndex, targetIndex);
                    }
                }
                else
                {
                     // TODO :: Improve this logic, use a collection
                    if (!nextTile.IsSafeZone)
                    {
                        if (nextTile.PlayerPawn != null)
                        {
                            // TODO :: Send existing pawn back to base.
                            // means there's already a pawn there, move him back to the base.
                        }

                        nextTile.InitPlayerPawn(playerPawn, currentPlayerTypeTurn);
                    }

                    SwitchPlayer(playerPawn);
                    CanRollDice = true;
                }
            }, 
            index);
    }

    private Vector3 GetAndInitPositionInsideSafeZone(PlayerPawn playerPawn, Tile targetTile)
    {
        Vector3 targetPosition;
        SafeTile targetSafeTile = (SafeTile)targetTile;
        Debug.Log($"targetSafeTile.PlayerTypesCount: {targetSafeTile.PlayerTypesCount}");
        if (targetSafeTile.PlayerTypesCount == 1)
        {
            Debug.Log($"targetSafeTile.ContainsPlayerType(currentPlayerTypeTurn): {targetSafeTile.ContainsPlayerType(currentPlayerTypeTurn)}");
            if (!targetSafeTile.ContainsPlayerType(currentPlayerTypeTurn)) // means it is a new player type, the second one
            {
                PlayerTypes initialPlayerType = targetSafeTile.GetFirstPlayerType();

                // rearrange already existing player from center position to it's saved transform
                var playerPawns = targetSafeTile.GetPlayerPawns(initialPlayerType);
                foreach (var pawn in playerPawns)
                {
                    var placementPoint = targetSafeTile.GetPlacementPoint(initialPlayerType);
                    pawn.MoveToCustomTilePosition(placementPoint.position);
                }

                targetSafeTile.InitPlayerPawn(playerPawn, currentPlayerTypeTurn);
                targetPosition = targetSafeTile.GetPlacementPoint(currentPlayerTypeTurn).position;
            }
            else
            {
                targetSafeTile.InitPlayerPawn(playerPawn, currentPlayerTypeTurn);
                targetPosition = targetTile.CenterPlacementPosition;
            }
        }
        else
        {
            // TODO :: Check the data if it's consistent
            Debug.Log($"targetSafeTile.ContainsPlayerType(currentPlayerTypeTurn): {targetSafeTile.ContainsPlayerType(currentPlayerTypeTurn)}");
            if (!targetSafeTile.ContainsPlayerType(currentPlayerTypeTurn))
            {
                    Debug.Log($"targetSafeTile.PlayerTypesCount: {targetSafeTile.PlayerTypesCount}");
                if (targetSafeTile.PlayerTypesCount < 1) // he is the only player that is being added to the safe zone
                {
                    targetSafeTile.InitPlayerPawn(playerPawn, currentPlayerTypeTurn);
                    targetPosition = targetTile.CenterPlacementPosition;
                }
                else
                {
                    targetSafeTile.InitPlayerPawn(playerPawn, currentPlayerTypeTurn);
                    targetPosition = targetSafeTile.GetPlacementPoint(currentPlayerTypeTurn).position;
                }
            }
            else
            {
                targetSafeTile.InitPlayerPawn(playerPawn, currentPlayerTypeTurn);
                targetPosition = targetSafeTile.GetPlacementPoint(currentPlayerTypeTurn).position;
            }
        }

        return targetPosition;
    }

    private void MoveThroughFinishingPath(PlayerPawn playerPawn, int index, int targetIndex)
    {
        playerPawn.SetPlayerState(PlayerState.InFinishingPath);
        playerPawn.MoveToTile(
            tilesManager.RetrievePositionForFinishingTile(currentPlayerTypeTurn, index).position,
            onComplete: () =>
            {
                diceRolledValue--;

                Debug.Log($"DiceRolledValue: {diceRolledValue}");
                if (diceRolledValue > 0)
                {
                    int tileIndex = GetNextFinishingTileIndex(playerPawn);
                    Debug.Log($"tileIndex: {tileIndex}, targetIndex: {targetIndex}");
                    if (tileIndex <= targetIndex)
                    {
                        // MoveThroughTiles(playerPawn, index, targetIndex);
                        MoveThroughFinishingPath(playerPawn, tileIndex, targetIndex);
                    }
                }
                else
                {
                    if (playerPawn.CurrentTileIndex == tilesManager.GetFinishingTileDataLength(currentPlayerTypeTurn) - 1)
                    {
                        playerPawn.SetPlayerState(PlayerState.HasFinished);
                        playerGameDatasDict[currentPlayerTypeTurn].totalPawnsFinished++;

                        if (playerGameDatasDict[currentPlayerTypeTurn].totalPawnsFinished == playerGameDatasDict[currentPlayerTypeTurn].playerPawnsDict.Count)
                        {
                            CanRollDiceAgain = false;
                            
                            SwitchPlayer();
                            if (playerTypes.Contains(currentPlayerTypeTurn))
                                playerTypes.Remove(currentPlayerTypeTurn);

                            Debug.Log($"PlayerTypes: {playerTypes.Count}");
                        }
                        else
                        {
                            CanRollDiceAgain = true;
                        }
                    }
                    else
                    {
                        SwitchPlayer();
                    }
                    
                    CanRollDice = true;
                }
            }, 
            index);
    }
}
