using Cryville.Common;
using Cryville.Common.Pdt;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using SIdentifier = Cryville.Common.Identifier;

namespace Cryville.Crtr {
	public class Ruleset {
		[JsonRequired]
		public long format;
		
		[JsonRequired]
		public string @base;
		
		[JsonRequired]
		public string pdt;

		[JsonIgnore]
		public PdtRuleset Root { get; private set; }

		public void LoadPdt(DirectoryInfo dir) {
			using (StreamReader pdtreader = new StreamReader(dir.FullName + "/" + pdt + ".pdt", Encoding.UTF8)) {
				var src = pdtreader.ReadToEnd();
				Root = new RulesetInterpreter(src, null).Interpret();
			}
		}
	}

	[Binder(typeof(PdtRulesetBinder))]
	public class PdtRuleset {
		public Dictionary<Identifier, InputDefinition> inputs;
		public Dictionary<Identifier, JudgeDefinition> judges;
		public Dictionary<Identifier, ScoreDefinition> scores;
		public Constraint constraints;
		public void Optimize(PdtEvaluatorBase etor) {
			foreach (var i in inputs.Values) {
				if (i.pass != null) foreach (var e in i.pass.Values) {
					etor.Optimize(e);
				}
			}
			foreach (var j in judges.Values) {
				if (j.hit != null) etor.Optimize(j.hit);
				if (j.scores != null) {
					foreach (var s in j.scores) {
						if (s.Key.op != default(Identifier))
							etor.PatchCompound(s.Key.name.Key, s.Key.op.Key, s.Value);
						etor.Optimize(s.Value);
					}
				}
			}
			foreach (var s in scores.Values) {
				if (s.value != null) etor.Optimize(s.value);
			}
			constraints.Optimize(etor);
		}
		public void PrePatch(Chart chart) {
			constraints.PrePatch(chart);
		}
	}
	public class Constraint {
		static readonly PropOp.Arbitrary _arbop = new PropOp.Arbitrary();
		[ElementList]
		public Dictionary<RulesetSelectors, Constraint> Elements = new Dictionary<RulesetSelectors, Constraint>();
		[PropertyList]
		public Dictionary<PropertyKey, PdtExpression> Properties = new Dictionary<PropertyKey, PdtExpression>();
		public void Optimize(PdtEvaluatorBase etor) {
			foreach (var e in Properties.Values) {
				etor.Optimize(e);
			}
			foreach (var e in Elements) {
				e.Key.Optimize(etor);
				e.Value.Optimize(etor);
			}
		}
		public void PrePatch(ChartEvent ev) {
			var etor = ChartPlayer.etor;
			PropSrc src;
			etor.ContextCascadeInsert();
			etor.ContextEvent = ev;
			foreach (var prop in Properties) {
				var name = prop.Key.Name;
				switch (prop.Key.Type) {
					case PropertyType.Property:
						if (ev.PropSrcs.TryGetValue(name, out src))
							etor.ContextSelfValue = src;
						etor.Evaluate(ev.PropOps[name], prop.Value);
						etor.ContextSelfValue = null;
						break;
					case PropertyType.Variable:
						_arbop.Name = name;
						etor.Evaluate(_arbop, prop.Value);
						break;
					default: throw new NotSupportedException("Unknown property key type");
				}
			}
			etor.ContextEvent = null;
			foreach (var el in Elements) {
				var targets = el.Key.Match(ev);
				if (targets == null) continue;
				foreach (var target in targets)
					el.Value.PrePatch(target);
			}
			etor.ContextCascadeDiscard();
		}
	}
	public class PropertyKey {
		public PropertyType Type { get; private set; }
		public int Name { get; private set; }
		public PropertyKey(PropertyType type, string name) {
			Type = type;
			Name = IdentifierManager.SharedInstance.Request(name);
		}
		public override string ToString() {
			switch (Type) {
				case PropertyType.Property: return (string)IdentifierManager.SharedInstance.Retrieve(Name);
				case PropertyType.Variable: return string.Format("@var {0}", IdentifierManager.SharedInstance.Retrieve(Name));
				default: return string.Format("<{0}> {1}", Type, IdentifierManager.SharedInstance.Retrieve(Name));
			}
		}
	}
	public enum PropertyType {
		Property,
		Variable,
	}
	public class PdtRulesetBinder : EmptyBinder {
		public override object ChangeType(object value, Type type, CultureInfo culture) {
			if (value is PdtExpression) {
				var exp = (PdtExpression)value;
				if (type.Equals(typeof(bool))) {
					bool result = false;
					ChartPlayer.etor.Evaluate(new PropOp.Boolean(r => result = r), exp);
					return result;
				}
				else if (type.Equals(typeof(int))) {
					int result = 0;
					ChartPlayer.etor.Evaluate(new PropOp.Integer(r => result = r), exp);
					return result;
				}
				else if (type.Equals(typeof(float))) {
					float result = 0;
					ChartPlayer.etor.Evaluate(new PropOp.Float(r => result = r), exp);
					return result;
				}
				else if (type.Equals(typeof(string))) {
					string result = default(string);
					ChartPlayer.etor.Evaluate(new PropOp.String(r => result = r), exp);
					return result;
				}
				else if (type.Equals(typeof(float[]))) {
					float[] result = null;
					ChartPlayer.etor.Evaluate(new pop_numarr(r => result = r), exp);
					return result;
				}
				else if (type.Equals(typeof(Identifier))) {
					Identifier result = default(Identifier);
					ChartPlayer.etor.Evaluate(new pop_identstr(r => result = r), exp);
					return result;
				}
				else if (type.Equals(typeof(Identifier[]))) {
					Identifier[] result = null;
					ChartPlayer.etor.Evaluate(new pop_identstrarr(r => result = r), exp);
					return result;
				}
			}
			else if (value is string) {
				var exp = (string)value;
				if (type.Equals(typeof(Identifier))) {
					return (Identifier)exp;
				}
				else if (type == typeof(ScoreOperation)) {
					var m = Regex.Match(exp, @"^(\S+)\s*?(\S+)?$");
					var name = new Identifier(m.Groups[1].Value);
					if (!m.Groups[2].Success) return new ScoreOperation { name = name };
					var op = new Identifier(m.Groups[2].Value);
					return new ScoreOperation { name = name, op = op };
				}
			}
			return base.ChangeType(value, type, culture);
		}
#pragma warning disable IDE1006
		class pop_numarr : PdtOperator {
			readonly Action<float[]> _cb;
			public pop_numarr(Action<float[]> cb) : base(16) { _cb = cb; }
			protected override void Execute() {
				var result = new float[LoadedOperandCount];
				for (int i = 0; i < LoadedOperandCount; i++) {
					result[i] = GetOperand(i).AsNumber();
				}
				_cb(result);
			}
		}
		class pop_identstr : PropOp {
			readonly Action<SIdentifier> _cb;
			public pop_identstr(Action<SIdentifier> cb) { _cb = cb; }
			protected override void Execute() {
				var op = GetOperand(0);
				if (op.Type == PdtInternalType.Undefined) _cb(new SIdentifier(op.AsIdentifier()));
				else if (op.Type == PdtInternalType.String) _cb(new SIdentifier(op.AsString()));
				else throw new InvalidCastException("Not an identifier or string");
			}
		}
		class pop_identstrarr : PdtOperator {
			readonly Action<SIdentifier[]> _cb;
			public pop_identstrarr(Action<SIdentifier[]> cb) : base(16) { _cb = cb; }
			protected override void Execute() {
				var result = new SIdentifier[LoadedOperandCount];
				for (int i = 0; i < LoadedOperandCount; i++) {
					var op = GetOperand(i);
					if (op.Type != PdtInternalType.Undefined)
						throw new InvalidCastException("Not an identifier");
					result[i] = new SIdentifier(op.AsIdentifier());
				}
				_cb(result);
			}
		}
#pragma warning restore IDE1006
	}
	public class RulesetViolationException : Exception {
		public RulesetViolationException() { }
		public RulesetViolationException(string message) : base(message) { }
		public RulesetViolationException(string message, Exception innerException) : base(message, innerException) { }
	}
}
