using Cryville.Common;
using Cryville.Common.Collections.Generic;
using Cryville.Common.Pdt;
using Cryville.Crtr.Extension;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace Cryville.Crtr.Ruleset {
	public class RulesetDefinition : MetaInfo {
		public const long CURRENT_FORMAT = 2;

		[JsonRequired]
		public long format;
		
		public string @base;

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

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

	[Binder(typeof(PdtBinder))]
	public class PdtRuleset {
		public Dictionary<Identifier, ConfigDefinition> configs;
		public Dictionary<Identifier, MotionDefinition> motions;
		public Dictionary<Identifier, InputDefinition> inputs;
		public Dictionary<Identifier, PdtExpression> areas;
		public Dictionary<Identifier, JudgeDefinition> judges;
		public Dictionary<Identifier, ScoreDefinition> scores;
		public Constraint constraints;
		public void Optimize(PdtEvaluatorBase etor) {
			foreach (var i in inputs) {
				var input = i.Value;
				if (input.pass != null) foreach (var e in input.pass) {
					etor.Optimize(e.Value);
				}
			}
			if (areas != null) foreach (var a in areas) {
				etor.Optimize(a.Value);
			}
			foreach (var j in judges) {
				var judge = j.Value;
				if (judge.hit != null) etor.Optimize(judge.hit);
				if (judge.on_hit != null) OptimizeJudgeActions(judge.on_hit, etor);
				if (judge.on_miss != null) OptimizeJudgeActions(judge.on_miss, etor);
			}
			foreach (var s in scores) {
				var score = s.Value;
				if (score.value != null) etor.Optimize(score.value);
			}
			constraints.Optimize(etor);
		}
		void OptimizeJudgeActions(PairList<JudgeAction, PdtExpression> actions, PdtEvaluatorBase etor) {
			foreach (var a in actions) a.Key.Optimize(etor, a.Value);
		}
		public void PrePatch(Chart chart) {
			constraints.PrePatch(chart);
		}
	}
	public class ConfigDefinition {
		public string category;
		public ConfigType type;
		public PdtExpression @default;
		public PdtExpression range;
		public PdtExpression value;
	}
	public enum ConfigType {
		unknown, number, number_stepped,
	}
	public class MotionDefinition {
		// TODO
	}
	public class InputDefinition {
		public int dim;
		public string pdim;
		public bool notnull;
		public PairList<Identifier, PdtExpression> pass;
	}
	public class JudgeDefinition {
		public int stack;
		public int prop;
		public PdtExpression clip;
		public PdtExpression input;
		public PdtExpression hit;
		public PdtExpression persist;
		public PairList<JudgeAction, PdtExpression> on_hit;
		public PairList<JudgeAction, PdtExpression> on_miss;
#pragma warning disable IDE1006
		public PairList<ScoreOperation, PdtExpression> scores {
			set {
				if (on_hit == null) on_hit = new PairList<JudgeAction, PdtExpression>();
				int i = 0;
				foreach (var s in value) {
					on_hit.Insert(i++, new JudgeAction.Score(s.Key), s.Value);
				}
			}
		}
		public Identifier[] pass {
			set {
				if (on_hit == null) on_hit = new PairList<JudgeAction, PdtExpression>();
				on_hit.Add(new JudgeAction.Pass(Enumerable.Empty<string>(), value), PdtExpression.Empty);
			}
		}
		public Identifier[] miss {
			set {
				if (on_miss == null) on_miss = new PairList<JudgeAction, PdtExpression>();
				on_miss.Add(new JudgeAction.Pass(Enumerable.Empty<string>(), value), PdtExpression.Empty);
			}
		}
#pragma warning restore IDE1006
	}
	public class ScoreOperation {
		public Identifier name;
		public Identifier op;
		public ScoreOperation(Identifier name, Identifier op) {
			this.name = name;
			this.op = op;
		}
		public ScoreOperation(string str) {
			var m = Regex.Match(str, @"^(\S+)\s*?(\S+)?$");
			name = new Identifier(m.Groups[1].Value);
			if (!m.Groups[2].Success) return;
			op = new Identifier(m.Groups[2].Value);
		}
		public override string ToString() {
			if (op == default(Identifier)) return name.ToString();
			else return string.Format("{0} {1}", name, op);
		}
	}
	public class ScoreDefinition {
		public PdtExpression value;
		public float init = 0;
		public string format = "";
	}
	public class Constraint {
		static readonly PropOp.Arbitrary _arbop = new PropOp.Arbitrary();
		[ElementList]
		public PairList<RulesetSelectors, Constraint> Elements = new PairList<RulesetSelectors, Constraint>();
		[PropertyList]
		public PairList<ConstraintKey, PdtExpression> Properties = new PairList<ConstraintKey, PdtExpression>();
		public void Optimize(PdtEvaluatorBase etor) {
			foreach (var e in Properties) {
				etor.Optimize(e.Value);
			}
			foreach (var e in Elements) {
				e.Key.Optimize(etor);
				e.Value.Optimize(etor);
			}
		}
		public void PrePatch(ChartEvent ev) {
			var etor = PdtEvaluator.Instance;
			PropSrc src;
			etor.ContextCascadeInsert();
			etor.ContextEvent = ev;
			foreach (var prop in Properties) {
				var name = prop.Key.Name;
				switch (prop.Key.Type) {
					case ConstraintType.Property:
						if (ev.PropSrcs.TryGetValue(name, out src))
							etor.ContextSelfValue = src;
						etor.Evaluate(ev.PropOps[name], prop.Value);
						etor.ContextSelfValue = null;
						break;
					case ConstraintType.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 ConstraintKey {
		public ConstraintType Type { get; private set; }
		public int Name { get; private set; }
		public ConstraintKey(ConstraintType type, string name) {
			Type = type;
			Name = IdentifierManager.Shared.Request(name);
		}
		public override string ToString() {
			switch (Type) {
				case ConstraintType.Property: return (string)IdentifierManager.Shared.Retrieve(Name);
				case ConstraintType.Variable: return string.Format("@var {0}", IdentifierManager.Shared.Retrieve(Name));
				default: return string.Format("<{0}> {1}", Type, IdentifierManager.Shared.Retrieve(Name));
			}
		}
	}
	public enum ConstraintType {
		Property,
		Variable,
	}
	public class RulesetViolationException : Exception {
		public RulesetViolationException() { }
		public RulesetViolationException(string message) : base(message) { }
		public RulesetViolationException(string message, Exception innerException) : base(message, innerException) { }
	}
}
