using Cryville.Common;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text.RegularExpressions;

namespace Cryville.Crtr {
	[JsonConverter(typeof(BeatTimeConverter))]
	public struct BeatTime {
		[JsonConstructor()]
		public BeatTime(int _b, int _n, int _d) {
			b = _b;
			n = _n;
			d = _d;
		}
		
		[JsonIgnore]
		public int b;
		
		[JsonIgnore]
		public int n;
		
		[JsonIgnore]
		public int d;
	}
	
	public class BeatTimeConverter : JsonConverter {
		public override bool CanConvert(Type objectType) {
			return objectType == typeof(int[]);
		}
		
		public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
			int b = (int)reader.ReadAsInt32();
			int n = (int)reader.ReadAsInt32();
			int d = (int)reader.ReadAsInt32();
			reader.Read();
			return new BeatTime(b, n, d);
		}
		
		public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
			BeatTime obj = (BeatTime)value;
			writer.WriteStartArray();
			writer.WriteValue(obj.b);
			writer.WriteValue(obj.n);
			writer.WriteValue(obj.d);
			writer.WriteEndArray();
		}
	}
	
	public abstract class ChartEvent {
		public BeatTime? time;
		
		[JsonIgnore]
		public float BeatPosition {
			get {
				return time.Value.b + time.Value.n / (float)time.Value.d + BeatOffset;
			}
		}

		public BeatTime? endtime;
		
		[JsonIgnore]
		public float EndBeatPosition {
			get {
				if (endtime == null) return BeatPosition;
				return endtime.Value.b + endtime.Value.n / (float)endtime.Value.d + BeatOffset;
			}
		}
		
		[JsonIgnore]
		public float BeatOffset;
		
		[JsonIgnore]
		public abstract int Priority {
			get;
		}
		
		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 InstantEvent attev = null;
		[JsonIgnore]
		public InstantEvent AttackEvent {
			get {
				if (attev == null) attev = new InstantEvent(this);
				return attev;
			}
		}

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

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

		protected ChartEvent() {
			PropSrcs = new Dictionary<int, PropSrc>();
			PropOps = new Dictionary<int, PropOp>();
			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 InstantEvent : ChartEvent {
		public readonly ChartEvent Original;
		public bool IsRelease;
		
		public InstantEvent(ChartEvent orig, bool release = false) {
			IsRelease = release;
			if (orig != null) {
				Original = orig;
				time = orig.time;
				if (IsRelease)
					BeatOffset = orig.Duration;
			}
		}
		
		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 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("Unknown 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();
		}
	}

	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 10; }
			}
		}

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

		public class Motion : ChartEvent {
#pragma warning disable IDE1006
			[JsonRequired]
			public string motion {
				get { return ToString(); }
				set { LoadFromString(value); }
			}
#pragma warning restore IDE1006

			private void LoadFromString(string s) {
				Match m = Regex.Match(s, @"^(.+?)(#(\d+))?(@(.+?))?(\^(.+?))?(\*(.+?))?(:(.+))?$");
				if (!m.Success) throw new ArgumentException(); // TODO
				name = new Identifier(m.Groups[1].Value);
				var registry = ChartPlayer.motionRegistry[name];
				if (m.Groups[3].Success) {
					ushort id = ushort.Parse(m.Groups[3].Value);
					Vec1 time = m.Groups[5].Success ? new Vec1(m.Groups[5].Value) : null;
					byte? trs = m.Groups[7].Success ? byte.Parse(m.Groups[7].Value) : null;
					Vec1 rate = m.Groups[9].Success ? new Vec1(m.Groups[9].Value) : null;
					Vector value = m.Groups[11].Success ? Vector.Construct(registry.Type, m.Groups[11].Value) : null;
					RelativeNode = new MotionNode() {
						Id = id,
						Time = time,
						Transition = (TransitionType?)trs,
						Rate = rate,
						Value = value
					};
				}
				else {
					AbsoluteValue = Vector.Construct(registry.Type, m.Groups[11].Value);
				}
			}

			public override string ToString() {
				string result = Name.ToString();
				if (RelativeNode != null) {
					var node = RelativeNode;
					result += "#" + node.Id;
					if (node.Time != null) result += "@" + node.Time.ToString();
					if (node.Transition != null) result = "^" + node.Transition.ToString();
					if (node.Rate != null) result += "*" + node.Rate.ToString();
					if (node.Value != null) result += ":" + node.Value.ToString();
				}
				else {
					result += ":" + AbsoluteValue.ToString();
				}
				return result;
			}

			private Identifier name;
			[JsonIgnore]
			public Identifier Name {
				get {
					return name;
				}
				private set {
					MotionRegistry reg;
					if (!ChartPlayer.motionRegistry.TryGetValue(value, out reg))
						throw new ArgumentException("Invalid motion name");
					if (RelativeNode != null) RelativeNode.Value = reg.InitValue;
					else AbsoluteValue = reg.InitValue;
					name = value;
				}
			}

			[JsonIgnore]
			public Vector AbsoluteValue;
			[JsonIgnore]
			public MotionNode RelativeNode;

			[DefaultValue(TransitionType.Ease)] // TODO [Obsolete]
			public TransitionType transition = TransitionType.Ease;
			[DefaultValue(1.0f)] // TODO [Obsolete]
			public float rate = 1.0f;
			[DefaultValue(0.0f)]
			public float sumfix = 0.0f;

			public override int Priority {
				get { return -2; }
			}

			public Motion() {
				SubmitPropSrc("value", new VectorSrc(() => {
					if (RelativeNode != null) return RelativeNode.Value;
					else return AbsoluteValue;
				}));
				SubmitPropOp("motion", new PropOp.String(v => motion = v));
				SubmitPropOp("name", new PropOp.Identifier(v => {
					var n = new Identifier(v);
					if (Name.Equals(n)) { }
					else if (Name.Equals(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);
					if (RelativeNode != null) RelativeNode.Value = vec;
					else AbsoluteValue = 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 Note() {
				SubmitPropSrc("track", new PropSrc.Float(() => {
					var i = motions.FirstOrDefault(m => m.RelativeNode == null && m.Name == "track");
					if (i == null) return ((Vec1)ChartPlayer.motionRegistry["track"].InitValue).Value;
					else return ((Vec1)i.AbsoluteValue).Value;
				}));
			}

			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 12; }
			}
		}

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

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

			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 -4; }
			}
		}

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

			// TODO [Obsolete]
			public float offset;

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