using System;
using UnityEngine;
using VRC.SDK3.Data;

namespace Marro.PacManUdon
{
    public class GhostManager : SyncedObject
    {
        [NonSerialized] public GameManager gameController;

        private Ghost[] ghosts;
        private Ghost blinky;

        private CollisionManager collisionManager;

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

        private float powerPelletDuration;
        private float[] scatterPattern;
        private float pelletTimeoutLimit;

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

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

        // Scattering logic
        private float scatterCounter;
        private int scatterPatternIndex;

        // Elroy logic
        public int elroyLevel;

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

        private bool frozen;
        private bool kinematic;

        public void Initialize(Transform[] ghostStarts, Transform[] ghostTargets, PacMan pacMan, CollisionManager collisionManager, GameManager gameController)
        {
            this.gameController = gameController;
            this.collisionManager = collisionManager;

            ghosts = transform.GetComponentsInChildren<Ghost>(true);
            blinky = ghosts[0];
            for (int ghostIndex = 0; ghostIndex < ghosts.Length; ghostIndex++)
            {
                Transform startTransform = ghostStarts[ghostIndex];
                Vector2 homePosition = ghostTargets[0].localPosition;
                Vector2 idlePosition1 = ghostTargets[1 + ghostIndex * 3].localPosition;
                Vector2 idlePosition2 = ghostTargets[2 + ghostIndex * 3].localPosition;
                Vector2 cornerPosition = ghostTargets[3 + ghostIndex * 3].localPosition;

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

        public void RestartLevel(bool afterLifeLost = false)
        {
            powerPelletActive = false;
            scatterCounter = 0;
            scatterPatternIndex = 0;
            sharedPelletCounter = 0;
            pelletTimeout = 0;
            elroyLevel = 0;
            kinematic = false;

            if (afterLifeLost)
            {
                SetSharedPelletCounterActive(true);
            }

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

            SetScattering(true, reverseDirection: false);

            SetSharedPelletCounterActive(false);
            UpdateElroyLevel(collisionManager.PelletCount);
            foreach (Ghost ghost in ghosts)
            {
                ghost.ResetHousePelletCounter();
            }
        }

        public override void SyncedUpdate()
        {
            if (frozen || kinematic)
            {
                return;
            }

            UpdatePelletTimeout();

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

        void UpdateScattering()
        {
            scatterCounter += networkManager.SyncedDeltaTime;
            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 -= networkManager.SyncedDeltaTime;
            if (powerPelletCountdown <= 0)
            {
                gameController.EndPowerPellet(); // End power pellet
                SetPowerPellet(false);
            }
            else if (!blinkingActivated && powerPelletCountdown <= 2.1166667)
            {
                SetGhostBlinking(true);
            }

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

        void UpdatePelletTimeout()
        {
            pelletTimeout += networkManager.SyncedDeltaTime;
            
            if (pelletTimeout >= pelletTimeoutLimit)
            {
                pelletTimeout -= pelletTimeoutLimit;

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

        public void GhostCaught(Ghost ghost)
        {
            if (gameController.GameState == PacManGameState.AttractMode)
            {
                gameController.GhostCaught(0);
                return;
            }

            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 (var ghost in ghosts)
            {
                ghost.SetWhite(white);
            }
        }

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

            if (powerPelletActive)
            {
                powerPelletCountdown = powerPelletDuration;
                powerPelletMultiplier = 0;
                SetGhostBlinking(false);
                foreach (var 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 (var ghost in ghosts)
            {
                ghost.SetFrozen(frozen, ignoreIfCaught: ignoreIfCaught);
            }
        }

        public void SetLevel(int level)
        {
            //Debug.Log($"GhostManager: SetLevel {level}");
            SetLevelConstants(level);

            int[] privatePelletCounterReleaseValues = PacManConstants.GetGhostHousePrivatePelletCounterLimitForLevel(level);
            for (int i = 0; i < ghosts.Length; i++)
            {
                ghosts[i].SetHousePelletCounterLimit(privatePelletCounterReleaseValues[i]);
                ghosts[i].UpdateSpeed();
            }
        }

        private void SetLevelConstants(int 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);
        }

        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 (var ghost in ghosts)
            {
                if (ghost == blinky && elroyLevel > 0) // Once blinky is elroy he no longer scatters
                {
                    continue;
                }
                ghost.SetScattering(scattering, reverseDirection);
            }
        }

        /// <summary>
        /// Whether to use the shared pellet counter for ghost exiting.
        /// Should be called before ghosts are reset.
        /// </summary>
        void SetSharedPelletCounterActive(bool active)
        {
            //Debug.Log($"{gameObject} SetSharedPelletCounterActive {active}");
            sharedPelletCounterActive = active;
            foreach (var ghost in ghosts)
            {
                ghost.SetHousePelletCounterActive(!active);
            }
        }

        public void PelletConsumed(int pelletsRemainingCount)
        {
            pelletTimeout = 0;

            UpdateElroyLevel(pelletsRemainingCount);

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

        void IncrementSharedPelletCounter()
        {
            sharedPelletCounter++;
            //Debug.Log($"Incremented shared pellet counter to {sharedPelletCounter}");
            for (int ghostIndex = 0; ghostIndex < sharedPelletCounterReleaseValues.Length; ghostIndex++)
            {
                var 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++)
            {
                var ghost = ghosts[ghostIndex];
                if (ghost.GetGhostState() == PacManGhostState.Home)
                {
                    ghost.IncrementHousePelletCounter();
                    break;
                }
            }
        }

        void UpdateElroyLevel(int pelletsRemainingCount)
        {
            // Debug.Log($"{gameObject} Updating Elroy Level with pelletsRemaining {pelletsRemaining} with elroy2PelletCount {elroy2PelletCount} and elroy1PelletCount {elroy1PelletCount}");
            var oldElroyLevel = elroyLevel;

            if (pelletsRemainingCount < elroy2PelletCount) elroyLevel = 2;
            else if (pelletsRemainingCount < elroy1PelletCount) elroyLevel = 1;
            else elroyLevel = 0;

            if (elroyLevel != oldElroyLevel)
            {
                blinky.SetElroy(elroyLevel);
            }
        }

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

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

        public override void CollectSyncedData(byte[] data, ref int index, NetworkEventType eventType)
        {
            // Power Pellet logic
            data.Append(powerPelletActive, ref index);
            data.Append(powerPelletCountdown, ref index);
            data.AppendAsByte(powerPelletMultiplier, ref index);

            // Blink logic
            data.Append(blinkingActivated, ref index);
            data.Append(blinkCountdown, ref index);
            data.Append(blinkCurrentlyWhite, ref index);

            // Scattering logic
            data.Append(scatterCounter, ref index);
            data.AppendAsByte(scatterPatternIndex, ref index);

            // Elroy logic
            data.AppendAsByte(elroyLevel, ref index);

            // Ghost house logic
            data.Append(sharedPelletCounterActive, ref index);
            data.AppendAsByte(sharedPelletCounter, ref index);
            data.Append(pelletTimeout, ref index);

            data.Append(frozen, ref index);
            data.Append(kinematic, ref index);

            data.AppendAsByte(gameController.Level, ref index);

            foreach (var ghost in ghosts)
            {
                ghost.CollectSyncedData(data, ref index, eventType);
            }
        }

        public override bool WriteSyncedData(byte[] data, ref int index, NetworkEventType eventType)
        {
            // Power Pellet logic
            powerPelletActive = data.ReadBool(ref index);
            powerPelletCountdown = data.ReadFloat(ref index);
            powerPelletMultiplier = data.ReadByte(ref index);

            // Blink logic
            blinkingActivated = data.ReadBool(ref index);
            blinkCountdown = data.ReadFloat(ref index);
            blinkCurrentlyWhite = data.ReadBool(ref index);

            // Scattering logic
            scatterCounter = data.ReadFloat(ref index);
            scatterPatternIndex = data.ReadByte(ref index);

            // Elroy logic
            elroyLevel = data.ReadByte(ref index);

            // Ghost house logic
            sharedPelletCounterActive = data.ReadBool(ref index);
            sharedPelletCounter = data.ReadByte(ref index);
            pelletTimeout = data.ReadFloat(ref index);

            frozen = data.ReadBool(ref index);
            kinematic = data.ReadBool(ref index);

            var level = data.ReadByte(ref index);
            SetLevelConstants(level);

            foreach (var ghost in ghosts)
            {
                ghost.WriteSyncedData(data, ref index, eventType);
            }

            return true;
        }

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