using System;
using UnityEngine;

namespace Cryville.Common.Unity.Input {
	public delegate void InputEventDelegate(InputIdentifier id, InputVector vec);
	public abstract class InputHandler : IDisposable {
		public event InputEventDelegate OnInput;

		~InputHandler() {
			Dispose(false);
		}
		public void Dispose() {
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		public bool Activated { get; private set; }
		public void Activate() {
			if (Activated) return;
			Activated = true;
			ActivateImpl();
		}
		protected abstract void ActivateImpl();
		public void Deactivate() {
			if (!Activated) return;
			Activated = false;
			DeactivateImpl();
		}
		protected abstract void DeactivateImpl();
		public abstract void Dispose(bool disposing);
		public abstract bool IsNullable(int type);
		public abstract byte GetDimension(int type);
		public abstract string GetTypeName(int type);
		public abstract double GetCurrentTimestamp();
		protected void Feed(int type, int id, InputVector vec) {
			var del = OnInput;
			if (del != null) del(new InputIdentifier { Source = new InputSource { Handler = this, Type = type }, Id = id }, vec);
		}
	}

	public struct InputSource : IEquatable<InputSource> {
		public InputHandler Handler { get; set; }
		public int Type { get; set; }
		public override bool Equals(object obj) {
			if (obj == null || !(obj is InputSource)) return false;
			return Equals((InputSource)obj);
		}
		public bool Equals(InputSource other) {
			return Handler == other.Handler && Type == other.Type;
		}
		public override int GetHashCode() {
			return Handler.GetHashCode() ^ Type;
		}
		public override string ToString() {
			return string.Format("{0}:{1}", ReflectionHelper.GetSimpleName(Handler.GetType()), Handler.GetTypeName(Type));
		}
		public static bool operator ==(InputSource lhs, InputSource rhs) {
			return lhs.Equals(rhs);
		}
		public static bool operator !=(InputSource lhs, InputSource rhs) {
			return !lhs.Equals(rhs);
		}
	}

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

	public struct InputVector {
		public double Time { get; set; }
		public bool IsNull { get; set; }
		public Vector3 Vector { get; set; }
		public InputVector(double time) {
			Time = time;
			IsNull = true;
			Vector = default(Vector3);
		}
		public InputVector(double time, Vector3 vector) {
			Time = time;
			IsNull = false;
			Vector = vector;
		}
		public override string ToString() {
			if (IsNull) return string.Format("null@{0}", Time);
			else return string.Format("{0}@{1}", Vector.ToString("G9"), Time);
		}
	}
}
