using Cryville.Common;
using Cryville.Common.Pdt;
using Cryville.Common.Unity.Input;
using Cryville.Crtr.Config;
using System;
using System.Collections.Generic;
using UnityEngine;
using Logger = Cryville.Common.Logger;

namespace Cryville.Crtr {
	public class InputProxy {
		readonly PdtEvaluator _etor;
		readonly PdtRuleset _ruleset;
		readonly Judge _judge;
		public InputProxy(PdtRuleset ruleset, Judge judge) {
			unsafe {
				fixed (byte* ptr = _vecbuf) {
					*(int*)(ptr + 3 * sizeof(float)) = PdtInternalType.Number;
				}
			}
			_etor = ChartPlayer.etor;
			_ruleset = ruleset;
			_judge = judge;
			foreach (var i in ruleset.inputs) {
				_use.Add(i.Key, 0);
				_rev.Add(i.Key, new List<string>());
			}
			foreach (var i in ruleset.inputs) {
				if (i.Value.pass != null) {
					foreach (var p in i.Value.pass)
						_rev[p.Key].Add(i.Key);
				}
			}
		}
		#region Settings
		readonly Dictionary<string, InputProxyEntry> _tproxies = new Dictionary<string, InputProxyEntry>();
		readonly Dictionary<InputSource, InputProxyEntry> _sproxies = new Dictionary<InputSource, InputProxyEntry>();
		readonly Dictionary<string, int> _use = new Dictionary<string, int>();
		readonly Dictionary<string, List<string>> _rev = new Dictionary<string, List<string>>();
		public event EventHandler<ProxyChangedEventArgs> ProxyChanged;
		public void LoadFrom(Dictionary<string, InputConfigEntry> config) {
			foreach (var cfg in config) {
				Set(new InputProxyEntry {
					Target = cfg.Key,
					Source = new InputSource {
						Handler = Game.InputManager.GetHandler(cfg.Value.handler),
						Type = cfg.Value.type
					}
				});
			}
		}
		public void SaveTo(Dictionary<string, InputConfigEntry> config) {
			config.Clear();
			foreach (var p in _tproxies) {
				config.Add(p.Key, new InputConfigEntry {
					handler = ReflectionHelper.GetNamespaceQualifiedName(p.Value.Source.Value.Handler.GetType()),
					type = p.Value.Source.Value.Type
				});
			}
		}
		public void Set(InputProxyEntry proxy) {
			var name = proxy.Target;
			if (!_ruleset.inputs.ContainsKey(name)) throw new ArgumentException("Invalid input name");
			if (_use[proxy.Target] > 0)
				throw new InvalidOperationException("Input already assigned");
			if (proxy.Source != null) {
				if (_judge != null) {
					proxy.Source.Value.Handler.OnInput -= OnInput; // Prevent duplicated hooks, no exception will be thrown
					proxy.Source.Value.Handler.OnInput += OnInput;
				}
				_tproxies.Add(proxy.Target, proxy);
				_sproxies.Add(proxy.Source.Value, proxy);
				IncrementUseRecursive(name);
				IncrementReversedUseRecursive(name);
			}
		}
		void Remove(InputProxyEntry proxy) {
			var name = proxy.Target;
			if (_judge != null) _tproxies[name].Source.Value.Handler.OnInput -= OnInput;
			_sproxies.Remove(_tproxies[name].Source.Value);
			_tproxies.Remove(name);
			DecrementUseRecursive(name);
			DecrementReversedUseRecursive(name);
		}
		public bool IsUsed(InputSource src) {
			return _sproxies.ContainsKey(src);
		}
		public bool IsCompleted {
			get {
				foreach (var i in _use)
					if (i.Value == 0 && !_tproxies.ContainsKey(i.Key)) return false;
				return true;
			}
		}
		void IncrementUseRecursive(string name) {
			BroadcastProxyChanged(name);
			var passes = _ruleset.inputs[name].pass;
			if (passes != null) {
				foreach (var p in _ruleset.inputs[name].pass) {
					_use[p.Key]++;
					IncrementUseRecursive(p.Key);
				}
			}
		}
		void IncrementReversedUseRecursive(string name) {
			foreach (var p in _rev[name]) {
				_use[p]++;
				BroadcastProxyChanged(p);
				IncrementReversedUseRecursive(p);
			}
		}
		void DecrementUseRecursive(string name) {
			BroadcastProxyChanged(name);
			var passes = _ruleset.inputs[name].pass;
			if (passes != null) {
				foreach (var p in _ruleset.inputs[name].pass) {
					_use[p.Key]--;
					DecrementUseRecursive(p.Key);
				}
			}
		}
		void DecrementReversedUseRecursive(string name) {
			foreach (var p in _rev[name]) {
				_use[p]--;
				BroadcastProxyChanged(p);
				DecrementReversedUseRecursive(p);
			}
		}
		void BroadcastProxyChanged(string name) {
			var del = ProxyChanged;
			if (del != null) del(this, this[name]);
		}
		public ProxyChangedEventArgs this[string name] {
			get {
				return new ProxyChangedEventArgs(name, _tproxies.ContainsKey(name) ? _tproxies[name].Source : null, _use[name] > 0);
			}
		}
		#endregion

		#region Handling
		public void Activate() {
			_activeCounts.Clear();
			_vect.Clear(); _vecs.Clear();
			foreach (var src in _sproxies.Keys) {
				_activeCounts.Add(src, 0);
				src.Handler.Activate();
			}
		}
		public void Deactivate() { foreach (var src in _sproxies.Keys) src.Handler.Deactivate(); }

		readonly object _lock = new object();
		static readonly int _var_value = IdentifierManager.SharedInstance.Request("value");
		static readonly PropOp.Arbitrary _arbop = new PropOp.Arbitrary();
		readonly byte[] _vecbuf = new byte[3 * sizeof(float) + sizeof(int)];
		readonly Dictionary<InputHandler, double> _timeOrigins = new Dictionary<InputHandler, double>();
		readonly Dictionary<InputSource, int> _activeCounts = new Dictionary<InputSource, int>();
		readonly Dictionary<InputIdentifier, float> _vect = new Dictionary<InputIdentifier, float>();
		readonly Dictionary<ProxiedInputIdentifier, PropSrc.Arbitrary> _vecs = new Dictionary<ProxiedInputIdentifier, PropSrc.Arbitrary>();
		static readonly byte[] _nullvalue = new byte[0];
		unsafe void OnInput(InputIdentifier id, InputVector vec) {
			lock (_lock) {
				InputProxyEntry proxy;
				if (_sproxies.TryGetValue(id.Source, out proxy)) {
					_etor.ContextCascadeInsert();
					float ft, tt = (float)(vec.Time - _timeOrigins[id.Source.Handler]);
					if (!_vect.TryGetValue(id, out ft)) ft = tt;
					if (vec.IsNull) {
						_etor.ContextCascadeUpdate(_var_value, new PropSrc.Arbitrary(PdtInternalType.Null, _nullvalue));
					}
					else {
						fixed (byte* ptr = _vecbuf) {
							*(Vector3*)ptr = vec.Vector;
						}
						_etor.ContextCascadeUpdate(_var_value, new PropSrc.Arbitrary(PdtInternalType.Vector, _vecbuf));
					}
					OnInput(id, proxy.Target, ft, tt);
					_vect[id] = tt;
					_etor.ContextCascadeDiscard();
				}
			}
		}
		static readonly int _var_fv = IdentifierManager.SharedInstance.Request("fv");
		static readonly int _var_tv = IdentifierManager.SharedInstance.Request("tv");
		unsafe void OnInput(InputIdentifier id, Identifier target, float ft, float tt) {
			var def = _ruleset.inputs[target];
			if (def.pass != null) {
				foreach (var p in def.pass) {
					_etor.ContextCascadeInsert();
					_arbop.Name = _var_value;
					_etor.Evaluate(_arbop, p.Value);
					OnInput(id, p.Key, ft, tt);
					_etor.ContextCascadeDiscard();
				}
			}
			else {
				var pid = new ProxiedInputIdentifier { Source = id, Target = target };
				PropSrc.Arbitrary fv, tv = _etor.ContextCascadeLookup(_var_value);
				if (!_vecs.TryGetValue(pid, out fv)) fv = new PropSrc.Arbitrary(PdtInternalType.Null, _nullvalue);
				if (fv.Type != PdtInternalType.Null || tv.Type != PdtInternalType.Null) {
					if (fv.Type == PdtInternalType.Null) _activeCounts[id.Source]++;
					_etor.ContextCascadeInsert();
					_etor.ContextCascadeUpdate(_var_fv, fv);
					_etor.ContextCascadeUpdate(_var_tv, tv);
					_judge.Feed(target, ft, tt);
					_etor.ContextCascadeDiscard();
					if (tv.Type == PdtInternalType.Null) _activeCounts[id.Source]--;
				}
				_judge.Cleanup(target, ft, tt);
				_vecs[pid] = tv;
			}
		}
		public void SyncTime(double time) {
			foreach (var s in _sproxies.Keys) {
				var h = s.Handler;
				if (!_timeOrigins.ContainsKey(h)) 
					_timeOrigins.Add(h, h.GetCurrentTimestamp() - time);
			}
		}
		public void ForceTick() {
			foreach (var src in _sproxies.Keys) {
				if (_activeCounts[src] == 0) {
					OnInput(new InputIdentifier { Source = src, Id = 0 }, new InputVector(src.Handler.GetCurrentTimestamp()));
				}
			}
		}
		public double GetTimestampAverage() {
			double result = 0;
			foreach (var src in _sproxies.Keys) {
				result += src.Handler.GetCurrentTimestamp() - _timeOrigins[src.Handler];
			}
			return result / _sproxies.Count;
		}
		#endregion
	}

	public class ProxyChangedEventArgs : EventArgs {
		public string Name { get; private set; }
		public InputSource? Proxy { get; private set; }
		public bool Used { get; private set; }
		public ProxyChangedEventArgs(string name, InputSource? src, bool used) {
			Name = name;
			Proxy = src;
			Used = used;
		}
	}

	public class InputProxyEntry {
		public InputSource? Source { get; set; }
		public string Target { get; set; }
		public byte[] Mapping { get; private set; }
	}

	public struct ProxiedInputIdentifier : IEquatable<ProxiedInputIdentifier> {
		public InputIdentifier Source { get; set; }
		public Identifier Target { get; set; }
		public override bool Equals(object obj) {
			if (obj == null || !(obj is ProxiedInputIdentifier)) return false;
			return Equals((ProxiedInputIdentifier)obj);
		}
		public bool Equals(ProxiedInputIdentifier other) {
			return Source == other.Source && Target == other.Target;
		}
		public override int GetHashCode() {
			return Source.GetHashCode() ^ Target.GetHashCode();
		}
		public override string ToString() {
			return string.Format("{0}->{1}", Source, Target);
		}
		public static bool operator ==(ProxiedInputIdentifier lhs, ProxiedInputIdentifier rhs) {
			return lhs.Equals(rhs);
		}
		public static bool operator !=(ProxiedInputIdentifier lhs, ProxiedInputIdentifier rhs) {
			return !lhs.Equals(rhs);
		}
	}
}
