using UnityEngine;

namespace Marro.PacManUdon
{
    public partial class GameManager
    {
        // A note about the quality of the code here:
        // I intended to write this using separate classes per time sequence, until I realized Udon does not support instantiating classes.
        // While I'm not a big fan of using partial classes for this, I'm not redoing this unless there's a major Udon update before I wrap up this project.
        bool currentlyInTimeSequence;
        bool waitingForTimeSequenceFinalize;
        bool jumpingToTimeSequence;
        PacManTimeSequence currentTimeSequence;
        float timeSequenceSecondsPassed;
        int timeSequenceProgress;
        float[] timeSequenceKeyframeTimes;

        private void StartTimeSequence(PacManTimeSequence timeSequence)
        {
            //Debug.Log($"StartTimeSequence: {timeSequence}");

            TimeSequenceTryEndCurrent();

            TimeSequencePrepareForStart(timeSequence);

            currentlyInTimeSequence = true;
            currentTimeSequence = timeSequence;
            timeSequenceProgress = 0;
            timeSequenceSecondsPassed = 0;
            timeSequenceKeyframeTimes = GetTimeSequenceKeyframeTimes(timeSequence);
            TimeSequenceProgressToTime(timeSequenceSecondsPassed);
        }

        private void TimeSequenceTryEndCurrent()
        {
            if (!currentlyInTimeSequence)
            {
                return;
            }

            //Debug.Log($"{gameObject} TimeSequenceEndCurrent");

            jumpingToTimeSequence = true;
            TimeSequenceProgressToTime(100000f);

            if (waitingForTimeSequenceFinalize)
            {
                TimeSequenceExecuteFinalize(currentTimeSequence);
            }

            jumpingToTimeSequence = false;
        }

        private void TimeSequenceUpdate(float deltaSeconds)
        {
            if (!currentlyInTimeSequence)
            {
                return;
            }

            TimeSequenceProgressToTime(timeSequenceSecondsPassed + deltaSeconds);
        }

        private void TimeSequenceSkipToNextStep()
        {
            //Debug.Log($"{gameObject} TimeSequenceSkipToNextStep");
            if (timeSequenceProgress < timeSequenceKeyframeTimes.Length)
            {
                TimeSequenceProgressToTime(timeSequenceKeyframeTimes[timeSequenceProgress]);
            }
            else
            {
                Debug.LogWarning($"{gameObject} Tried skipping to next time sequence step when already on last step!");
                currentlyInTimeSequence = false;
            }
        }

        private void TimeSequenceProgressToTime(float seconds)
        {
            timeSequenceSecondsPassed = seconds;
            while (timeSequenceSecondsPassed >= timeSequenceKeyframeTimes[timeSequenceProgress])
            {
                TimeSequenceExecuteStep(currentTimeSequence, timeSequenceProgress);

                timeSequenceProgress += 1;
                if (timeSequenceProgress >= timeSequenceKeyframeTimes.Length)
                {
                    currentlyInTimeSequence = false;
                    TimeSequenceFinish(currentTimeSequence);
                    break;
                }
            }
        }

        private void TimeSequenceFinish(PacManTimeSequence timeSequence)
        {
            TimeSequenceExecuteFinalize(timeSequence);
            if (!jumpingToTimeSequence)
            {
                TimeSequenceExecuteFinished(timeSequence);
            }
        }

        private void TimeSequenceSyncWithRemote(PacManTimeSequence remoteCurrentTimeSequence, float timeSequenceSecondsPassedRemote)
        {
            // If our current time sequence state is incompatible with the remote time sequence, kill it
            if (currentlyInTimeSequence && remoteCurrentTimeSequence != currentTimeSequence
                || timeSequenceSecondsPassedRemote < timeSequenceSecondsPassed)
            {
                currentlyInTimeSequence = false;
            }

            // If we're not/no longer in a time sequence, start the remote's time sequence
            if (!currentlyInTimeSequence)
            {
                StartTimeSequence(remoteCurrentTimeSequence);
            }

            // Jump our progress to match the one on the remote
            TimeSequenceProgressToTime(timeSequenceSecondsPassedRemote);
        }

        #region Events

        public void JumpToTimeSequenceAttractScreenIntroduction()
        {
            StartTimeSequence(PacManTimeSequence.AttractScreenIntroduction);
        }

        public void JumpToTimeSequenceAttractScreenDemo()
        {
            StartTimeSequence(PacManTimeSequence.AttractScreenDemo);
        }

        public void JumpToTimeSequenceAttractScreenWaitToRestart()
        {
            StartTimeSequence(PacManTimeSequence.AttractScreenWaitToRestart);
        }

        public void JumpToTimeSequenceBoardClear()
        {
            StartTimeSequence(PacManTimeSequence.BoardClear);
        }

        public void JumpToTimeSequenceGameOver()
        {
            StartTimeSequence(PacManTimeSequence.GameOver);
        }

        public void JumpToTimeSequenceGhostCaught()
        {
            StartTimeSequence(PacManTimeSequence.GhostCaught);
        }

        public void JumpToTimeSequenceIntermission1()
        {
            StartTimeSequence(PacManTimeSequence.Intermission1);
        }

        public void JumpToTimeSequenceIntermission2()
        {
            StartTimeSequence(PacManTimeSequence.Intermission2);
        }

        public void JumpToTimeSequenceIntermission3()
        {
            StartTimeSequence(PacManTimeSequence.Intermission3);
        }

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

        public void JumpToTimeSequenceRestartLevel()
        {
            StartTimeSequence(PacManTimeSequence.RestartLevel);
        }

        public void JumpToTimeSequenceStartNewGame()
        {
            StartTimeSequence(PacManTimeSequence.StartNewGame);
        }

        public void JumpToTimeSequenceStartNewLevel()
        {
            StartTimeSequence(PacManTimeSequence.StartNewLevel);
        }

        public void JumpToTimeSequenceWaitForStart()
        {
            StartTimeSequence(PacManTimeSequence.WaitForStart);
        }

        public void JumpToTimeSequenceWaitForStartTimeout()
        {
            StartTimeSequence(PacManTimeSequence.WaitForStartTimeout);
        }

        #endregion

        #region Jump tables

        private void TimeSequencePrepareForStart(PacManTimeSequence timeSequence)
        {
            switch (timeSequence)
            {
                default:
                    Debug.LogError($"{gameObject} No time sequence start known for sequence {currentTimeSequence}");
                    break;
                case PacManTimeSequence.AttractScreenIntroduction:
                case PacManTimeSequence.AttractScreenDemo:
                case PacManTimeSequence.StartNewGame:
                case PacManTimeSequence.WaitForStart:
                case PacManTimeSequence.StartNewLevel:
                case PacManTimeSequence.Intermission1:
                case PacManTimeSequence.Intermission2:
                case PacManTimeSequence.Intermission3:
                case PacManTimeSequence.AttractScreenWaitToRestart:
                case PacManTimeSequence.WaitForStartTimeout:
                case PacManTimeSequence.GhostCaught:
                case PacManTimeSequence.GameOver:
                case PacManTimeSequence.PacManCaught:
                case PacManTimeSequence.BoardClear:
                case PacManTimeSequence.RestartLevel:
                    // These don't have start logic
                    break;
            }
        }

        private void TimeSequenceExecuteStep(PacManTimeSequence timeSequence, int sequenceProgress)
        {
            //Debug.Log($"{gameObject} Triggered time sequence step for sequence {currentTimeSequence} with progress {sequenceProgress}");
            switch (timeSequence)
            {
                default:
                    Debug.LogError($"{gameObject} No time sequence keyframes known for sequence {currentTimeSequence}");
                    break;
                case PacManTimeSequence.AttractScreenIntroduction:
                    TimeSequenceStepAttractScreenIntroduction(sequenceProgress);
                    break;
                case PacManTimeSequence.AttractScreenDemo:
                    TimeSequenceStepAttractScreenDemo(sequenceProgress);
                    break;
                case PacManTimeSequence.WaitForStart:
                    TimeSequenceStepWaitForStart(sequenceProgress);
                    break;
                case PacManTimeSequence.StartNewGame:
                    TimeSequenceStepStartNewGame(sequenceProgress);
                    break;
                case PacManTimeSequence.BoardClear:
                    TimeSequenceStepBoardClear(sequenceProgress);
                    break;
                case PacManTimeSequence.StartNewLevel:
                    TimeSequenceStepStartNewLevel(sequenceProgress);
                    break;
                case PacManTimeSequence.GhostCaught:
                    TimeSequenceStepGhostCaught(sequenceProgress);
                    break;
                case PacManTimeSequence.PacManCaught:
                    TimeSequenceStepPacManCaught(sequenceProgress);
                    break;
                case PacManTimeSequence.RestartLevel:
                    TimeSequenceStepRestartLevel(sequenceProgress);
                    break;
                case PacManTimeSequence.GameOver:
                    TimeSequenceStepGameOver(sequenceProgress);
                    break;
                case PacManTimeSequence.Intermission1:
                    TimeSequenceStepIntermission1(sequenceProgress);
                    break;
                case PacManTimeSequence.Intermission2:
                    TimeSequenceStepIntermission2(sequenceProgress);
                    break;
                case PacManTimeSequence.Intermission3:
                    TimeSequenceStepIntermission3(sequenceProgress);
                    break;
                case PacManTimeSequence.AttractScreenWaitToRestart:
                case PacManTimeSequence.WaitForStartTimeout:
                    // These don't have steps
                    break;
            }
        }

        private void TimeSequenceExecuteFinalize(PacManTimeSequence timeSequence)
        {
            //Debug.Log($"{gameObject} Triggered time sequence finalize for sequence {currentTimeSequence}");
            switch (timeSequence)
            {
                default:
                    Debug.LogError($"{gameObject} No time sequence finalize known for sequence {currentTimeSequence}");
                    break;
                case PacManTimeSequence.AttractScreenIntroduction:
                    TimeSequenceFinalizeAttractScreenIntroduction();
                    break;
                case PacManTimeSequence.WaitForStart:
                    TimeSequenceFinalizeWaitForStart();
                    break;
                case PacManTimeSequence.StartNewGame:
                    TimeSequenceFinalizeStartNewGame();
                    break;
                case PacManTimeSequence.StartNewLevel:
                    TimeSequenceFinalizeStartNewLevel();
                    break;
                case PacManTimeSequence.GhostCaught:
                    TimeSequenceFinalizeGhostCaught();
                    break;
                case PacManTimeSequence.RestartLevel:
                    TimeSequenceFinalizeRestartLevel();
                    break;
                case PacManTimeSequence.Intermission2:
                    TimeSequenceFinalizeIntermission2();
                    break;
                case PacManTimeSequence.Intermission3:
                    TimeSequenceFinalizeIntermission3();
                    break;
                case PacManTimeSequence.AttractScreenDemo:
                case PacManTimeSequence.WaitForStartTimeout:
                case PacManTimeSequence.AttractScreenWaitToRestart:
                case PacManTimeSequence.GameOver:
                case PacManTimeSequence.Intermission1:
                case PacManTimeSequence.PacManCaught:
                case PacManTimeSequence.BoardClear:
                    // These don't have a finalize
                    break;
            }
        }

        private void TimeSequenceExecuteFinished(PacManTimeSequence timeSequence)
        {
            //Debug.Log($"{gameObject} Triggered time sequence finished for sequence {currentTimeSequence}");
            switch (timeSequence)
            {
                default:
                    Debug.LogError($"{gameObject} No time sequence finish known for sequence {currentTimeSequence}");
                    break;
                case PacManTimeSequence.AttractScreenIntroduction:
                    TimeSequenceFinishedAttractScreenIntroduction();
                    break;
                case PacManTimeSequence.AttractScreenDemo:
                    TimeSequenceFinishedAttractScreenDemo();
                    break;
                case PacManTimeSequence.AttractScreenWaitToRestart:
                    TimeSequenceFinishedAttractScreenWaitToRestart();
                    break;
                case PacManTimeSequence.WaitForStartTimeout:
                    TimeSequenceFinishedWaitForStartTimeout();
                    break;
                case PacManTimeSequence.BoardClear:
                    TimeSequenceFinishedBoardClear();
                    break;
                case PacManTimeSequence.PacManCaught:
                    TimeSequenceFinishedPacManCaught();
                    break;
                case PacManTimeSequence.GameOver:
                    TimeSequenceFinishedGameOver();
                    break;
                case PacManTimeSequence.Intermission1:
                    TimeSequenceFinishedIntermission1();
                    break;
                case PacManTimeSequence.Intermission2:
                    TimeSequenceFinishedIntermission2();
                    break;
                case PacManTimeSequence.Intermission3:
                    TimeSequenceFinishedIntermission3();
                    break;
                case PacManTimeSequence.RestartLevel:
                case PacManTimeSequence.StartNewLevel:
                case PacManTimeSequence.GhostCaught:
                case PacManTimeSequence.WaitForStart:
                case PacManTimeSequence.StartNewGame:
                    // These don't have a finished
                    break;
            }
        }

        private float[] GetTimeSequenceKeyframeTimes(PacManTimeSequence timeSequence)
        {
            switch (timeSequence)
            {
                default:
                    Debug.LogError($"{gameObject} No time sequence keyframe times known for sequence {timeSequence}");
                    return new float[0];
                case PacManTimeSequence.AttractScreenIntroduction:
                    return DeltaToAbsolute(new float[] { 0, 0.032f, 1f, 1f, .5f, .5f, 1f, .5f, .5f, 1f, .5f, .5f, 1f, .5f, 1f, 1f, 1f,
                                                         5f, 0.2f, 2f, 0.91667f, 2f, 0.91667f, 2f, 0.91667f, 2f, 0.91667f });
                case PacManTimeSequence.AttractScreenDemo:
                    return DeltaToAbsolute(new float[] { 0, 0.016f, 0.05f, 0.16f, 0.33f, 1.85f, 54f });
                case PacManTimeSequence.AttractScreenWaitToRestart:
                    return DeltaToAbsolute(new float[] { 0, 2f });
                case PacManTimeSequence.WaitForStart:
                    return DeltaToAbsolute(new float[] { 0, 0.016f });
                case PacManTimeSequence.WaitForStartTimeout:
                    return DeltaToAbsolute(new float[] { 0, 5f });
                case PacManTimeSequence.StartNewGame:
                    return DeltaToAbsolute(new float[] { 0, 0.016f, 2.2f, 0.032f, 0.032f, 1.92f, 0.032f });
                case PacManTimeSequence.BoardClear:
                    return DeltaToAbsolute(new float[] { 0, 2f, 0.016f, 1.6f - 0.016f, 0.016f, 0.032f, 0.3f });
                case PacManTimeSequence.StartNewLevel:
                    return DeltaToAbsolute(new float[] { 0, 0.064f, 0.032f, 1.85f, 0.016f });
                case PacManTimeSequence.GhostCaught:
                    return DeltaToAbsolute(new float[] { 0, 0.91667f });
                case PacManTimeSequence.PacManCaught:
                    return DeltaToAbsolute(new float[] { 0, 1, 0.35f, 2.40f });
                case PacManTimeSequence.RestartLevel:
                    return DeltaToAbsolute(new float[] { 0, 0.016f, 0.064f, 0.032f, 1.85f, 0.016f });
                case PacManTimeSequence.GameOver:
                    return DeltaToAbsolute(new float[] { 0, 1.95f });
                case PacManTimeSequence.Intermission1:
                    return DeltaToAbsolute(new float[] { 0, 0.316f, 0.3f, 3.96f, 2.25f, 3.93f });
                case PacManTimeSequence.Intermission2:
                    return DeltaToAbsolute(new float[] { 0, 0.25f, 0.083f, 0.3f, 1.43f, 2.5f, 1.816f, 1.25f, 0.017f, 1f, 1.966f, 0.033f });
                case PacManTimeSequence.Intermission3:
                    return DeltaToAbsolute(new float[] { 0, 0.316f, 0.7f, 3.35f, 0.83f, 3.67f });
            }
        }

        private static float[] DeltaToAbsolute(float[] delta)
        {
            if (delta.Length < 1)
            {
                return new float[0];
            }

            float[] absolute = new float[delta.Length];
            absolute[0] = delta[0];
            for (int i = 1; i < delta.Length; i++)
            {
                absolute[i] = delta[i] + absolute[i - 1];
            }
            return absolute;
        }

        #endregion
    }
}