using Cryville.Common;
using Cryville.Common.Pdt;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using System;
using System.Text.RegularExpressions;

namespace Cryville.Crtr {
	public class ChartCompatibilityHandler : CustomCreationConverter<Chart> {
		public static IMotionStringParser MotionStringParser { get; private set; }
		public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
			var obj = JToken.ReadFrom(reader);
			switch (obj["format"].ToObject<int>()) {
				case 2: MotionStringParser = MotionStringParser2.Instance; break;
				case 3: MotionStringParser = MotionStringParser3.Instance; break;
				default: throw new FormatException("Unsupported chart format");
			}
			return base.ReadJson(obj.CreateReader(), objectType, existingValue, serializer);
		}
		public override Chart Create(Type objectType) {
			return new Chart();
		}
		public interface IMotionStringParser {
			void Parse(string str, out Identifier name, out MotionNode node);
		}
		class MotionStringParser2 : IMotionStringParser {
			static MotionStringParser2 _instance;
			public static MotionStringParser2 Instance {
				get {
					if (_instance == null)
						_instance = new MotionStringParser2();
					return _instance;
				}
			}
			static readonly PdtFragmentInterpreter _itor = new PdtFragmentInterpreter();
			static readonly PropOp _vecop = new VectorOp(v => _vecbuf = v);
			static float[] _vecbuf;
			public void Parse(string str, out Identifier name, out MotionNode node) {
				Match m = Regex.Match(str, @"^(.+?)(#(\d+))?(@(.+?))?(\^(.+?))?(\*(.+?))?(:(.+))?$");
				if (!m.Success) throw new ArgumentException("Invalid motion string format");
				name = new Identifier(m.Groups[1].Value);
				var registry = ChartPlayer.motionRegistry[name];
				if (m.Groups[3].Success) {
					short id = short.Parse(m.Groups[3].Value);
					if (id < 0) throw new ArgumentException("Got negative motion node index");
					Vec1 time = m.Groups[5].Success ? new Vec1(float.Parse(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(float.Parse(m.Groups[9].Value)) : null;*/
					Vector value = m.Groups[11].Success ? Vector.Construct(registry.Type, ParseVector(m.Groups[11].Value)) : null;
					node = new MotionNode {
						Id = id,
						Time = time,
						Value = value
					};
				}
				else {
					node = new MotionNode {
						Value = Vector.Construct(registry.Type, ParseVector(m.Groups[11].Value))
					};
				}
			}
			float[] ParseVector(string str) {
				var comps = str.Split(',');
				for (int i = 0; i < comps.Length; i++) {
					if (comps[i] == "") comps[i] = "0";
				}
				_itor.SetSource(string.Format("({0});", string.Join(',', comps)));
				PdtEvaluator.Instance.Evaluate(_vecop, _itor.GetExp());
				return _vecbuf;
			}
		}
		class MotionStringParser3 : IMotionStringParser {
			static MotionStringParser3 _instance;
			public static MotionStringParser3 Instance {
				get {
					if (_instance == null)
						_instance = new MotionStringParser3();
					return _instance;
				}
			}
			static readonly PdtFragmentInterpreter _itor = new PdtFragmentInterpreter();
			static readonly PropOp _vecop = new VectorOp(v => _vecbuf = v);
			static float[] _vecbuf;
			public void Parse(string str, out Identifier name, out MotionNode node) {
				Match m = Regex.Match(str, @"^([0-9A-Za-z_]+)(#(\d+))?(.+)?$");
				if (!m.Success) throw new ArgumentException("Invalid motion string format");
				name = new Identifier(m.Groups[1].Value);
				node = new MotionNode();
				if (m.Groups[3].Success) {
					short id = short.Parse(m.Groups[3].Value);
					if (id < 0) throw new ArgumentException("Got negative motion node index");
					node.Id = id;
				}
				if (m.Groups[4].Success) {
					_itor.SetSource(m.Groups[4].Value);
					while (_itor.Position < _itor.Source.Length) {
						var c = _itor.GetChar();
						var exp = _itor.GetExp();
						switch (c) {
							case '@':
								PdtEvaluator.Instance.Evaluate(_vecop, exp);
								node.Time = new Vec1(_vecbuf);
								break;
							case '~':
								PdtEvaluator.Instance.Evaluate(_vecop, exp);
								node.EndTime = new Vec1(_vecbuf);
								break;
							case '^':
								node.Transition = exp;
								break;
							case ':':
								PdtEvaluator.Instance.Evaluate(_vecop, exp);
								node.Value = Vector.Construct(ChartPlayer.motionRegistry[name].Type, _vecbuf);
								break;
							default:
								throw new ArgumentException("Invalid motion string format");
						}
					}
				}
				else node.Reset = true;
			}
		}
	}
}
