using System;
using System.Collections.Generic;

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

		Dictionary<EventContainer, ContainerState> states
			= new Dictionary<EventContainer, ContainerState>();
		List<EventContainer> activeContainers
			= new List<EventContainer>();
		HashSet<ContainerState> workingStates
			= new HashSet<ContainerState>();
		HashSet<ContainerState> invalidatedStates
			= new HashSet<ContainerState>();

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

		public EventBus Clone(byte ct, float offsetTime = 0) {
			var r = (EventBus)MemberwiseClone();
			r.prototype = this;
			r.states = new Dictionary<EventContainer, ContainerState>();
			r.activeContainers = new List<EventContainer>();
			r.workingStates = new HashSet<ContainerState>();
			r.invalidatedStates = new HashSet<ContainerState>();
			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(byte ct, EventBus dest) {
			base.CopyTo(dest);
			dest.workingStates.Clear();
			dest.invalidatedStates.Clear();
			RootState.CopyTo(ct, dest.RootState);
			if (ct >= 2) {
				dest.activeContainers.Clear();
				foreach (var c in activeContainers) {
					if (states[c].Working) {
						states[c].CopyTo(ct, dest.states[c]);
						dest.activeContainers.Add(c);
					}
				}
			}
			dest.ValidateStates();
		}

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

		public void NotifyWorkingChanged(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.Values)
				Expand(c);
		}

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

		void EnsureActivity(EventContainer c) {
			if (activeContainers.Contains(c)) return;
			if (RootState.CloneType >= 2) prototype.states[c].CopyTo(RootState.CloneType, states[c]);
			activeContainers.Add(c);
		}

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

		readonly List<StampedEvent> patch = new List<StampedEvent>();
		public void IssuePatch(IEnumerable<StampedEvent> l) {
			patch.AddRange(l);
		}
		public void DoPatch() {
			foreach (var ev in patch) {
				var batch = new EventBatch(ev.Time);
				var batchIndex = events.BinarySearch(batch);
				if (batchIndex >= 0) batch = events[batchIndex];
				else events.Insert(~batchIndex, batch);
				batch.Add(ev);
			}
			patch.Clear();
		}

		public override void ForwardOnceToTime(float toTime, Action<EventBatch> callback = null) {
			if (EventId < events.Count && events[EventId].Time <= toTime) {
				Time = events[EventId].Time;
				var batch = events[EventId];
				foreach (var s in workingStates) s.Handle(null);
				ValidateStates();
				for (var i = 0; i < batch.Count; i++) {
					var ev = batch[i];
					if (ev.Container != null) {
						EnsureActivity(ev.Container);
						states[ev.Container].Handle(ev);
					}
					if (ev.Unstamped is EventContainer) {
						if (ev.Container != null) EnsureActivity((EventContainer)ev.Unstamped);
					}
				}
				ValidateStates();
				EventId++;
			}
			else {
				Time = toTime;
				foreach (var s in workingStates) s.Handle(null);
				ValidateStates();
			}
		}

		private void ValidateStates() {
			foreach (var s in invalidatedStates)
				if (s.Working && !workingStates.Contains(s)) workingStates.Add(s);
				else if (!s.Working && workingStates.Contains(s)) workingStates.Remove(s);
			invalidatedStates.Clear();
		}

		public void BroadcastInit() {
			RootState.BroadcastInit();
		}
		
		public void BroadcastEndUpdate() {
			RootState.BroadcastEndUpdate();
		}

		public void Anchor() {
			RootState.Anchor();
		}
	}
}
