// Represents a single set of blocks to run in order, waiting for each group's completion before
// moving on to the next one.

using Sandbox.ModAPI.Ingame;
using System.Collections.Generic;
using System.Linq;

namespace IngameScript
{
    partial class Program
    {
        public class Sequencer
        {
            public bool Running { get; private set; }
            public string Name { get; private set; }

            private IConsole _console;

            private SortedDictionary<int, List<ISequenceable>> _sequence = new SortedDictionary<int, List<ISequenceable>>();

            public Sequencer(IConsoleProgram program, string name)
            {
                Name = name;
                _console = new PrefixedConsole(program.Console, Name);
            }

            public void AddBlock(ISequenceable block)
            {
                if (!_sequence.ContainsKey(block.Step)) _sequence[block.Step] = new List<ISequenceable>();
                _sequence[block.Step].Add(block);
            }

            // To activate the Sequencer, call this once, then call `MoveNext()` once per tick
            //   on the returned object until it returns false.
            //   (then be sure to call `Dispose() on that object`)
            public IEnumerator<bool> RunSequence(bool deploy = true)
            {
                if (Running)
                {
                    _console.Print("Already running, ignoring invocation.");
                    yield break;
                }

                // This is a bit convoluted, but we're extracting the iterator for use in the foreach directly.
                // This allows us to iterate in reverse when reversing/"closing" the sequence.
                IEnumerable<KeyValuePair<int, List<ISequenceable>>> steps = deploy ? _sequence : _sequence.Reverse();
                foreach (KeyValuePair<int, List<ISequenceable>> kvp in steps)
                {
                    List<ISequenceable> blocks = kvp.Value;
                    List<IEnumerator<bool>> subJobs = new List<IEnumerator<bool>>();
                    Running = true;

                    foreach (ISequenceable block in blocks)
                    {
                        // TODO: add some sort of handling for block.Running == true, maybe?
                        // It *should* be an impossible case, but...
                        subJobs.Add(block.Run(deploy));
                    }

                    while (Running)
                    {
                        Running = false;
                        foreach (IEnumerator<bool> subJob in subJobs)
                        {
                            if (subJob.MoveNext()) Running = true;
                        }
                        yield return true;
                    }
                    foreach (IEnumerator<bool> subJob in subJobs) subJob.Dispose(); // clean up after ourselves
                }
            }
        }
    }
}