using UdonSharp;
using UnityEngine;

namespace Marro.PacManUdon
{
    public partial class GameManager : SyncedObject
    {
        [Header("Static game components")]
        [SerializeField] private Maze[] mazes;
        [SerializeField] private PacMan pacMan;
        [SerializeField] private GhostManager ghostManager;
        [SerializeField] private BonusFruit bonusFruit;
        [SerializeField] private CollisionManager collisionManager;
        [SerializeField] private StatusDisplay statusDisplay;
        [SerializeField] private CollisionManager attractScreen;
        [SerializeField] private GameObject intermissionScreen;
        [SerializeField] private GameObject pressStartButtonScreen;
        [SerializeField] private PlayerInput playerInput;
        [SerializeField] private SoundManager soundManager;
        [SerializeField] private NetworkManager networkManagerSetup;

        [SerializeField] private GameObject recorder;

        [Header("Game settings")]
        [SerializeField] private int startingExtraLives = 3;
        [SerializeField] private int scoreToExtraLife = 10000;

        private Maze maze;
        private Intermission2Pole intermission2Pole;

        private Animator mazeSpriteAnimator;
        private GameObject[] attractScreenElements;
        private GameObject[] intermissionScreenElements;

        private PacManGameState gameState;
        private int score;
        private int level;
        private int highScore;
        private int extraLives;

        public void Start()
        {
            attractScreenElements = new GameObject[attractScreen.transform.childCount];
            for (int i = 0; i < attractScreenElements.Length; i++)
            {
                attractScreenElements[i] = attractScreen.transform.GetChild(i).gameObject;
            }

            intermissionScreenElements = new GameObject[intermissionScreen.transform.childCount];
            for (int i = 0; i < intermissionScreenElements.Length; i++)
            {
                intermissionScreenElements[i] = intermissionScreen.transform.GetChild(i).gameObject;
            }

            maze = mazes[0];
            mazeSpriteAnimator = maze.mazeSprite.GetComponent<Animator>();
            intermission2Pole = intermissionScreenElements[4].GetComponent<Intermission2Pole>();

            networkManager.Initialize();
            ghostManager.Initialize(maze.ghostStarts, maze.ghostTargets, pacMan, collisionManager, this);
            pacMan.Initialize(playerInput, maze.pacManStart, this, collisionManager);
            bonusFruit.Initialize();
            collisionManager.Initialize(this, bonusFruit, ghostManager.Ghosts);
            statusDisplay.Initialize();
            playerInput.Initialize(this);
            soundManager.Initialize();
            intermission2Pole.Initialize(this, ghostManager.Ghosts[0]);

            SubscribeToEvent(NetworkEventType.StartGameButtonPressed);
            SubscribeToEvent(NetworkEventType.FullSync);

            HideEverything();

            SetScore(0);
            SetHighScore(0);
            SetLevel(0);

            StartAttractMode();
        }

        public override void SyncedUpdate()
        {
            TimeSequenceUpdate(networkManager.SyncedDeltaTime);
        }

        public void JoystickGrabbed()
        {
            if (gameState == PacManGameState.AttractMode || gameState == PacManGameState.AttractModeDemo)
                StartTimeSequence(PacManTimeSequence.WaitForStart);
        }

        public void JoystickReleased()
        {
            if (gameState == PacManGameState.WaitForStart)
                StartTimeSequence(PacManTimeSequence.WaitForStartTimeout);
        }

        public void ResetButtonPressed()
        {
            Debug.Log($"{gameObject} Reset button was pressed!");
            Start();
        }

        public void StartGameButtonPressed()
        {
            Debug.Log($"{gameObject} Start Game Button was pressed!");
            if (networkManager.IsOwner)
            {
                networkManager.SendEventSoon(NetworkEventType.StartGameButtonPressed, false);
            }
            StartTimeSequence(PacManTimeSequence.StartNewGame);
        }

        private void StartAttractMode()
        {
            // #if RECORDING_DEMO
            // recorder.gameObject.SetActive(true);
            StartTimeSequence(PacManTimeSequence.AttractScreenIntroduction);
            // #else
            //             SetGameState(PacManGameState.AttractMode);
            //             HideEverything();
            //             demo.gameObject.SetActive(true);
            // #endif
        }

        private void InitializeNewGame()
        {
            //Debug.Log($"{gameObject} Started new game!");
            SetScore(0);
            SetExtraLives(startingExtraLives);
            SetLevel(1);
        }

        private void RestartLevel(bool afterLifeLost = false)
        {
            //Debug.Log($"{gameObject} (Re)started level!");

            mazeSpriteAnimator.SetBool("Blinking", false);

            ghostManager.RestartLevel(afterLifeLost);
            pacMan.Reset();
            bonusFruit.Despawn();
            soundManager.Reset();
            collisionManager.Reset();
            statusDisplay.Reset();
        }

        private void PrepareForCutscene()
        {
            HideEverything();
            RestartLevel();
            SetFrozen(true);
        }

        internal void GotPellet(Pellet pellet, bool isPowerPellet, int pelletsCollectedCount, int pelletsRemainingCount) 
        {
            AddScore(isPowerPellet ? 50 : 10);

            ghostManager.PelletConsumed(pelletsRemainingCount);

            soundManager.PlayPelletSound();
            soundManager.UpdatePelletCount(pelletsRemainingCount);

            if (pelletsRemainingCount <= 0)
            {
                StartTimeSequence(PacManTimeSequence.BoardClear);
                return;
            }

            if (isPowerPellet)
            {
                if (gameState == PacManGameState.AttractMode)
                {
                    TimeSequenceSkipToNextStep();
                    return;
                }

                ghostManager.SetPowerPellet(true);
                pacMan.SetPowerPellet(true);
                soundManager.SetGhostBlue(true);
            }

            if (pelletsCollectedCount == 70 || pelletsCollectedCount == 170)
            {
                bonusFruit.Spawn();
            }
        }

        public void GotPowerPellet(Pellet pellet)
        {
            //Debug.Log($"{gameObject} GotPowerPellet");

            
        }

        public void EndPowerPellet()
        {
            ghostManager.SetPowerPellet(false);
            pacMan.SetPowerPellet(false);
            soundManager.SetGhostBlue(false);
        }

        public void GotFruit()
        {
            AddScore(bonusFruit.Collected());
            soundManager.PlayFruitSound();
        }

        public void GhostCaught(int scoreBonus)
        {
            //Debug.Log($"{gameObject} GhostCaught");

            if (gameState == PacManGameState.AttractMode)
            {
                TimeSequenceSkipToNextStep();
                return;
            }

            AddScore(scoreBonus);
            StartTimeSequence(PacManTimeSequence.GhostCaught);
        }

        public void PacManCaught()
        {
            return;
            StartTimeSequence(PacManTimeSequence.PacManCaught);
        }

        public void NoGhostsScared()
        {
            soundManager.SetGhostBlue(false);
        }

        public void NoGhostsRetreating()
        {
            soundManager.SetGhostRetreat(false);
        }

        public void Intermission2PoleUpdate()
        {
            //Debug.Log($"{gameObject} Intermission2PoleUpdate");

            TimeSequenceSkipToNextStep();
        }

        void BoardClearAnimation()
        {
            ghostManager.gameObject.SetActive(false);
            mazeSpriteAnimator.SetBool("Blinking", true);
        }


        private void HideEverything()
        {
            SetPelletsActive(false);
            SetMazeVisible(false);
            SetGhostsActive(false);
            SetPacManActive(false);
            SetPressStartButtonScreenVisible(false);
            SetIntermissionScreenVisible(false);
            statusDisplay.SetGameOverTextVisible(false);
            statusDisplay.SetExtraLivesDisplayVisible(false);
            statusDisplay.SetLevelDisplayVisible(false);
            statusDisplay.SetPlayer1TextVisible(false);
            statusDisplay.SetReadyTextVisible(false);
        }

        void SetPelletsActive(bool active)
        {
            collisionManager.gameObject.SetActive(active);
        }

        void SetMazeVisible(bool visible)
        {
            mazeSpriteAnimator.SetBool("Hidden", !visible);
        }

        void SetGhostsActive(bool active)
        {
            ghostManager.SetActive(active);
        }

        void SetPacManActive(bool active)
        {
            pacMan.SetActive(active);
        }

        void SetPressStartButtonScreenVisible(bool visible)
        {
            pressStartButtonScreen.SetActive(visible);
        }

        void SetIntermissionScreenVisible(bool visible)
        {
            intermissionScreen.SetActive(visible);
        }

        void SetGameState(PacManGameState newGameState)
        {
            // Debug.Log($"{gameObject} State transitioning from {gameState} to {newGameState}");
            gameState = newGameState;
        }

        private void IncrementLevel()
        {
            SetLevel(level + 1);
        }

        private void SetLevel(int level)
        {
            this.level = level;
            pacMan.SetLevel(level);
            ghostManager.SetLevel(level);
            statusDisplay.SetLevel(level);
            bonusFruit.SetFruitType(PacManConstants.GetFruitTypeForLevel(level));
        }

        void AddScore(int score)
        {
            if (gameState == PacManGameState.AttractMode || gameState == PacManGameState.AttractModeDemo)
            {
                return;
            }

            if (this.score < scoreToExtraLife && this.score + score >= scoreToExtraLife)
            {
                BonusLifeReached();
            }

            SetScore(this.score + score);
        }

        void SetScore(int score)
        {
            this.score = score;
            statusDisplay.Set1UPScore(score);
            if (score > highScore)
            {
                highScore = score;
                statusDisplay.SetHighScore(score);
            }
        }

        void SetHighScore(int highScore)
        {
            this.highScore = highScore;
            statusDisplay.SetHighScore(score);
        }

        public void DecrementLives()
        {
            // Debug.Log($"{gameObject} Decremented lives from {extraLives} to {extraLives - 1}");
            SetExtraLives(extraLives - 1);
        }

        void IncrementLives()
        {
            // Debug.Log($"{gameObject} Incremented lives from {extraLives} to {extraLives + 1}");
            SetExtraLives(extraLives + 1);
        }

        void SetExtraLives(int extraLives)
        {
            // Debug.Log($"{gameObject} Set lives from {this.extraLives} to {extraLives}");
            this.extraLives = extraLives;
            statusDisplay.SetExtraLives(extraLives);
        }

        void BonusLifeReached()
        {
            IncrementLives();
            soundManager.PlayExtraLifeSound();
        }

        private void SetupInGameState()
        {
            gameState = PacManGameState.InGame;

            attractScreen.gameObject.SetActive(false);

            SetPelletsActive(true);
            SetMazeVisible(true);
            SetGhostsActive(true);
            SetPacManActive(true);

            statusDisplay.SetExtraLivesDisplayVisible(true);
            statusDisplay.SetLevelDisplayVisible(true);
            statusDisplay.SetLabel1UPTextBlinking(true);

            soundManager.SuppressSound(false);
            soundManager.StartGhostSound();

            SetFrozen(false);
        }

        public void SetFrozen(bool frozen, bool ghostIgnoreIfCaught = false, bool ghostKeepAnimating = false)
        {
            // Debug.Log($"{gameObject} Set Frozen: {frozen}");
            pacMan.SetFrozen(frozen);
            bonusFruit.SetFrozen(frozen);
            ghostManager.SetFrozen(frozen, ignoreIfCaught: ghostIgnoreIfCaught);
            collisionManager.SetFrozen(frozen);
        }

        public override void CollectSyncedData(byte[] data, ref int index, NetworkEventType eventType)
        {
            if (eventType != NetworkEventType.FullSync)
            {
                return;
            }

            data.Append(gameState == PacManGameState.InGame, ref index);

            data.AppendAsByte(level, ref index);
            data.Append(score, ref index);
            data.AppendAsByte(extraLives, ref index);

            data.Append(currentlyInTimeSequence, ref index);

            if (currentlyInTimeSequence)
            {
                data.AppendAsByte((int)currentTimeSequence, ref index);
                data.Append(timeSequenceSecondsPassed, ref index);
            }

            bonusFruit.CollectSyncedData(data, ref index, eventType);

            collisionManager.CollectSyncedData(data, ref index, eventType);

            ghostManager.CollectSyncedData(data, ref index, eventType);

            pacMan.CollectSyncedData(data, ref index, eventType);

            soundManager.CollectSyncedData(data, ref index, eventType);
        }

        public override bool WriteSyncedData(byte[] data, ref int index, NetworkEventType eventType)
        {
            if (eventType == NetworkEventType.StartGameButtonPressed)
            {
                StartGameButtonPressed();
                return true;
            }

            if (eventType != NetworkEventType.FullSync)
            {
                return true;
            }

            // If we're currently in-game, we'll need to make sure the state is setup correctly
            var remoteCurrentlyInGame = data.ReadBool(ref index);
            if (remoteCurrentlyInGame)
            {
                currentlyInTimeSequence = false; // Kill the current time sequence, otherwise it might interfere
                                                 // No need to run it to completion since we're overriding all state
                SetupInGameState();
            }

            SetLevel(data.ReadByte(ref index));
            SetScore(data.ReadInt(ref index));
            SetExtraLives(data.ReadByte(ref index));

            // Sync up with the remote's time sequence
            var remoteCurrentlyInTimeSequence = data.ReadBool(ref index);
            if (remoteCurrentlyInTimeSequence)
            {
                var currentTimeSequence = (PacManTimeSequence)data.ReadByte(ref index);
                var timeSequenceSecondsPassed = data.ReadFloat(ref index);
                TimeSequenceSyncWithRemote(currentTimeSequence, timeSequenceSecondsPassed);
            }

            // Note that we don't have any logic for if the remote is not in-game and not in a time sequence.
            // Such a state should be impossible.

            bonusFruit.WriteSyncedData(data, ref index, eventType);

            collisionManager.WriteSyncedData(data, ref index, eventType);

            ghostManager.WriteSyncedData(data, ref index, eventType);

            pacMan.WriteSyncedData(data, ref index, eventType);

            soundManager.WriteSyncedData(data, ref index, eventType);

            return true;
        }

        public PacManGameState GameState => gameState;

        public int Level => level;
    }
}
