using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

using glue.Tokenization;
using glue;

namespace agree.Parse
{

	public class ParserConfiguration : INotifyPropertyChanged
	{
		static readonly Char[] dot_space = new Char[] { '.', ' ' };

		/// <summary>
		/// Options for configuring which rule daughter the parser will try to unify first
		/// </summary>
		public enum KeyStrategy
		{
			/// <summary>
			/// The parser will unify the rule daughter whose key feature (configurable) has a certain key
			/// value (configurable)
			/// </summary>
			KeyDriven,	// use, e.g.  KEY-ARG +
			/// <summary>
			/// The parser will unify rule daughters left-to-right
			/// </summary>
			LeftToRight,
			/// <summary>
			/// The parser will unify rule daughters right-to-left
			/// </summary>
			RightToLeft,
			/// <summary>
			/// The parser will unify the rule daughter that is designated as the head daughter
			/// </summary>
			HeadDaughter,	// use, e.g. HD-DTR		(todo)
		};

		public enum DependencyScope
		{
			Unidirectional,
			TwoWay,
		};

		bool f_irregular_forms_only = false;
		KeyStrategy key_strategy = KeyStrategy.KeyDriven;
		Double timeout_sec = Double.MaxValue;
		Type T_tokenizer = typeof(SpaceCharTokenizer);
		DependencyScope chart_dependency_scope = DependencyScope.TwoWay;
		KeyValuePair<String[], String[]>[] chart_dependency_paths = null;

		/// <summary>
		/// Strategy used by the parser to determine which of a mother rule's unifications to perform
		/// first. The available options are documented in ParseConfiguration.KeyStrategy
		/// </summary>
		public KeyStrategy ParsingStrategy
		{
			get { return key_strategy; }
			set
			{
				if (value != key_strategy)
				{
					key_strategy = value;
					NotifyPropertyChanged("ParsingStrategy");
				}
			}
		}

		/// <summary>
		/// If 'true', the presence of an irregular form in a morphological analysis hypothesis will
		/// block the proposal of any regularly inflected forms.
		/// </summary>
		public bool IrregularFormsOnly
		{
			get { return f_irregular_forms_only; }
			set
			{
				if (value != f_irregular_forms_only)
				{
					f_irregular_forms_only = value;
					NotifyPropertyChanged("IrregularFormsOnly");
				}
			}
		}

		/// <summary>
		/// Number of seconds allowed per parse. If the parse is not complete within this amount of time,
		/// the parse will be aborted with a ParseTimeoutException.
		/// </summary>
		public Double ItemTimeoutSeconds
		{
			get { return timeout_sec; }
			set
			{
				if (value != timeout_sec)
				{
					timeout_sec = value;
					NotifyPropertyChanged("TimeoutSeconds");
				}
			}
		}

		public Type TokenizerType
		{
			get { return T_tokenizer; }
			set
			{
				if (value == null)
					throw new ArgumentNullException();
				if (!value.HasInterface(typeof(ITokenizer)))
					throw new ArgumentException("Tokenizer type must be derived from ITokenizer");
				if (value != T_tokenizer)
				{
					T_tokenizer = value;
					NotifyPropertyChanged("TokenizerType");
				}
			}
		}

		public DependencyScope ChartDependencyScope
		{
			get { return chart_dependency_scope; }
			set
			{
				if (value != chart_dependency_scope)
				{
					chart_dependency_scope = value;
					NotifyPropertyChanged("ChartDependencyScope");
				}
			}
		}

		public IEnumerable<KeyValuePair<String, String>> ChartDependencyPaths
		{
			set
			{
				chart_dependency_paths = value
							.Select(kvp => new KeyValuePair<String[], String[]>(
												kvp.Key.Trim('\"').Split(dot_space),
												kvp.Value.Trim('\"').Split(dot_space)))
							.ToArray();
				if (chart_dependency_paths.Length == 0)
					chart_dependency_paths = null;
				NotifyPropertyChanged("ChartDependencyPaths");
			}
		}
		public KeyValuePair<String[], String[]>[] ChartDependencyPathsArray { get { return chart_dependency_paths; } }


		public event PropertyChangedEventHandler PropertyChanged;

		/// <summary>
		/// Notify interested parties that a configuration option has changed.
		/// </summary>
		void NotifyPropertyChanged(String s_field)
		{
			var h = PropertyChanged;
			if (h != null)
				h(this, new PropertyChangedEventArgs(s_field));
		}
	};

	public class NodeLabelConfiguration : INotifyPropertyChanged
	{
		static readonly String[] rg_string_empty = new String[0];
		static readonly Char[] dot_space = new Char[] { '.', ' ' };

		bool simple_tree_display = false;
		String[] label_path = null;
		String[] prefix_path = null;
		String[] suffix_path = null;
		String[] recursive_path = null;
		String[] local_path = null;
		String[] label_fs_path = null;
		String label_template_type = null;
		String meta_template_type = "meta";

		public bool SimpleTreeDisplay
		{
			get { return simple_tree_display; }
			set
			{
				if (value != simple_tree_display)
				{
					simple_tree_display = value;
					NotifyPropertyChanged("SimpleTreeDisplay");
				}
			}
		}

		bool CheckAndCleanPath(ref String[] path, ref String[] new_value)
		{
			if (new_value != null)
				new_value = new_value.Where(s => !String.IsNullOrWhiteSpace(s.Trim(dot_space))).ToArray();
			else
				new_value = rg_string_empty;

			if (path == new_value || (path != null && path.SequenceEqual(new_value)))
				return false;
			path = new_value;
			return true;
		}

		public String[] LabelPath
		{
			get { return label_path; }
			set
			{
				if (simple_tree_display)
					throw new Exception("Cannot specify 'LabelPath' when using 'SimpleTreeDisplay'");
				if (value == label_path)
					return;
				if (CheckAndCleanPath(ref label_path, ref value))
				{
					NotifyPropertyChanged("LabelPath");
				}
			}
		}

		public String[] PrefixPath
		{
			get { return prefix_path; }
			set
			{
				if (simple_tree_display)
					throw new Exception("Cannot specify 'PrefixPath' when using 'SimpleTreeDisplay'");
				if (value == prefix_path)
					return;
				if (CheckAndCleanPath(ref prefix_path, ref value))
				{
					NotifyPropertyChanged("PrefixPath");
				}
			}
		}

		public String[] SuffixPath
		{
			get { return suffix_path; }
			set
			{
				if (simple_tree_display)
					throw new Exception("Cannot specify 'SuffixPath' when using 'SimpleTreeDisplay'");
				if (value == suffix_path)
					return;
				if (CheckAndCleanPath(ref suffix_path, ref value))
				{
					NotifyPropertyChanged("SuffixPath");
				}
			}
		}

		public String[] RecursivePath
		{
			get { return recursive_path; }
			set
			{
				if (simple_tree_display)
					throw new Exception("Cannot specify 'RecursivePath' when using 'SimpleTreeDisplay'");
				if (value == recursive_path)
					return;
				if (CheckAndCleanPath(ref recursive_path, ref value))
				{
					NotifyPropertyChanged("RecursivePath");
				}
			}
		}

		public String[] LocalPath
		{
			get { return local_path; }
			set
			{
				if (simple_tree_display)
					throw new Exception("Cannot specify 'LocalPath' when using 'SimpleTreeDisplay'");
				if (value == local_path)
					return;
				if (CheckAndCleanPath(ref local_path, ref value))
				{
					NotifyPropertyChanged("LocalPath");
				}
			}
		}

		public String[] LabelFsPath
		{
			get { return label_fs_path; }
			set
			{
				if (simple_tree_display)
					throw new Exception("Cannot specify 'LabelFsPath' when using 'SimpleTreeDisplay'");
				if (value == label_fs_path)
					return;
				if (CheckAndCleanPath(ref label_fs_path, ref value))
				{
					NotifyPropertyChanged("LabelFsPath");
				}
			}
		}

		public String LabelTemplateType
		{
			get { return label_template_type; }
			set
			{
				if (simple_tree_display)
					throw new Exception("Cannot specify 'LabelTemplateType' when using 'SimpleTreeDisplay'");
				if (value != label_template_type)
				{
					label_template_type = value;
					NotifyPropertyChanged("LabelTemplateType");
				}
			}
		}

		public String MetaTemplateType
		{
			get { return meta_template_type; }
			set
			{
				if (simple_tree_display)
					throw new Exception("Cannot specify 'MetaTemplateType' when using 'SimpleTreeDisplay'");
				if (value != meta_template_type)
				{
					meta_template_type = value;
					NotifyPropertyChanged("MetaTemplateType");
				}
			}
		}


		public event PropertyChangedEventHandler PropertyChanged;

		/// <summary>
		/// Notify interested parties that a configuration option has changed.
		/// </summary>
		void NotifyPropertyChanged(String s_field)
		{
			var h = PropertyChanged;
			if (h != null)
				h(this, new PropertyChangedEventArgs(s_field));
		}
	};
}