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

    public class GhostManager : UdonSharpBehaviour
    {
        [NonSerialized] public GameManager gameController;
        private Ghost[] ghosts;
        private Ghost blinky;

        // Level constants
        private float speedDefault;
        private float speedScared;
        private float speedReturn;
        private float speedTunnel;
        private float speedHome;
        private float speedElroy1;
        private float speedElroy2;
        private int elroy1PelletCount;
        private int elroy2PelletCount;

        // Power Pellet logic
        private bool powerPelletActive;
        private float powerPelletDuration;
        private float powerPelletCountdown;
        private int powerPelletMultiplier;

        private DataList ghostScaredQueue;

        // Blink logic
        private float blinkCycleRate = 0.233333333f;
        private bool blinkingActivated;
        private float blinkCountdown;
        private bool blinkCurrentlyWhite;

        // Scattering logic
        private float scatterCounter;
        private float[] scatterPattern;
        private int scatterPatternIndex;

        // Elroy logic
        public int elroyLevel;
        private int pelletsRemaining;

        // Ghost house logic
        private bool sharedPelletCounterActive;
        private int sharedPelletCounter;
        private int[] sharedPelletCounterReleaseValues = { 0, 7, 17, 32 };
        private float pelletTimeout;
        private float pelletTimeoutLimit;

        private bool frozen;
        private bool kinematic;

        // This should be called once when the game is initialized
        public void Initialize(GameObject[] ghostTargets, PacMan pacMan, GameManager gameController)
        {
            this.gameController = gameController;
            ghosts = transform.GetComponentsInChildren<Ghost>(true);
            blinky = ghosts[0];
            for (int ghostIndex = 0; ghostIndex < ghosts.Length; ghostIndex++)
            {
                Vector2 homePosition = ghostTargets[0].transform.localPosition;
                Vector2 idlePosition1 = ghostTargets[1 + ghostIndex * 3].transform.localPosition;
                Vector2 idlePosition2 = ghostTargets[2 + ghostIndex * 3].transform.localPosition;
                Vector2 cornerPosition = ghostTargets[3 + ghostIndex * 3].transform.localPosition;

                ghosts[ghostIndex].Initialize(pacMan, blinky, homePosition, idlePosition1, idlePosition2, cornerPosition);
            }
        }

        // This should be called every time the level is reset
        public void Reset()
        {
            ghostScaredQueue = new DataList();
            powerPelletActive = false;
            scatterCounter = 0;
            scatterPatternIndex = 0;
            sharedPelletCounter = 0;
            pelletTimeout = 0;
            elroyLevel = 0;
            kinematic = false;

            foreach (Ghost ghost in ghosts)
            {
                ghost.Reset();
            }

            SetScattering(true, reverseDirection: false);
            RequestSerialization();
        }

        public void NewLevel()
        {
            SetSharedPelletCounterActive(false);
            foreach (Ghost ghost in ghosts)
            {
                ghost.ResetHousePelletCounter();
            }
        }

        public void LifeLost()
        {
            SetSharedPelletCounterActive(true);
        }

        public void FixedUpdate()
        {
            // gameStateManager.statusDisplay.SetDebugText(1, this.blinkCountdown.ToString());
            if (frozen || kinematic)
            {
                return;
            }

            UpdatePelletTimeout();

            if (powerPelletActive)
            {
                UpdatePowerPellet();
            }
            else
            {
                UpdateScattering();
            }
        }

        void UpdateScattering()
        {
            scatterCounter += Time.deltaTime;
            if (scatterPatternIndex < scatterPattern.Length && scatterCounter >= scatterPattern[scatterPatternIndex])
            {
                // Debug.Log($"{gameObject} SetScattering: {scatterPatternIndex%1 == 0}, scatterCounter: {scatterCounter}, scatterPattern: {scatterPattern[scatterPatternIndex]}, scatterPatternIndex: {scatterPatternIndex}");
                scatterPatternIndex++;
                SetScattering(scatterPatternIndex % 2 == 0);
            }
        }

        void UpdatePowerPellet()
        {
            powerPelletCountdown -= Time.deltaTime;
            if (powerPelletCountdown <= 0)
            {
                gameController.EndPowerPellet(); // End power pellet
                SetPowerPellet(false);
            }
            else if (!blinkingActivated && powerPelletCountdown <= 2.1166667)
            {
                SetGhostBlinking(true);
            }

            if (blinkingActivated)
            {
                blinkCountdown -= Time.deltaTime;
                if (blinkCountdown <= 0)
                {
                    blinkCountdown += blinkCycleRate;
                    SetGhostBlinkingState(!blinkCurrentlyWhite);
                }
            }
        }

        void UpdatePelletTimeout()
        {
            pelletTimeout += Time.deltaTime;
            if (pelletTimeout >= pelletTimeoutLimit)
            {
                pelletTimeout -= pelletTimeoutLimit;

                for (int ghostIndex = 0; ghostIndex < ghosts.Length; ghostIndex++)
                {
                    Ghost ghost = ghosts[ghostIndex];
                    if (ghost.GetGhostState() == PacManGhostState.Home)
                    {
                        ghost.Release();
                        break;
                    }
                }
            }
        }

        public void GhostCaughtQueue(Ghost ghost)
        {
            if (gameController.GameState == PacManGameState.AttractMode)
            {
                gameController.GhostCaught(0);
                return;
            }
            // Debug.Log($"{gameObject} GhostCaughtQueue with ghost {ghost}");
            ghostScaredQueue.Add(ghost);
            GhostCaughtExecute(ghost);
        }

        public void GhostCaughtContinue()
        {
            // Debug.Log($"{gameObject} GhostCaughtContinue with ghost queue length {ghostScaredQueue.Count}");
            if (!ghostScaredQueue.TryGetValue(0, out DataToken currentGhost))
            {
                Debug.LogError("Called GhostCaughtContinue without a ghost in the queue!");
                return;
            }

            ((Ghost)currentGhost.Reference).ReturnHome();
            ghostScaredQueue.RemoveAt(0);

            if (ghostScaredQueue.TryGetValue(0, out DataToken nextGhost))
            {
                GhostCaughtExecute((Ghost)nextGhost.Reference);
            }
        }

        void GhostCaughtExecute(Ghost ghost)
        {
            int scoreBonus = 200 * (int)Math.Pow(2, powerPelletMultiplier);
            powerPelletMultiplier += 1;
            ghost.Caught(scoreBonus);
            gameController.GhostCaught(scoreBonus);
        }

        public void CapturedPacMan()
        {
            gameController.PacManCaught();
        }

        public void GhostReturned()
        {
            bool noGhostsScared = true;
            bool noGhostsRetreating = true;

            foreach (var ghost in ghosts)
            {
                var state = ghost.GetGhostState();

                if (ghost.IsScared)
                {
                    noGhostsScared = false;
                }

                if (state == PacManGhostState.CaughtScore || state == PacManGhostState.Returning || state == PacManGhostState.Entering)
                {
                    noGhostsRetreating = false;
                }
            }

            if (noGhostsScared)
            {
                gameController.NoGhostsScared();
            }

            if (noGhostsRetreating)
            {
                gameController.NoGhostsRetreating();
            }
        }

        void SetGhostBlinking(bool blinking)
        {
            blinkingActivated = blinking;
            if (blinking == true)
            {
                blinkCountdown = blinkCycleRate;
                SetGhostBlinkingState(true);
            }
        }

        void SetGhostBlinkingState(bool white)
        {
            blinkCurrentlyWhite = white;
            foreach (Ghost ghost in ghosts)
            {
                ghost.SetWhite(white);
            }
        }

        public void SetPowerPellet(bool powerPelletActive)
        {
            this.powerPelletActive = powerPelletActive;

            if (powerPelletActive)
            {
                powerPelletCountdown = powerPelletDuration;
                powerPelletMultiplier = 0;
                SetGhostBlinking(false);
                foreach (Ghost ghost in ghosts)
                {
                    ghost.BecomeScared();
                }
            }
            if (!powerPelletActive || powerPelletDuration == 0)
            {
                foreach (Ghost ghost in ghosts)
                {
                    ghost.CalmDown();
                    SetGhostBlinking(false);
                }
            }
        }

        public void SetFrozen(bool frozen, bool ignoreIfCaught = false)
        {
            this.frozen = frozen;
            foreach (Ghost ghost in ghosts)
            {
                ghost.SetFrozen(frozen, ignoreIfCaught: ignoreIfCaught);
            }
        }

        public void SetLevel(int level)
        {
            Debug.Log($"GhostManager: SetLevel {level}");
            speedDefault = PacManConstants.GetGhostDefaultSpeedForLevel(level);
            speedScared = PacManConstants.GetGhostScaredSpeedForLevel(level);
            speedReturn = 15f;
            speedTunnel = PacManConstants.GetGhostTunnelSpeedForLevel(level);
            speedHome = PacManConstants.GetGhostHomeSpeed();
            speedElroy1 = PacManConstants.GetBlinkyElroy1SpeedForLevel(level);
            speedElroy2 = PacManConstants.GetBlinkyElroy2SpeedForLevel(level);
            elroy1PelletCount = PacManConstants.GetElroy1PelletsRemainingForLevel(level);
            elroy2PelletCount = PacManConstants.GetElroy2PelletsRemainingForLevel(level);
            powerPelletDuration = PacManConstants.GetScaredDurationForLevel(level);
            scatterPattern = PacManConstants.GetScatterPatternForLevel(level);
            pelletTimeoutLimit = PacManConstants.GetGhostHousePelletTimeoutLimitForLevel(level);

            int[] privatePelletCounterReleaseValues = PacManConstants.GetGhostHousePrivatePelletCounterLimitForLevel(level);
            for (int i = 0; i < ghosts.Length; i++)
            {
                ghosts[i].SetHousePelletCounterLimit(privatePelletCounterReleaseValues[i]);
                ghosts[i].Reset(); // Reset needed to properly apply level
            }
        }

        public void SetOwner(VRCPlayerApi player)
        {
            Networking.SetOwner(player, gameObject);
            foreach (Ghost ghost in ghosts)
            {
                Networking.SetOwner(player, ghost.gameObject);
            }
        }

        public float GetTargetSpeed(Ghost ghost, PacManGhostState ghostState, bool isScared, bool inTunnel)
        {
            if (ghostState == PacManGhostState.Returning || ghostState == PacManGhostState.Entering)
            {
                return speedReturn;
            }
            if (inTunnel)
            {
                return speedTunnel;
            }
            if (ghostState == PacManGhostState.Exiting || ghostState == PacManGhostState.Home)
            {
                return speedHome;
            }
            if (isScared)
            {
                return speedScared;
            }
            if (ghost == blinky)
            {
                if (elroyLevel == 1)
                {
                    return speedElroy1;
                }
                if (elroyLevel == 2)
                {
                    return speedElroy2;
                }
            }
            return speedDefault;
        }

        void SetScattering(bool scattering, bool reverseDirection = true)
        {
            Debug.Log($"{gameObject} SetScattering: {scattering}");
            foreach (Ghost ghost in ghosts)
            {
                if (ghost == blinky && pelletsRemaining <= elroy1PelletCount)
                {
                    continue;
                }
                ghost.SetScattering(scattering, reverseDirection);
            }
        }

        public void SetPelletsRemaining(int pelletsRemaining)
        {
            this.pelletsRemaining = pelletsRemaining;
            UpdateElroyLevel();
        }

        void SetSharedPelletCounterActive(bool active)
        {
            // Debug.Log($"{gameObject} SetSharedPelletCounterActive {active}");
            sharedPelletCounterActive = active;
            foreach (Ghost ghost in ghosts)
            {
                ghost.SetHousePelletCounterActive(!active);
            }
        }

        public void PelletConsumed()
        {
            SetPelletsRemaining(pelletsRemaining - 1);

            pelletTimeout = 0;

            if (sharedPelletCounterActive)
            {
                IncrementSharedPelletCounter();
            }
            else
            {
                IncrementPrivatePelletCounter();
            }
        }

        void IncrementSharedPelletCounter()
        {
            sharedPelletCounter++;
            for (int ghostIndex = 0; ghostIndex < sharedPelletCounterReleaseValues.Length; ghostIndex++)
            {
                Ghost ghost = ghosts[ghostIndex];
                if (ghost.GetGhostState() == PacManGhostState.Home && sharedPelletCounter == sharedPelletCounterReleaseValues[ghostIndex])
                {
                    ghost.Release();

                    if (ghostIndex == sharedPelletCounterReleaseValues.Length - 1)
                    {
                        SetSharedPelletCounterActive(false);
                    }
                }
            }
        }

        void IncrementPrivatePelletCounter()
        {
            for (int ghostIndex = 0; ghostIndex < ghosts.Length; ghostIndex++)
            {
                Ghost ghost = ghosts[ghostIndex];
                if (ghost.GetGhostState() == PacManGhostState.Home)
                {
                    ghost.IncrementHousePelletCounter();
                    break;
                }
            }
        }

        void UpdateElroyLevel()
        {
            // Debug.Log($"{gameObject} Updating Elroy Level with pelletsRemaining {pelletsRemaining} with elroy2PelletCount {elroy2PelletCount} and elroy1PelletCount {elroy1PelletCount}");
            int oldElroyLevel = elroyLevel;
            if (pelletsRemaining < elroy2PelletCount) elroyLevel = 2;
            else if (pelletsRemaining < elroy1PelletCount) elroyLevel = 1;
            else elroyLevel = 0;
            if (elroyLevel != oldElroyLevel)
            {
                blinky.SetElroy(elroyLevel);
            }
        }

        public void SetActive(bool active)
        {
            gameObject.SetActive(active);
            foreach (Ghost ghost in ghosts)
            {
                ghost.SetActive(active);
            }
        }

        public void SetKinematic(bool kinematic)
        {
            this.kinematic = kinematic;
            foreach (Ghost ghost in ghosts)
            {
                ghost.SetKinematic(kinematic);
            }
        }

        public Ghost[] Ghosts
        {
            get => ghosts;
        }
    }
}