using System;
using System.Diagnostics;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Threading;
using System.Threading.Tasks;

using miew.String;
using miew.Enumerable;
using miew.Tokenization;
using miew.Concurrency;

using Resources = agree.Properties.Resources;

namespace agree
{

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class CommandToken
	{
		//static int next_id = 0;
		public CommandToken(ISysObj iso)
		{
			this.iso = iso;
			//this.id = Interlocked.Increment(ref next_id);
		}

		public ISysObj Context { get { return iso; } }

		readonly public ISysObj iso;
		//readonly public int id;
		protected Task t;
		String error_message = null;

		public String ErrorMessage { get { return error_message; } }

		public Task Task
		{
			get { return t; }
			protected set { t = value; }
		}

		public bool IsComplete { get { return t == null || t.IsCompleted; } }

		public void TransactionStatus(String fmt, params Object[] args)
		{
			SystemInstance.TransactionStatus(this, fmt, args);
		}
		public void AddTransaction(CommandToken ct)
		{
			SystemInstance.AddTransaction(this, ct);
		}

		public void Abort(params String[] args)
		{
			error_message = String.Format(args[0], args.Skip(1).ToArray());
			if (args != null && args.Length > 0)
				SystemInstance.TransactionStatus(this, error_message);
			t = Tasks.CompletedTask;
		}

		public SysObj SystemInstance
		{
			get
			{
				SysObj o;
				ISysObj t = iso;
				while ((o = t as SysObj) == null)
					t = t.SysObjParent;
				return o;
			}
		}

		//public void Execute()
		//{
		//    if (t.Status == TaskStatus.Created)
		//        t.Start();
		//}
	}

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class CommandToken<T> : CommandToken
	{
		public CommandToken(ISysObj so)
			: base(so)
		{
		}

		public new Task<T> Task
		{
			get
			{
				if (t == null)
					throw new Exception();
				return t as Task<T>;
			}
			protected set { t = value; }
		}

		internal void _complete_and_post(T result)
		{
			t = Tasks.FromResult<T>(result);
			SystemInstance.TransactionStatus(this);
		}
		internal void _complete_and_post(String fmt, params Object[] args)
		{
			Debug.Assert(typeof(T) == typeof(String));
			t = Tasks.FromResult<String>(String.Format(fmt, args));
			SystemInstance.TransactionStatus(this);
		}
		public Task<CommandToken> QueueResultInteraction(String s)
		{
			return Task.ContinueWith<CommandToken>(x =>
				{
					var next_cmd = new SysCommands.CmdTokInteractive((ISysObj)x.Result, s);
					return next_cmd.Task.Result;
				},
				CancellationToken.None,
				TaskContinuationOptions.ExecuteSynchronously,
				TaskScheduler.Default);
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class CommandSet
	{
		protected SysObj _so;
		public CommandSet(SysObj so)
		{
			this._so = so;
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class SysCommands : CommandSet
	{
		public SysCommands(SysObj so)
			: base(so)
		{
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		public class CmdTokNop : CommandToken
		{
			public CmdTokNop(ISysObj iso) : base(iso) { }
		};

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		public class CmdTokDisplayMessage : CommandToken<String>
		{
			public CmdTokDisplayMessage(ISysObj iso, String resource_key, params Object[] args)
				: base(iso)
			{
				_complete_and_post(String.Format(SystemInstance.ResourceManager.GetString(resource_key), args));
			}

			public CmdTokDisplayMessage(ISysObj iso) : base(iso) { }

			public void SetMessage(String fmt, params Object[] args)
			{
				_complete_and_post(String.Format(fmt, args));
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		public class CmdTokInteractive : CommandToken<CommandToken>
		{
			public CmdTokInteractive(ISysObj iso, String s_input)
				: base(iso)
			{
				this.t = Tasks.FromResult<CommandToken>(CommandFromString(iso, s_input));
			}

			CommandToken CommandFromString(ISysObj iso, String s_input)
			{
				s_input = s_input.Trim();
				if (s_input.Length == 0)
					return new CmdTokNop(iso);

				if (s_input.StartsWith("/"))
					s_input = "/ " + s_input.Substring(1);

				String[] rgs = s_input.Split(default(Char[]), StringSplitOptions.RemoveEmptyEntries);
				if (rgs.Length == 0)
					return new CmdTokNop(iso);

				String cmd = rgs[0].ToLower();
				rgs = rgs.Skip(1).ToArray();

				if (cmd == ".." || cmd == iso.SysObjParent.SysObjName)
					return new CmdTokChangeDefaultObject(iso, iso.SysObjParent);

				CommandToken ct;
				switch (cmd)
				{
					case "version":
						return new CmdTokDisplayVersionInfo(SystemInstance);

					case "pwd":
						return new CmdTokDisplayCurrentPath(SystemInstance);

					case "cd":
						if (rgs.Length != 1)
							return new CmdTokDisplayMessage(SystemInstance, Resources.ihelpmsg_cd);
						return new CmdTokChangeCurrentPath(SystemInstance, rgs[0]);

					case "help":
						{
							CmdTokDisplayMessage xd = new CmdTokDisplayMessage(SystemInstance);
							ct = xd;

							if (rgs.Length == 0)
							{
								xd.SetMessage(Resources.ihelpmsg_help);
							}
							else
							{
								String sub_help_topic = rgs.StringJoin("_").ToLower();
								String sub_help = SystemInstance.ResourceManager.GetString("ihelpmsg_" + sub_help_topic);
								if (sub_help != null)
									xd.SetMessage(sub_help);
								else
									xd.SetMessage(Resources.ihelpmsg_unknown_help_topic, sub_help_topic);
							}
						}
						break;

					case "/":
					case "select":
						if (rgs.Length == 1)
							return new CmdTokChangeDefaultObject(SystemInstance, rgs[0]);
						ct = new CmdTokDisplayMessage(SystemInstance, Resources.msg_need_target, "select");
						break;

					case "load":
						{
							if (rgs.Length == 0 || rgs.Length > 2)
							{
								ct = new CmdTokDisplayMessage(SystemInstance, Resources.ihelpmsg_load);
							}
							else
							{
								ct = new CmdTokLoadGrammar(SystemInstance, rgs[0], rgs.Length == 2 ? rgs[1] : null);
							}
						}
						break;

					case "cls":
						return new CmdTokClearConsole(SystemInstance);

					case "unload":
						return new CmdTokUnloadGrammar(iso);

					case "save":
						ct = null;
						break;

					case "names":
					case "*":
						return new CmdTokQueryNamePrefix(iso, cmd);

					case "show":
						if (rgs.Length == 1)
						{
							CmdTokQueryNamePrefix qp = new CmdTokQueryNamePrefix(iso, rgs[0]);
							if (qp != null && qp.Task.Result.Length == 1)
								return new CmdTokShowItem(iso, qp.Task.Result[0]);
						}
						ct = new CmdTokDisplayMessage(SystemInstance);
						(ct as CmdTokDisplayMessage).SetMessage(Resources.msg_need_name, cmd);
						break;

					case "parse":
						return new CmdTokParse(iso, rgs.StringJoin(" "));

					case "info":
						return new CmdTokDisplayGrammarInfo(iso);

					default:
						{
							CmdTokQueryNamePrefix rp = new CmdTokQueryNamePrefix(iso, cmd);
							if (rp != null)
							{
								if (rp.Task.Result.Length == 1)
								{
									ISysObj so_new = rp.Task.Result[0];
									if (so_new.SysObjChildren.Any())
										return new CmdTokChangeDefaultObject(SystemInstance, so_new);
									return new CmdTokShowItem(iso, so_new);
								}
								else if (rp.Task.Result.Length > 0)
									return rp;
							}
						}
						ct = new CmdTokDisplayMessage(SystemInstance);
						(ct as CmdTokDisplayMessage).SetMessage(Resources.errmsg_unknown_cmd, cmd, iso.SysObjName);
						break;
				}
				return ct;
			}

		};

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		public class CmdTokLoadGrammar : CommandToken<Grammar>
		{
			public CmdTokLoadGrammar(ISysObj iso, String filename, String name = null, Config config = null)
				: base(iso)
			{
				if (name == null)
					name = Path.GetFileNameWithoutExtension(filename);

				Grammar g = new Grammar(SystemInstance, name, config);

				if (!g.config.system.MultiThreading)
				{
					g.Load(this, filename);
					TaskCompletionSource<Grammar> tcs = new TaskCompletionSource<Grammar>();
					tcs.SetResult(g);
					t = tcs.Task;
				}
				else
				{
					t = new Task<Grammar>(() =>
					{
						try
						{
							g.Load(this, filename);
						}
						catch (Exception ex)
						{
							TransactionStatus(ex.Message);
						}
						return g;
					});
					t.Start();
				}
			}
		};

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		public class CmdTokUnloadGrammar : CommandToken<bool>
		{
			public CmdTokUnloadGrammar(ISysObj target)
				: base(target)
			{
				this.g = target as Grammar;
				bool f_result = false;
				if (target == null)
				{
					TransactionStatus(Resources.msg_need_target, "unload");
				}
				else
				{
					Grammar g = target as Grammar;
					if (g == null)
						TransactionStatus(Resources.ihelpmsg_cannot_unload);
					else
						f_result = true;
				}
				t = Tasks.FromResult<bool>(f_result);
			}
			public Grammar g;
		};


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		public class CmdTokSaveGrammar : CommandToken
		{
			public CmdTokSaveGrammar(ISysObj iso, Grammar target, String filename)
				: base(iso)
			{
				if (target == null)
				{
					Abort(Resources.msg_need_target, "save");
					t = Tasks.FromResult<bool>(false);
					return;
				}
				Grammar g = target as Grammar;
				if (g == null)
				{
					TransactionStatus(Resources.ihelpmsg_cannot_save);
					t = Tasks.FromResult<bool>(false);
					return;
				}
				else
				{
					t = Task.Factory.StartNew(() =>
					{
						g.Save(this, filename);
					});
				}
			}
		};


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		public class CmdTokDisplayVersionInfo : CmdTokDisplayMessage
		{
			public CmdTokDisplayVersionInfo(ISysObj iso)
				: base(iso)
			{
				_complete_and_post(SystemInstance.VersionInfo());
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		public class CmdTokDisplayCurrentPath : CmdTokDisplayMessage
		{
			public CmdTokDisplayCurrentPath(ISysObj iso)
				: base(iso)
			{
				_complete_and_post(Environment.CurrentDirectory);
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		public class CmdTokChangeCurrentPath : CmdTokDisplayMessage
		{
			public CmdTokChangeCurrentPath(ISysObj iso, String s)
				: base(iso)
			{
				s = Path.Combine(Environment.CurrentDirectory, s);
				s += Path.DirectorySeparatorChar;
				s = Path.GetFullPath(s);
				String msg;
				if (Directory.Exists(s))
				{
					Environment.CurrentDirectory = s;
					msg = Environment.CurrentDirectory;
				}
				else
				{
					s = s.Trim(Path.DirectorySeparatorChar);
					msg = String.Format(Resources.msg_path_not_found, s);
				}
				_complete_and_post(msg);
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		public class CmdTokDisplayGrammarInfo : CmdTokDisplayMessage
		{
			public CmdTokDisplayGrammarInfo(ISysObj target)
				: base(target)
			{
				if (target == null)
				{
					Abort(Resources.msg_need_target, "info");
					return;
				}
				Grammar g = target as Grammar;

				String s;
				if (g == null)
					s = Resources.ihelpmsg_no_info;
				else
					s = g.GetInfo();

				_complete_and_post(s);
			}
		}


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		public class CmdTokClearConsole : CommandToken
		{
			public CmdTokClearConsole(SysObj so)
				: base(so)
			{
				t = Tasks.CompletedTask;
			}
		};

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		public class CmdTokQueryNamePrefix : CommandToken<ISysObj[]>
		{
			public CmdTokQueryNamePrefix(ISysObj target, String s_pfx)
				: base(target)
			{
				if (target == null)
				{
					Abort(Resources.msg_need_target, "names");
				}
				else
				{
					t = Tasks.FromResult<ISysObj[]>(SysObjHelper<ISysObj>.GetNamePrefixMatches(target, s_pfx).ToArray());
				}
			}
		};


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		public class CmdTokShowItem : CommandToken<ISysObj>
		{
			public CmdTokShowItem(ISysObj target, ISysObj obj)
				: base(target)
			{
				if (target == null)
				{
					Abort(Resources.msg_need_target, "show");
				}
				else
				{
					t = Tasks.FromResult<ISysObj>(obj);
				}
			}
			public CmdTokShowItem(ISysObj target, IDerivation obj)
				: base(target)
			{
				if (target == null)
				{
					Abort(Resources.msg_need_target, "show");
				}
				else
				{
					t = Tasks.FromResult<IDerivation>(obj);
				}
			}
		};

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		public class CmdTokShowHighlightedItem : CommandToken
		{
			public ISysObj obj_parent;
			public ISysObj obj_highlight;

			public CmdTokShowHighlightedItem(ISysObj target, ISysObj obj_parent, ISysObj obj_highlight)
				: base(target)
			{
				this.obj_parent = obj_parent;
				this.obj_highlight = obj_highlight;

				if (target == null)
				{
					Abort(Resources.msg_need_target, "show");
				}
				else
				{
					t = Tasks.CompletedTask;
				}
			}
		};

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		public class CmdTokParse : CommandToken<ParseControl>
		{
			public CmdTokParse(ISysObj grm, String s)
				: base(grm)
			{
				if (grm == null)
				{
					Abort(Resources.msg_need_target, "parse");
					return;
				}
				if (String.IsNullOrEmpty(s))
				{
					Abort(Resources.msg_nothing_to_parse);
					return;
				}

				Grammar g = grm as Grammar;
				if (g == null)
				{
					Abort(Resources.msg_cannot_parse, grm.SysObjName, SysObj.GetObjectTypeName(grm));
				}
				else
				{
					try
					{
						t = g.Parse(s);
					}
					catch (ParseException ex)
					{
						Abort(ex.Message);
					}
					catch (DebuggingTfsDisplayException ex)
					{
						var exhl = ex as DebuggingTfsDisplayExceptionWithHighlighting;
						if (exhl != null)
							AddTransaction(new CmdTokShowHighlightedItem(grm, exhl.Tfs[0], exhl.TfsHighlight));
						else
						{
							foreach (var te in ex.Tfs)
								AddTransaction(new CmdTokShowItem(grm, te));
						}
						Abort("parse interrupted");
					}
				}
			}

			public CmdTokParse(ISysObj grm, TokenSet ts)
				: base(grm)
			{
				if (grm == null)
				{
					Abort(Resources.msg_need_target, "parse");
					return;
				}
				if (ts == null || ts.Count == 0)
				{
					Abort(Resources.msg_nothing_to_parse);
					return;
				}

				Grammar g = grm as Grammar;
				if (g == null)
				{
					Abort(Resources.msg_cannot_parse, grm.SysObjName, SysObj.GetObjectTypeName(grm));
				}
				else
				{
					try
					{
						t = g.Parse(ts);
					}
					catch (ParseException ex)
					{
						Abort(ex.Message);
					}
					catch (DebuggingTfsDisplayException ex)
					{
						var exhl = ex as DebuggingTfsDisplayExceptionWithHighlighting;
						if (exhl != null)
							AddTransaction(new CmdTokShowHighlightedItem(grm, exhl.Tfs[0], exhl.TfsHighlight));
						else
						{
							foreach (var te in ex.Tfs)
								AddTransaction(new CmdTokShowItem(grm, te));
						}
						Abort("parse interrupted");
					}
				}
			}
		};


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		public class CmdTokChangeDefaultObject : CommandToken<ISysObj>
		{

			public CmdTokChangeDefaultObject(ISysObj target, ISysObj so_new)
				: base(target)
			{
				Finish(target, so_new);
			}

			public CmdTokChangeDefaultObject(ISysObj target, String s_obj)
				: base(target)
			{
				if (target == null)
				{
					Abort(Resources.msg_need_target, "select");
					return;
				}
				ISysObj so_new;
				if (!target.SysObjChildren.TryGetValue(s_obj, out so_new))
				{
					Abort(Resources.errmsg_name_not_found, s_obj, target.SysObjName);
					return;
				}
				Finish(target, so_new);
			}

			public void Finish(ISysObj target, ISysObj so_new)
			{
				if (target == null)
				{
					Abort(Resources.msg_need_target, "select");
					return;
				}
				t = Tasks.FromResult<ISysObj>(so_new);
			}

		};


	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class GrammarCommands : SysCommands
	{
		public GrammarCommands(SysObj so)
			: base(so)
		{
		}

	};
}