#define RECORDING_DEMO

namespace Marro.PacManUdon
{
    using System;
    using UdonSharp;
    using UnityEngine;
    using VRC.SDKBase;
    using VRC.Udon;
    using VRC.SDK3.Components;
    using VRC.Udon.Common.Interfaces;
    using VRC.SDK3.Data;

    public partial class GameManager : UdonSharpBehaviour
    {
        [Header("Static game components")]
        [SerializeField] private Maze[] mazes;
        [SerializeField] private PacMan pacMan;
        [SerializeField] private GhostManager ghostManager;
        [SerializeField] private BonusFruit bonusFruit;
        [SerializeField] private PelletManager pelletManager;
        [SerializeField] public StatusDisplay statusDisplay; // This one is public so other scripts can write to the debug display
        [SerializeField] private PelletManager attractScreen;
        [SerializeField] private GameObject intermissionScreen;
        [SerializeField] private GameObject pressStartButtonScreen;
        [SerializeField] private PlayerInput playerInput;
        [SerializeField] private Animator demo;
        [SerializeField] private SoundManager soundManager;

        [SerializeField] private GameObject recorder;


        [Header("Game settings")]
        [SerializeField] private int startingExtraLives = 3;
        [SerializeField] private int scoreToExtraLife = 10000;
        [Tooltip("Override amount of pellets needed to clear stage, set to -1 to disable.")]
        [SerializeField] private int pelletCountOverride = -1;

        private Maze maze;
        private VRCObjectPool pelletPool;
        private Intermission2Pole intermission2Pole;

        private Animator mazeSpriteAnimator;
        private int pelletCountTotal;
        private int pelletCountRemaining;
        private GameObject[] attractScreenElements;
        private GameObject[] intermissionScreenElements;

        [UdonSynced, FieldChangeCallback(nameof(GameState))] private PacManGameState gameState;
        [UdonSynced, FieldChangeCallback(nameof(Score))] private int score;
        [UdonSynced, FieldChangeCallback(nameof(Level))] private int level;
        [UdonSynced, FieldChangeCallback(nameof(HighScore))] private int highScore;
        [UdonSynced, FieldChangeCallback(nameof(ExtraLives))] 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];
            pelletPool = maze.pelletContainer.GetComponent<VRCObjectPool>();
            mazeSpriteAnimator = maze.mazeSprite.GetComponent<Animator>();
            intermission2Pole = intermissionScreenElements[4].GetComponent<Intermission2Pole>();

            ghostManager.Initialize(maze.ghostTargets, pacMan, this);
            pacMan.Initialize(playerInput, pelletPool, this);
            bonusFruit.Initialize();
            pelletManager.Initialize(pelletPool);
            statusDisplay.Initialize();
            playerInput.Initialize(this);
            soundManager.Initialize();
            intermission2Pole.Initialize(this, ghostManager.Ghosts[0]);

            HideEverything();

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

            StartAttractMode();
        }

        public void FixedUpdate()
        {
            TimeSequenceUpdate(Time.deltaTime);
        }

        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!");
            TakeOwnership();
            StartTimeSequence(PacManTimeSequence.StartNewGame);
        }

        public void SkipLevelButtonPressed()
        {
            if (Networking.IsOwner(gameObject))
            {
                Debug.Log($"{gameObject} Skip level button pressed!");
                StartTimeSequence(PacManTimeSequence.BoardClear);
                TimeSequenceSkipToNextStep();
            }
        }

        public void StartDemoButtonPressed()
        {
            if (Networking.IsOwner(gameObject))
            {
                Debug.Log($"{gameObject} Start demo button pressed!");
                StartTimeSequence(PacManTimeSequence.Intermission1);
            }
        }

        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 InitializeLevel()
        {
            Debug.Log($"{gameObject} New level started!");

            if (Networking.IsOwner(gameObject))
            {
                pelletCountTotal = pelletPool.Pool.Length;
                pelletCountRemaining = pelletCountTotal;
                ghostManager.SetPelletsRemaining(pelletCountRemaining);
                ghostManager.NewLevel();

                pelletManager.RestoreAllPellets();

                if (pelletCountOverride > 0)
                {
                    pelletCountRemaining = pelletCountOverride;
                }
            }
            mazeSpriteAnimator.SetBool("Blinking", false);
        }

        private void RestartLevel()
        {
            Debug.Log($"{gameObject} (Re)started level!");

            // SetInGameComponentVisibility(true);

            ghostManager.Reset();
            pacMan.Reset();
            bonusFruit.Despawn();
            soundManager.Reset();
            pelletManager.SetPowerPelletsBlink(false);
        }

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

        public void GotPellet(bool addScore = true)
        {
            pelletCountRemaining--;
            if (addScore) AddScore(10);

            ghostManager.PelletConsumed();

            soundManager.PlayPelletSound();

            soundManager.UpdatePelletCount(pelletCountRemaining);

            int pelletsConsumed = pelletCountTotal - pelletCountRemaining;
            if (pelletCountRemaining <= 0)
            {
                StartTimeSequence(PacManTimeSequence.BoardClear);
            }
            else if (pelletsConsumed == 70 || pelletsConsumed == 170)
            {
                bonusFruit.Spawn();
            }
        }

        public void GotPowerPellet()
        {
            if (gameState == PacManGameState.AttractMode)
            {
                TimeSequenceSkipToNextStep();
                return;
            }
            GotPellet(addScore: false);
            AddScore(50);
            ghostManager.SetPowerPellet(true);
            pacMan.SetPowerPellet(true);
            soundManager.SetGhostBlue(true);
        }

        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)
        {
            if (gameState == PacManGameState.AttractMode)
            {
                TimeSequenceSkipToNextStep();
                return;
            }

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

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

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

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

        public void 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);
            demo.gameObject.SetActive(false);
        }

        void SetPelletsActive(bool active)
        {
            pelletPool.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;
            if (Networking.IsOwner(gameObject))
            {
                RequestSerialization();
            }
        }

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

        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()
        {
            if (!Networking.IsOwner(gameObject))
            {
                return;
            }
            // Debug.Log($"{gameObject} Decremented lives from {extraLives} to {extraLives - 1}");
            SetExtraLives(extraLives - 1);
        }

        void IncrementLives()
        {
            if (!Networking.IsOwner(gameObject))
            {
                return;
            }
            // 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();
        }

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

            if (!frozen)
            {
                pelletManager.SetPowerPelletsBlink(true);
            }
        }

        void TakeOwnership()
        {
            Networking.SetOwner(Networking.LocalPlayer, gameObject);
            Networking.SetOwner(Networking.LocalPlayer, pacMan.gameObject);
            Networking.SetOwner(Networking.LocalPlayer, pelletPool.gameObject);
            ghostManager.SetOwner(Networking.LocalPlayer);
        }

        public int ExtraLives
        {
            set
            {
                SetExtraLives(value);
            }
            get => extraLives;
        }

        public PacManGameState GameState
        {
            set
            {
                SetGameState(value);
            }
            get => gameState;
        }

        public bool GhostsScared
        {
            set
            {

            }
            get => GhostsScared;
        }

        public int Score
        {
            set
            {
                SetScore(value);
            }
            get => score;
        }

        public int HighScore
        {
            set
            {
                SetHighScore(value);
            }
            get => score;
        }

        public int Level
        {
            set
            {
                SetLevel(value);
            }
            get => level;
        }
    }
}