using System;
using System.Collections.Generic;
using System.Reflection;

namespace Cryville.Common.Unity.Input {
	public class InputManager {
		static readonly List<Type> HandlerRegistries = new List<Type> {
			typeof(WindowsPointerHandler),
			typeof(UnityKeyHandler<UnityKeyboardReceiver>),
			typeof(UnityKeyHandler<UnityMouseButtonReceiver>),
			typeof(UnityMouseHandler),
			typeof(UnityTouchHandler),
		};
		// TODO set private
		public readonly List<InputHandler> _handlers = new List<InputHandler>();
		readonly Dictionary<Type, InputHandler> _typemap = new Dictionary<Type, InputHandler>();
		readonly Dictionary<InputHandler, double> _timeOrigins = new Dictionary<InputHandler, double>();
		readonly object _lock = new object();
		readonly Dictionary<InputIdentifier, InputVector> _vectors = new Dictionary<InputIdentifier, InputVector>();
		readonly List<InputEvent> _events = new List<InputEvent>();
		public InputManager() {
			foreach (var t in HandlerRegistries) {
				try {
					if (!typeof(InputHandler).IsAssignableFrom(t)) continue;
					var h = (InputHandler)ReflectionHelper.InvokeEmptyConstructor(t);
					_typemap.Add(t, h);
					h.OnInput += OnInput;
					_handlers.Add(h);
					_timeOrigins.Add(h, 0);
					Logger.Log("main", 1, "Input", "Initialized {0}", ReflectionHelper.GetSimpleName(t));
				}
				catch (TargetInvocationException ex) {
					Logger.Log("main", 1, "Input", "Cannot initialize {0}: {1}", ReflectionHelper.GetSimpleName(t), ex.InnerException.Message);
				}
			}
		}
		public InputHandler GetHandler(string name) {
			return _typemap[Type.GetType(name)];
		}
		public void Activate() {
			lock (_lock) {
				_events.Clear();
			}
			foreach (var h in _handlers) h.Activate();
		}
		public void SyncTime(double time) {
			foreach (var h in _handlers) {
				_timeOrigins[h] = time - h.GetCurrentTimestamp();
			}
		}
		public void Deactivate() {
			foreach (var h in _handlers) h.Deactivate();
		}
		void OnInput(InputIdentifier id, InputVector vec) {
			lock (_lock) {
				double timeOrigin = _timeOrigins[id.Source.Handler];
				vec.Time += timeOrigin;
				InputVector vec0;
				if (_vectors.TryGetValue(id, out vec0)) {
					_events.Add(new InputEvent {
						Id = id,
						From = vec0,
						To = vec,
					});
					if (vec.IsNull) _vectors.Remove(id);
					else _vectors[id] = vec;
				}
				else {
					_events.Add(new InputEvent {
						Id = id,
						From = new InputVector(vec.Time),
						To = vec,
					});
					_vectors.Add(id, vec);
				}
			}
		}
		public void EnumerateEvents(Action<InputEvent> cb) {
			lock (_lock) {
				foreach (var ev in _events) cb(ev);
				_events.Clear();
			}
		}
	}

	public struct InputEvent {
		public InputIdentifier Id { get; set; }
		public InputVector From { get; set; }
		public InputVector To { get; set; }
		public override string ToString() {
			return string.Format("[{0}] {1} -> {2}", Id, From, To);
		}
	}
}
