using Cryville.Crtr.Ruleset;
using Cryville.Crtr.Skin;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

namespace Cryville.Crtr.Event {
	public class EventBus : StateBase<EventBatch>, IDisposable {
		public ContainerState RootState {
			get;
			private set;
		}

		Dictionary<EventContainer, ContainerState> states
			= new Dictionary<EventContainer, ContainerState>();
		HashSet<ContainerState> activeStates
			= new HashSet<ContainerState>();
		HashSet<ContainerState> invalidatedStates
			= new HashSet<ContainerState>();
		public int ActiveStateCount { get { return activeStates.Count; } }

		public EventBus(ContainerState root, List<EventBatch> b) : base(b) {
			RootState = root;
			Expand();
			AttachBus();
		}

		public EventBus Clone(byte ct, float offsetTime = 0) {
			var r = (EventBus)MemberwiseClone();
			r.states = new Dictionary<EventContainer, ContainerState>();
			r.activeStates = new HashSet<ContainerState>();
			r.invalidatedStates = new HashSet<ContainerState>();
			r.tempEvents = new List<StampedEvent.Temporary>();
			r.Time += offsetTime;
			r.RootState = RootState.Clone(ct);
			r.Expand();
			r.AttachBus();
			foreach (var s in r.states) r.invalidatedStates.Add(s.Value);
			r.ValidateStates();
			return r;
		}

		public void CopyTo(EventBus dest) {
			base.CopyTo(dest);
			dest.activeStates.Clear();
			dest.invalidatedStates.Clear();
			RootState.CopyTo(dest.RootState);
			dest.ValidateStates();
		}

		public void Dispose() {
			RootState.Dispose();
		}
		public void DisposeAll() {
			RootState.DisposeAll();
		}

		public void NotifyActiveChanged(ContainerState state) {
			if (!invalidatedStates.Contains(state)) invalidatedStates.Add(state);
		}

		void Expand(ContainerState s = null) {
			if (s == null) {
				states.Clear();
				s = RootState;
			}
			AddState(s);
			foreach (var c in s.Children)
				Expand(c.Value);
		}

		public void AddState(ContainerState s) {
			states[s.Container] = s;
			s.Bus = this;
		}

		void AttachBus() {
			foreach (var s in states)
				s.Value.Bus = this;
		}
		
		public void AttachSystems(PdtSkin skin, Judge judge) {
			foreach (var s in states)
				s.Value.AttachSystems(skin, judge);
		}

		List<StampedEvent.Temporary> tempEvents = new List<StampedEvent.Temporary>();
		public void PushTempEvent(StampedEvent.Temporary ev) {
			var index = tempEvents.BinarySearch(ev);
			if (index < 0) index = ~index;
			tempEvents.Insert(index, ev);
		}

		readonly StampedEvent.Temporary _dummyEvent = new StampedEvent.Temporary();
		public void StripTempEvents() {
			_dummyEvent.Time = Time;
			var index = tempEvents.BinarySearch(_dummyEvent);
			if (index < 0) index = ~index;
			for (var i = index - 1; i >= 0; i--) {
				var ev = tempEvents[i];
				tempEvents.RemoveAt(i);
				if (ev.CanDiscard) {
					if (ev.Container != null) {
						states[ev.Container].Discard(ev);
					}
				}
				else {
					ev.Time = Time;
					PushTempEvent(ev);
				}
			}
		}

		public override void ForwardOnceToTime(double toTime) {
			double time1 = EventId < Events.Count ? Events[EventId].Time : double.PositiveInfinity;
			double time2 = tempEvents.Count > 0 ? tempEvents[0].Time : double.PositiveInfinity;
			double time0 = Math.Min(time1, time2);
			if (time0 <= toTime && time0 != double.PositiveInfinity) {
				Time = time0;
				foreach (var s in activeStates) s.Handle(null);
				ValidateStates();
				if (time1 == time0) {
					var batch = Events[EventId];
					for (var i = 0; i < batch.Count; i++) {
						var ev = batch[i];
						HandleTempEvents(time0, ev.Priority);
						if (ev is StampedEvent.ClipBehind) {
							var cevs = ev.Origin.Coevents;
							if (cevs != null) foreach (var cev in cevs) {
								if (cev.Container == null) continue;
								states[cev.Container].Handle(cev);
							}
							states[(EventContainer)ev.Origin.Unstamped].PhysicalActive = true;
						}
						else if (ev is StampedEvent.ClipAhead) {
							states[(EventContainer)ev.Origin.Unstamped].PhysicalActive = false;
						}
						else if (ev.Container != null) {
							states[ev.Container].Handle(ev);
						}
					}
					EventId++;
				}
				HandleTempEvents(time0);
			}
			else {
				Time = toTime;
				foreach (var s in activeStates) s.Handle(null);
			}
			ValidateStates();
		}
		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		void HandleTempEvents(double time, int maxPriority = int.MaxValue) {
			while (tempEvents.Count > 0) {
				var ev = tempEvents[0];
				if (ev.Time != time || ev.Priority >= maxPriority) break;
				if (ev.Container != null) {
					states[ev.Container].Handle(ev);
				}
				tempEvents.RemoveAt(0);
			}
		}

		private void ValidateStates() {
			foreach (var s in invalidatedStates)
				if (s.Active && !activeStates.Contains(s)) activeStates.Add(s);
				else if (!s.Active && activeStates.Contains(s)) activeStates.Remove(s);
			invalidatedStates.Clear();
		}
		
		public void BroadcastPreInit() {
			RootState.BroadcastPreInit();
		}
		public void BroadcastPostInit() {
			RootState.BroadcastPostInit();
		}

		public void EndPreGraphicalUpdate() {
			RootState.EndPreGraphicalUpdate();
			ClearTempEvents();
		}
		public void EndGraphicalUpdate() {
			RootState.EndGraphicalUpdate();
			ClearTempEvents();
		}

		void ClearTempEvents() {
			foreach (var ev in tempEvents) {
				if (ev.Container != null) {
					states[ev.Container].Discard(ev);
				}
			}
			tempEvents.Clear();
		}

		public void PreAnchor() {
			if (RootState.Handler.Alive) RootState.PreAnchor();
		}

		public void Anchor() {
			if (RootState.Handler.Alive) RootState.Anchor();
		}
	}
}
