using System.Collections;
using System;

namespace Strawberry
{
	public class Scene
	{
		public float TimeStarted { get; private set; }

		private List<Entity> entities = new .() ~ delete _;
		private HashSet<Entity> toRemove = new .() ~ delete _;
		private HashSet<Entity> toAdd = new .() ~ delete _;
		private Dictionary<Type, List<Component>> componentTracker = new .() ~ DeleteDictionaryAndValues!(_);

		public this()
		{
			for (let type in Tracker.AssignmentLists.Keys)
				componentTracker.Add(type, new List<Component>());
			for (let type in Tracker.Interfaces)
				componentTracker.Add(type, new List<Component>());
		}

		public ~this()
		{
			for (var e in entities)
				if (e.DeleteOnRemove)
					delete e;

			for (var e in toAdd)
				if (e.DeleteOnRemove)
					delete e;
		}

		public virtual void Started()
		{
			TimeStarted = Time.Elapsed;
		}

		public virtual void Update()
		{
			{
				delegate void(IEarlyUpdate) early = scope (u) => u.EarlyUpdate();
				delegate void(IUpdate) update = scope (u) => u.Update();
				delegate void(ILateUpdate) late = scope (u) => u.LateUpdate();

				ForEach<IEarlyUpdate>(early);
				ForEach<IUpdate>(update);
				ForEach<ILateUpdate>(late);
			}
			
			UpdateLists();
		}

		public virtual void Draw()
		{
			ForEach<IDraw>(scope (d) => d.Draw());
		}

		/*
			Entities
		*/

		public Entity Add(Entity e)
		{
			if (e.Scene == null)
				toAdd.Add(e);
			return e;
		}

		public Entity Remove(Entity e)
		{
			if (e.Scene == this)
				toRemove.Add(e);
			return e;
		}

		private void UpdateLists()
		{
			if (toRemove.Count > 0)
			{
				for (let e in toRemove)
				{
					entities.Remove(e);
					e.[Friend]Removed();
					if (e.DeleteOnRemove)
						delete e;
				}

				toRemove.Clear();
			}

			if (toAdd.Count > 0)
			{
				for (let e in toAdd)
				{
					entities.Add(e);
					e.[Friend]Added(this);
				}
				toAdd.Clear();
			}

			for (let e in entities)
				e.[Friend]UpdateLists();
			for (let e in entities)
				e.[Friend]AwakeCheck();
		}

		/*
			Tracking
		*/

		private void TrackComponent(Component c)
		{
			for (let t in Tracker.AssignmentLists[c.GetType()])
				componentTracker[t].Add(c);
		}

		private void UntrackComponent(Component c)
		{
			for (let t in Tracker.AssignmentLists[c.GetType()])
				componentTracker[t].Remove(c);
		}

		/*
			Time
		*/

		public float TimeElapsed =>	Time.Elapsed - TimeStarted;
		public float PreviousTimeElapsed =>	Time.PreviousElapsed - TimeStarted;

		public bool TimeOnInterval(float interval, float offset = 0)
		{
			return (int)((TimeElapsed - offset) / interval) != (int)((PreviousTimeElapsed - offset) / interval);
		}

		public bool TimeBetweenInterval(float interval, float offset = 0)
		{
			return (TimeElapsed - offset) % (interval * 2) >= interval;
		}

		/*
			Finding Components
		*/

		public int Count<T>() where T : Component
		{
			return componentTracker[typeof(T)].Count;
		}

		public int Count<T>(delegate bool(T) condition) where T : Component
		{
			int count = 0;
			for (T c in componentTracker[typeof(T)])
				if (condition(c))
					count++;
			return count;
		}

		public bool Check<T>(delegate bool(T) condition) where T : Component
		{
			for (T c in componentTracker[typeof(T)])
				if (condition(c))
					return true;
			return false;
		}


		public bool Check<T>(Point point) where T : Component, IHasHitbox
		{
			for (T c in componentTracker[typeof(T)])
				if (c.Hitbox.Check(point))
					return true;
			return false;
		}

		public bool Check<T>(Point point, delegate bool(T) condition) where T : Component, IHasHitbox
		{
			for (T c in componentTracker[typeof(T)])
				if (c.Hitbox.Check(point) && condition(c))
					return true;
			return false;
		}

		public bool Check<T>(Rect rect) where T : Component, IHasHitbox
		{
			for (T c in componentTracker[typeof(T)])
				if (c.Hitbox.Check(rect))
					return true;
			return false;
		}

		public bool Check<T>(Rect rect, delegate bool(T) condition) where T : Component, IHasHitbox
		{
			for (T c in componentTracker[typeof(T)])
				if (c.Hitbox.Check(rect) && condition(c))
					return true;
			return false;
		}

		public T First<T>() where T : Component
		{
			for (T c in componentTracker[typeof(T)])
				return c;
			return null;
		}

		public T First<T>(delegate bool(T) condition) where T : Component
		{
			for (T c in componentTracker[typeof(T)])
				if (condition(c))
					return c;
			return null;
		}

		public T First<T>(Point point) where T : Component, IHasHitbox
		{
			for (T c in componentTracker[typeof(T)])
				if (c.Hitbox.Check(point))
					return c as T;
			return null;
		}

		public T First<T>(Point point, delegate bool(T) condition) where T : Component, IHasHitbox
		{
			for (T c in componentTracker[typeof(T)])
				if (c.Hitbox.Check(point) && condition(c))
					return c as T;
			return null;
		}

		public T First<T>(Rect rect) where T : Component, IHasHitbox
		{
			for (T c in componentTracker[typeof(T)])
				if (c.Hitbox.Check(rect))
					return c as T;
			return null;
		}

		public T First<T>(Rect rect, delegate bool(T) condition) where T : Component, IHasHitbox
		{
			for (T c in componentTracker[typeof(T)])
				if (c.Hitbox.Check(rect) && condition(c))
					return c as T;
			return null;
		}

		public List<T> All<T>(List<T> into) where T : Component
		{
			for (T c in componentTracker[typeof(T)])
				into.Add(c as T);
			return into;
		}

		public List<T> All<T>(List<T> into, delegate bool(T) condition) where T : Component
		{
			for (T c in componentTracker[typeof(T)])
				if (condition(c))
					into.Add(c as T);
			return into;
		}

		public List<T> All<T>(Point point, List<T> into) where T : Component, IHasHitbox
		{
			for (T c in componentTracker[typeof(T)])
				if (c.Hitbox.Check(point))
					into.Add(c as T);
			return into;
		}

		public List<T> All<T>(Point point, List<T> into, delegate bool(T) condition) where T : Component, IHasHitbox
		{
			for (T c in componentTracker[typeof(T)])
				if (c.Hitbox.Check(point) && condition(c))
					into.Add(c as T);
			return into;
		}

		public List<T> All<T>(Rect rect, List<T> into, delegate bool(T) condition) where T : Component, IHasHitbox
		{
			for (T c in componentTracker[typeof(T)])
				if (c.Hitbox.Check(rect) && condition(c))
					into.Add(c as T);
			return into;
		}

		public T CheckForEach<T>(delegate bool(T) func) where T : interface
		{
			List<Component> list;
			if (componentTracker.TryGetValue(typeof(T), out list))
				for (let c in list)
					if (func(c as T))
						return c as T;
			return null;
		}

		public T CheckForEach<T>(delegate bool(T) func) where T : Component
		{
			List<Component> list;
			if (componentTracker.TryGetValue(typeof(T), out list))
				for (let c in list)
					if (func(c as T))
						return c as T;
			return null;
		}

		public void CheckForEach<T>(delegate bool(T) func, List<T> into) where T : interface
		{
			List<Component> list;
			if (componentTracker.TryGetValue(typeof(T), out list))
				for (let c in list)
					if (func(c as T))
						into.Add(c as T);
		}

		public void CheckForEach<T>(delegate bool(T) func, List<T> into) where T : Component
		{
			List<Component> list;
			if (componentTracker.TryGetValue(typeof(T), out list))
				for (let c in list)
					if (func(c as T))
						into.Add(c as T);
		}

		public void ForEach<T>(delegate void(T) action) where T : interface
		{
			List<Component> list;
			if (componentTracker.TryGetValue(typeof(T), out list))
				for (let c in list)
					action(c as T);
		}

		public void ForEach<T>(delegate void(T) action) where T : Component
		{
			List<Component> list;
			if (componentTracker.TryGetValue(typeof(T), out list))
				for (T c in list)
					action(c);
		}
	}
}
