using System.Collections.Generic;

namespace Cryville.Crtr.Event {
	/// <summary>
	/// A time-forward handler of a sequence of events.
	/// </summary>
	/// <typeparam name="T">The event type.</typeparam>
	public abstract class StateBase<T> {
		/// <summary>
		/// The current time of the state in seconds.
		/// </summary>
		public double Time { get; protected set; }
		/// <summary>
		/// The index of the event to be handled next.
		/// </summary>
		public int EventId { get; protected set; }
		/// <summary>
		/// The event count.
		/// </summary>
		public int EventCount { get { return Events.Count; } }
		/// <summary>
		/// The event list.
		/// </summary>
		protected readonly List<T> Events;

		bool breakflag = false;
		
		/// <summary>
		/// Creates an instance of the <see cref="StateBase{T}" /> class.
		/// </summary>
		/// <param name="evs">The event list.</param>
		public StateBase(List<T> evs) {
			Events = evs;
			EventId = 0;
			Time = 0;
		}

		/// <summary>
		/// Creates a copy of the current state.
		/// </summary>
		/// <returns>A copy of the current state.</returns>
		/// <remarks>
		/// <para><see cref="Events" /> is shared across copies.</para>
		/// </remarks>
		public virtual StateBase<T> Clone() {
			return (StateBase<T>)MemberwiseClone();
		}

		/// <summary>
		/// Copies the state to another existing state.
		/// </summary>
		/// <param name="dest">The state to be overridden.</param>
		public virtual void CopyTo(StateBase<T> dest) {
			dest.Time = Time;
			dest.EventId = EventId;
			dest.breakflag = breakflag;
		}

		/// <summary>
		/// Interrupts the forward process.
		/// </summary>
		public virtual void Break() {
			breakflag = true;
		}

		/// <summary>
		/// Walks through all the remaining events.
		/// </summary>
		public void Forward() {
			ForwardToTime(double.PositiveInfinity);
		}

		/// <summary>
		/// Forwards to the next event.
		/// </summary>
		public void ForwardOnce() {
			ForwardOnceToTime(double.PositiveInfinity);
		}

		/// <summary>
		/// Forwards the time by the specified span and walks through all the encountered events.
		/// </summary>
		/// <param name="time">The span in seconds.</param>
		public void ForwardByTime(double time) {
			ForwardToTime(Time + time);
		}

		/// <summary>
		/// Forwards the time by the specified span but walks through at most one event.
		/// </summary>
		/// <param name="time">The span in seconds.</param>
		public void ForwardOnceByTime(double time) {
			ForwardOnceToTime(Time + time);
		}

		/// <summary>
		/// Forwards the time to the specified time and walks through all the encountered events.
		/// </summary>
		/// <param name="toTime">The time in seconds.</param>
		public void ForwardToTime(double toTime) {
			breakflag = false;
			ForwardOnceToTime(Time);
			while (Time < toTime) {
				ForwardOnceToTime(toTime);
				if (breakflag) break;
			}
		}

		/// <summary>
		/// Forwards the time by the specified span and walks through all the encountered events, with a specified step.
		/// </summary>
		/// <param name="time">The span in seconds.</param>
		/// <param name="step">The step in seconds.</param>
		public void ForwardStepByTime(double time, double step) {
			ForwardStepToTime(Time + time, step);
		}

		/// <summary>
		/// Forwards the time to the specified time and walks through all the encountered events, with a specified step.
		/// </summary>
		/// <param name="toTime">The time in seconds.</param>
		/// <param name="step">The step in seconds.</param>
		public void ForwardStepToTime(double toTime, double step) {
			breakflag = false;
			ForwardOnceToTime(Time);
			while (Time < toTime) {
				double next = Time + step;
				ForwardOnceToTime(next < toTime ? next : toTime);
				if (breakflag) break;
			}
		}

		/// <summary>
		/// Forwards the time to the specified time but walks through at most one event.
		/// </summary>
		/// <param name="toTime">The time in seconds.</param>
		public abstract void ForwardOnceToTime(double toTime);
	}
}
