using Cryville.Common;
using Cryville.Common.Collections.Specialized;
using Cryville.Common.Pdt;
using Cryville.Crtr.Ruleset;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using UnsafeIL;

namespace Cryville.Crtr {
	[Flags]
	public enum CoeventMode {
		None = 0x0,
		Coevent = 0x1,
		Standalone = 0x2,
	}
	public abstract class ChartEvent {
		public BeatTime? time;
		
		[JsonIgnore]
		public float BeatPosition {
			get {
				return (float)time.Value.Decimal + BeatOffset;
			}
		}

		public BeatTime? endtime;
		
		[JsonIgnore]
		public float EndBeatPosition {
			get {
				if (endtime == null) return BeatPosition;
				return (float)endtime.Value.Decimal + BeatOffset;
			}
		}
		
		[JsonIgnore]
		public float BeatOffset;
		
		[JsonIgnore]
		public abstract int Priority { get; }

		[JsonIgnore]
		public virtual CoeventMode CoeventMode { get { return CoeventMode.Coevent; } }

		public ChartEvent Clone() {
			return (ChartEvent)MemberwiseClone();
		}

		[JsonIgnore]
		public float Duration {
			get {
				if (endtime == null) return 0;
				return EndBeatPosition - BeatPosition;
			}
		}

		[JsonIgnore]
		public bool IsLong {
			get { return Duration > 0; }
		}

		private ReleaseEvent relev = null;
		[JsonIgnore]
		public ReleaseEvent ReleaseEvent {
			get {
				if (relev == null) relev = new ReleaseEvent(this);
				return relev;
			}
		}

		[JsonIgnore]
		public IntKeyedDictionary<PropSrc> PropSrcs { get; private set; }
		protected void SubmitPropSrc(string name, PropSrc property) {
			PropSrcs.Add(IdentifierManager.Shared.Request(name), property);
		}
		[JsonIgnore]
		public IntKeyedDictionary<PropOp> PropOps { get; private set; }
		protected void SubmitPropOp(string name, PropOp property) {
			PropOps.Add(IdentifierManager.Shared.Request(name), property);
		}

		protected ChartEvent() {
			PropSrcs = new IntKeyedDictionary<PropSrc>();
			PropOps = new IntKeyedDictionary<PropOp>();
			SubmitPropSrc("event", new PropSrc.Float(() => {
				int hash = GetHashCode();
				return Unsafe.As<int, float>(ref hash);
			}));
			SubmitPropSrc("long", new PropSrc.Boolean(() => IsLong));
			SubmitPropSrc("time", new PropSrc.BeatTime(() => time.Value));
			SubmitPropSrc("endtime", new PropSrc.BeatTime(() => endtime.Value));
			SubmitPropOp("time", new PropOp.BeatTime(v => time = v));
			SubmitPropOp("endtime", new PropOp.BeatTime(v => endtime = v));
		}
	}
	
	public class ReleaseEvent : ChartEvent {
		public readonly ChartEvent Original;
		
		public ReleaseEvent(ChartEvent orig) {
			Original = orig;
			time = orig.endtime;
		}
		
		public override int Priority {
			get {
				return Original.Priority + 1;
			}
		}
	}
	public abstract class EventContainer : ChartEvent {
		public List<Chart.Motion> motions = new List<Chart.Motion>();

		[JsonIgnore]
		public Clip Clip { get; private set; }

		public EventContainer() {
			SubmitPropOp("clip", new PropOp.Clip(v => Clip = v));
		}
		
		[JsonIgnore]
		public virtual IEnumerable<ChartEvent> Events {
			get {
				return motions.Cast<ChartEvent>();
			}
		}

		public virtual EventList GetEventsOfType(string type) {
			switch (type) {
				case "motions": return new EventList<Chart.Motion>(motions);
				default: throw new ArgumentException(string.Format("Unknown event type \"{0}\"", type));
			}
		}
	}
	public abstract class EventList : ChartEvent {
		public IList<ChartEvent> Events { get; private set; }
		protected EventList(IList<ChartEvent> events) {
			Events = events;
			SubmitPropSrc("count", new PropSrc.Float(() => Events.Count));
			SubmitPropOp("count", new ListCountOp(() => Events));
		}
		public abstract ChartEvent Create();
		public override int Priority {
			get { throw new NotSupportedException("Fake event"); }
		}
		class ListCountOp : PropOp {
			readonly Func<IList<ChartEvent>> _cb;

			public ListCountOp(Func<IList<ChartEvent>> cb) {
				_cb = cb;
			}

			protected override void Execute() {
				int ac = _cb().Count;
				int ec = (int)Math.Round(GetOperand(0).AsNumber());
				if (ac != ec) {
					throw new RulesetViolationException(string.Format(
						"Event count not matched, expected {0}, got {1}", ec, ac
					));
				}
			}
		}
	}
	public class EventList<T> : EventList where T : ChartEvent, new() {
		public EventList(List<T> events) : base(new CastedList<ChartEvent>(events)) { }
		public override ChartEvent Create() {
			return new T();
		}
	}

	[JsonConverter(typeof(ChartCompatibilityHandler))]
	public class Chart : EventContainer {
		[JsonRequired]
		public long format; // Format Version

		public string ruleset;

		public List<Group> groups = new List<Group>();
		
		public override IEnumerable<ChartEvent> Events {
			get {
				return base.Events
					.Concat(groups.Cast<ChartEvent>())
					.Concat(sigs.Cast<ChartEvent>())
					.Concat(sounds.Cast<ChartEvent>());
			}
		}

		public override EventList GetEventsOfType(string type) {
			switch (type) {
				case "groups": return new EventList<Group>(groups);
				default: return base.GetEventsOfType(type);
			}
		}

		public override int Priority { get { return 10; } }

		public class Group : EventContainer {
			public List<Track> tracks = new List<Track>();
			public List<Note> notes = new List<Note>();
			public override IEnumerable<ChartEvent> Events {
				get {
					return base.Events 
						.Concat(notes.Cast<ChartEvent>()
						.Concat(tracks.Cast<ChartEvent>()
					));
				}
			}
			public override EventList GetEventsOfType(string type) {
				switch (type) {
					case "tracks": return new EventList<Track>(tracks);
					case "notes": return new EventList<Note>(notes);
					default: return base.GetEventsOfType(type);
				}
			}
			public override int Priority { get { return 12; } }
		}

		public class Track : EventContainer {
			public override int Priority { get { return 14; } }
		}

		public class Motion : ChartEvent {
#pragma warning disable IDE1006
			string m_motion;
			[JsonRequired]
			public string motion {
				get { return m_motion == null ? ToString() : m_motion; }
				set { LoadFromString(value); }
			}
#pragma warning restore IDE1006
			private void LoadFromString(string s) {
				if (Node != null)
					throw new InvalidOperationException("The motion property can only be set at initialization");
				if (ChartPlayer.motionRegistry == null) {
					m_motion = s;
				}
				else {
					ChartCompatibilityHandler.MotionStringParser.Parse(s, out name, out Node);
					SubmitPropSrc("value", new VectorSrc(() => Node.Value));
				}
			}

			public override string ToString() {
				string result = Name.ToString();
				if (Node.Id >= 0) {
					var node = Node;
					result += "#" + node.Id;
					if (node.Time != null) result += "@" + node.Time.ToString();
					if (node.EndTime != null) result += "~" + node.EndTime.ToString();
					if (node.Transition != null) result += "^" + node.Transition.ToString();
					if (node.Value != null) result += ":" + node.Value.ToString();
				}
				else {
					result += ":" + Node.Value.ToString();
				}
				return result;
			}

			private Identifier name;
			[JsonIgnore]
			public Identifier Name {
				get {
					if (name == default(Identifier)) throw new InvalidOperationException("Motion name not set");
					return name;
				}
				private set {
					MotionRegistry reg;
					if (!ChartPlayer.motionRegistry.TryGetValue(value, out reg))
						throw new ArgumentException("Invalid motion name");
					Node = new MotionNode { Value = reg.InitValue };
					name = value;
				}
			}

			[JsonIgnore]
			public MotionNode Node;

			[JsonConverter(typeof(JsonPdtExpConverter))]
			public PdtExpression transition;

			[DefaultValue(0.0f)]
			public float sumfix = 0.0f;

			public override int Priority { get { return 0x1000; } }
			public override CoeventMode CoeventMode { get { return IsLong ? CoeventMode.Standalone : CoeventMode.Coevent; } }

			public Motion() {
				SubmitPropOp("motion", new PropOp.String(v => motion = v)); // TODO Obsolete
				SubmitPropOp("name", new PropOp.Identifier(v => {
					var n = new Identifier(v);
					if (name == n) { }
					else if (name == default(Identifier)) Name = n;
					else throw new RulesetViolationException(string.Format(
						"Motion name not matched, expected {0}, got {1}", n, Name
					));
				}));
				SubmitPropOp("value", new VectorOp(v => {
					var vec = Vector.Construct(ChartPlayer.motionRegistry[Name].Type, v);
					Node.Value = vec;
				}));
			}
		}

		public class Note : EventContainer {
			public List<Judge> judges = new List<Judge>();
			public override IEnumerable<ChartEvent> Events {
				get {
					return base.Events
						.Concat(judges.Cast<ChartEvent>()
					);
				}
			}

			public override EventList GetEventsOfType(string type) {
				switch (type) {
					case "judges": return new EventList<Judge>(judges);
					default: return base.GetEventsOfType(type);
				}
			}
			public override int Priority { get { return 20; } }
		}

		public class Judge : ChartEvent {
			[JsonIgnore]
			public Identifier Id;
#pragma warning disable IDE1006
			public string name {
				get { return Id.ToString(); }
				set { Id = new Identifier(value); }
			}
#pragma warning restore IDE1006

			public override int Priority { get { return 0; } }
			public override CoeventMode CoeventMode { get { return CoeventMode.Standalone; } }

			public Judge() {
				SubmitPropSrc("name", new PropSrc.Identifier(() => Id.Key));
				SubmitPropOp("name", new PropOp.Identifier(v => Id = new Identifier(v)));
			}
		}

		// TODO [Obsolete]
		public List<Signature> sigs; // Signatures
		// TODO [Obsolete]
		public class Signature : ChartEvent {
			public float? tempo;

			public override int Priority { get { return -0x2000; } }
		}

		public List<Sound> sounds;
		public class Sound : ChartEvent {
			[JsonRequired]
			public string id;

			// TODO [Obsolete]
			public float offset;

			public override int Priority { get { return 0; } }
		}
	}
}
