using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;
using nmo = Microsoft.SqlServer.Management.Nmo;
using smo = Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlServer.Management.Nmo;
using Microsoft.SqlServer.Management.Smo;
using System.Globalization;

namespace Microsoft.SqlServer.NotificationServices.Rules.Web
{
	[ToolboxData("<{0}:SubscriptionConditionControl runat=server></{0}:SubscriptionConditionControl>")]
	public class SubscriptionConditionControl : CompositeControl
	{
		const string conditionStateName = 
			"Microsoft.SqlServer.NotificationServices.Rules.Web.ConditionState";

		private string server;
		private string instance;
		private string application;
		private string subscriptionClass;
		private string ruleName;
        private string subscriberId;
		private ConditionState conditionState;

		[Bindable(true)]
		[Category("General")]
		[DefaultValue(".")]
		public string Server
		{
			get { return server; }
			set { server = value; }
		}


		[Bindable(true)]
		[Category("General")]
		[DefaultValue("")]
		public string Instance
		{
			get { return instance; }
			set { instance = value; }
		}


		[Bindable(true)]
		[Category("General")]
		[DefaultValue("")]
		public string Application
		{
			get { return application; }
			set { application = value; }
		}


		[Bindable(true)]
		[Category("General")]
		[DefaultValue("")]
		public string SubscriptionClass
		{
			get { return subscriptionClass; }
			set { subscriptionClass = value; }
		}


		[Bindable(true)]
		[Category("General")]
		[DefaultValue("")]
		public string RuleName
		{
			get { return ruleName; }
			set { ruleName = value; }
		}


        [Bindable(true)]
        [Category("General")]
        [DefaultValue("")]
        public string SubscriberId
        {
            get { return subscriberId; }
            set { subscriberId = value; }
        }


		private ConditionState State
		{
			get
			{
				if (null == conditionState)
				{
					try
					{
						conditionState = this.Page.Session[conditionStateName] as ConditionState;
					}
					catch
					{
						// eat exceptions retrieving state (e.g., for designer)
					}
					if (null == conditionState)
					{
						conditionState = new ConditionState(this);
						try
						{
							this.Page.Session[conditionStateName] = conditionState;
						}
						catch
						{
							// eat exceptions settings state (e.g., for designer)
						}
					}
				}
				conditionState.Parent = this;
				return conditionState;
			}
		}


		protected override void CreateChildControls()
		{
			try
			{
				State.Render();
			}
			catch (Exception e)
			{
				System.Windows.Forms.MessageBox.Show(e.ToString());
				throw;
			}
		}

		internal class ConditionState
		{
			private SubscriptionCreator creator;
			private Dictionary<string, EventHandler> handlers = new Dictionary<string, EventHandler>();
			private SubscriptionConditionControl parent;
			private nmo.SubscriptionConditionEventRule rule;

			internal SubscriptionConditionControl Parent
			{
				get { return this.parent; }
				set { this.parent = value; }
			}

			internal ConditionState(SubscriptionConditionControl parent)
			{
				this.parent = parent;
				InitializeComponents();
			}

			private void InitializeComponents()
			{
				// Initialize rule object.
				try
				{
					smo.Server server = new smo.Server(parent.Server);
					nmo.Instance instance = server.NotificationServices.Instances[Parent.Instance];
					nmo.Application application = instance.Applications[Parent.Application];
					nmo.SubscriptionClass subscriptionClass = application.SubscriptionClasses[Parent.SubscriptionClass];
					rule = subscriptionClass.SubscriptionConditionEventRules[Parent.RuleName];
				}
				catch
				{
					// eat exceptions (just means we aren't populated)
				}

				creator = new SubscriptionCreator();
				creator.ParentContext = rule;
			}

			internal void Render()
			{
				// check if a button was pressed last time around
				string component = null;
				try
				{
					component = Parent.Page.Request[RulesUserControl<smo.SqlSmoObject>.PostBackHiddenField];
				}
				catch
				{
					// Eat errors retrieving request for designer.
				}

				if (!String.IsNullOrEmpty(component) && handlers.ContainsKey(component))
				{
					handlers[component](Parent, new EventArgs());
				}

				creator.AddControls(Parent, handlers);

				parent.Page.ClientScript.RegisterHiddenField(
					RulesUserControl<smo.SqlSmoObject>.PostBackHiddenField,
					String.Empty);
			}
		}


		// Event indicating an SMO object has been chosen using a control.
		public class SmoEventArgs<T> : EventArgs
			where T : smo.SqlSmoObject
		{
			public SmoEventArgs(T parent)
				: base()
			{
				this.parent = parent;
			}

			public T ParentContext
			{
				get { return this.parent; }
			}

			private T parent;
		}


		// Abstract control supporting the interactive construction of "Rules"
		// components. The type P is the parent context for the control, e.g.,
		// "TableViewBase" for a "SimpleLeafConditionControl".
		public abstract class RulesUserControl<P>
			where P : smo.SqlSmoObject
		{
			protected Guid guid = Guid.NewGuid();

			public const string PostBackHiddenField = "RulesUserControlPostBack";

			// Gets or sets the parent context for the control.
			public P ParentContext
			{
				get { return this.parentContext; }
				set
				{
					this.parentContext = value;
					this.UpdateParentContext();
				}
			}

			protected string BuildCustomHandler(
				string componentName,
				Dictionary<string, EventHandler> handlers,
				EventHandler handler)
			{
				string returnValue = componentName + "_" + this.guid.ToString("N", CultureInfo.InvariantCulture);

				handlers[returnValue] = handler;

				return string.Format(CultureInfo.InvariantCulture, "theForm.{0}.value = '{1}'; theForm.submit();",
					PostBackHiddenField, returnValue);
			}

			// Handler for SMO object selection events.
			public void HandleParentContextChange(
				object sender, SmoEventArgs<P> e)
			{
				this.ParentContext = e.ParentContext;
			}

			public abstract void AddControls(SubscriptionConditionControl parent, Dictionary<string, EventHandler> handlers);

			// Handles changes to the parent context.
			protected abstract void UpdateParentContext();

			protected P parentContext;
		}

		public class SubscriptionCreator : RulesUserControl<nmo.SubscriptionConditionEventRule>
		{
			public SubscriptionCreator()
				: base()
			{
				InitializeComponents();
			}

			private void InitializeComponents()
			{
				andConditionControl = new AndConditionControl();
				subscriber = new TextBox();
				subscriber.Width = 150;
			}

			public override void AddControls(SubscriptionConditionControl parent, Dictionary<string, EventHandler> handlers)
			{
				nmo.SubscriptionFieldCollection fields = null;

				if (null != this.ParentContext)
				{
					fields = this.ParentContext.Parent.SubscriptionFields;
				}

				fieldTextBoxes.Clear();
				for (int i = 0; null != fields && i < fields.Count; i++)
				{
					TextBox field = new TextBox();
					field.Width = 150;
					field.ID = fields[i].Name;
					fieldTextBoxes.Add(field);
				}

				LinkButton addButton;

				addButton = new LinkButton();
				addButton.Text = "Add Subscription";
				addButton.Click += new EventHandler(addButton_Click);

				if (String.IsNullOrEmpty(subscriber.Text))
				{
					subscriber.Text = parent.SubscriberId;
				}

				if (!String.IsNullOrEmpty(subscriber.Text))
				{
					subscriber.Enabled = false;
				}

                parent.Controls.Add(new LiteralControl("<table><tr><td align='right'>Subscriber:</td><td>"));
                parent.Controls.Add(subscriber);
                parent.Controls.Add(new LiteralControl("</td></tr>"));


				for (int i = 0; null != fields && i < fields.Count; i++)
				{
					parent.Controls.Add(new LiteralControl("<tr><td align='right' width='170px'>" + fields[i].Name + ":</td><td width='250px'>"));
					parent.Controls.Add(fieldTextBoxes[i]);
					parent.Controls.Add(new LiteralControl("</td>"));
				}
				parent.Controls.Add(new LiteralControl("</table>"));
				andConditionControl.AddControls(parent, handlers);

				parent.Controls.Add(addButton);
			}

			private void addButton_Click(object sender, EventArgs e)
			{
				NSInstance instance = new NSInstance(ParentContext.Parent.Parent.Parent.Name);
				NSApplication application = new NSApplication(instance, ParentContext.Parent.Parent.Name);
				Subscription subscription = new Subscription(application, ParentContext.Parent.Name);

				subscription.SubscriberId = subscriber.Text;
				foreach (TextBox field in fieldTextBoxes)
				{
					subscription.SetFieldValue(field.ID,
						field.Text ?? String.Empty);
				}

				subscription.RuleName = ParentContext.Name;
				subscription.Condition = this.andConditionControl.Condition;

				subscription.Add();

				Page page = sender as Page;
				if (null != page)
				{
					(sender as SubscriptionConditionControl).Page.Response.Redirect("SubscriptionAdded.html");
				}
			}

			protected override void UpdateParentContext()
			{
				if (null != this.ParentContext)
				{
					Database db = this.ParentContext.Parent.Parent.Parent.Parent.Parent.Databases[
						this.ParentContext.Parent.Parent.DatabaseName];
					TableViewBase input = db.Views[this.ParentContext.InputTypeName, this.ParentContext.InputTypeSchema];
					if (null == input)
					{
						input = db.Tables[this.ParentContext.InputTypeName, this.ParentContext.InputTypeSchema];
					}

					this.andConditionControl.ParentContext = input;
				}
			}

			TextBox subscriber;
			AndConditionControl andConditionControl;
			List<TextBox> fieldTextBoxes = new List<TextBox>();
		}



		// A control supporting the interactive construction of an "AndCondition".
		public class AndConditionControl : RulesUserControl<smo.TableViewBase>
		{
			// Default constructor.
			public AndConditionControl()
				: base()
			{
				this.AddCondition();
				this.conditions[0].Adding = true;
			}

			// Gets the condition built with this control.
			public Condition Condition
			{
				get
				{
					AndCondition condition = new AndCondition();

					foreach (ConditionPlaceholder c in this.conditions)
					{
						// Only add conditions for placeholders that are active.
						if (c.Adding)
						{
							condition.Children.Add(c.Condition);
						}
					}

					// If the AndCondition has no children, add an "always true" condition
					if (0 == condition.Children.Count)
					{
						condition.Children.Add(new BooleanLeafCondition(true));
					}

					return condition;
				}
			}

			// Add a new ConditionPlaceholder to the control.
			private void AddCondition()
			{
				ConditionPlaceholder condition = new ConditionPlaceholder();
				conditions.Add(condition);

				condition.Add += new EventHandler(OnAdd);
				condition.Delete += new EventHandler(OnDelete);
				condition.ParentContext = this.ParentContext;
			}

			// Handles activation of a component placeholder.
			private void OnAdd(object sender, EventArgs e)
			{
				this.AddCondition();
			}

			// Handles deactivation of a component placeholder.
			private void OnDelete(object sender, EventArgs e)
			{
				ConditionPlaceholder deleted = sender as ConditionPlaceholder;
				if (null != deleted)
				{
					conditions.Remove(deleted);
				}
			}

			public override void AddControls(SubscriptionConditionControl parent, Dictionary<string, EventHandler> handlers)
			{
				foreach (ConditionPlaceholder c in conditions)
				{
					c.AddControls(parent, handlers);
					parent.Controls.Add(new LiteralControl("<BR/>"));
				}
			}

			protected override void UpdateParentContext()
			{
				foreach (ConditionPlaceholder c in this.conditions)
				{
					c.ParentContext = this.ParentContext;
				}
			}

			private List<ConditionPlaceholder> conditions = new List<ConditionPlaceholder>();
		}


		// Control representing a condition component within the "AndConditionControl".
		public class ConditionPlaceholder : RulesUserControl<smo.TableViewBase>
		{
			// Default constructor
			public ConditionPlaceholder()
				: base()
			{
				this.InitializeComponents();
			}

			private void InitializeComponents()
			{
				this.conditionControl = new SimpleLeafConditionControl();
				// conditionControl
				this.Adding = false;
			}

			public override void AddControls(SubscriptionConditionControl parent, Dictionary<string, EventHandler> handlers)
			{
				if (!adding)
				{
					LinkButton addRemoveButton = new LinkButton();
					addRemoveButton.Width = AddRemoveButtonWidth;
					addRemoveButton.OnClientClick = this.BuildCustomHandler(
						"AddRemoveCondition_", handlers, new EventHandler(addRemoveButton_Click));
					addRemoveButton.Text = buttonText;
					parent.Controls.Add(addRemoveButton);
				}

				// If necessary add control.
				if (this.Adding)
				{
					this.conditionControl.AddControls(parent, handlers);
				}
			}

			// Gets the condition built with this control.
			public Condition Condition
			{
				get
				{
					return this.conditionControl.Condition;
				}
			}

			private void addRemoveButton_Click(object sender, EventArgs e)
			{
				this.Adding = !this.Adding;
			}

			// Event raised when this placeholder is activated.
			public event EventHandler Add;

			// Event raised when this placeholder is deactivated.
			public event EventHandler Delete;

			// Gets or sets the state of the placeholder. Value is true when the placeholder
			// is active.
			internal bool Adding
			{
				get { return this.adding; }
				set
				{
					this.adding = value;
					if (this.Adding)
					{
						this.buttonText = "Delete";
						if (null != this.Add)
						{
							this.Add(this, new EventArgs());
						}
					}
					else
					{
						this.buttonText = "Add Criteria";
						if (null != this.Delete)
						{
							this.Delete(this, new EventArgs());
						}
					}
				}
			}

			protected override void UpdateParentContext()
			{
				this.conditionControl.ParentContext = this.ParentContext;
			}

			private SimpleLeafConditionControl conditionControl;
			private bool adding;
			private string buttonText;

			internal const int AddRemoveButtonWidth = 80;
		}


		// A control allowing the interactive construction of a "SimpleLeafCondition" of the
		// form "Field op Value". This control's parent context is "TableViewBase", the "InputType"
		// for the rule.
		public class SimpleLeafConditionControl : RulesUserControl<smo.TableViewBase>
		{
			// Default constructor
			public SimpleLeafConditionControl()
				: base()
			{
				this.InitializeComponents();
			}

			private void InitializeComponents()
			{
				this.columns = new ColumnChooser();
				this.operators = new DropDownList();
				this.parameter = new TextBox();

				// operators
				this.operators.Width = OperatorChooserWidth;
				this.operators.AutoPostBack = true;
				this.PopulateOperators();
				this.operators.SelectedIndex = 0;

				// parameter
				this.parameter.Width = ValueTextBoxWidth;
			}

			public override void AddControls(SubscriptionConditionControl parent, Dictionary<string, EventHandler> handlers)
			{
				this.columns.AddControls(parent, handlers);

				parent.Controls.Add(this.operators);
				parent.Controls.Add(this.parameter);
			}

			// Gets the condition built using this control. The condition has the form "Column operator Parameter"
			public Condition Condition
			{
				get
				{
					SimpleLeafCondition condition = new SimpleLeafCondition();
					Column column = this.columns.Selected;

					if (null != column)
					{
						condition.Left = new FieldValue(column.Name);
					}

					condition.Operator =
						Operators[this.operators.Items[this.operators.SelectedIndex].Value];

					// parse the text according to the data type of the column
					if (null != column)
					{
						try
						{
							bool parseSucceeded = false;
							long longValue;
							decimal decimalValue;
							DateTime dateTimeValue;
							switch (column.DataType.SqlDataType)
							{
								case SqlDataType.BigInt:
								case SqlDataType.Int:
								case SqlDataType.SmallInt:
								case SqlDataType.TinyInt:
									parseSucceeded = Int64.TryParse(this.parameter.Text, out longValue);
									if (parseSucceeded)
									{
										condition.Right = longValue;
									}
									break;
								case SqlDataType.SmallMoney:
								case SqlDataType.Money:
								case SqlDataType.Decimal:
								case SqlDataType.Float:
								case SqlDataType.Real:
									parseSucceeded = Decimal.TryParse(this.parameter.Text, out decimalValue);
									if (parseSucceeded)
									{

										condition.Right = decimalValue;
									}
									break;
								case SqlDataType.DateTime:
								case SqlDataType.SmallDateTime:
									parseSucceeded = DateTime.TryParse(this.parameter.Text, out dateTimeValue);
									if (parseSucceeded)
									{
										condition.Right = dateTimeValue;
									}
									break;
								default:
									break;
							}
							if (!parseSucceeded)
							{
								condition.Right = new ConstantValue(this.parameter.Text);
							}
						}
						catch
						{
						}
					}

					return condition;
				}
			}

			// Adds all "SimpleLeafOperators" supported by this control.
			private void PopulateOperators()
			{
				foreach (string op in Operators.Keys)
				{
					this.operators.Items.Add(op);
				}
			}

			// Gets a dictionary mapping from UI operator descriptions to "SimpleLeafOperators".
			static public Dictionary<string, SimpleOperator> Operators
			{
				get
				{
					if (null == rulesOperators)
					{
						Dictionary<string, SimpleOperator> temp =
							new Dictionary<string, SimpleOperator>();
						temp["="] = SimpleOperator.Equals;
						temp[">"] = SimpleOperator.GreaterThan;
						temp["<"] = SimpleOperator.LessThan;
						temp[">="] = SimpleOperator.GreaterThanOrEqualTo;
						temp["<="] = SimpleOperator.LessThanOrEqualTo;
						temp["LIKE"] = SimpleOperator.Like;
						temp["<>"] = SimpleOperator.NotEquals;
						rulesOperators = temp;
					}
					return rulesOperators;
				}
			}

			protected override void UpdateParentContext()
			{
				this.columns.ParentContext = this.ParentContext;
			}

			private ColumnChooser columns;
			private DropDownList operators;
			private System.Web.UI.WebControls.TextBox parameter;
			static private Dictionary<string, SimpleOperator> rulesOperators;

			internal const int ColumnChooserWidth = 120;
			internal const int OperatorChooserWidth = 70;
			internal const int ValueTextBoxWidth = 180;
		}

		// "ComboBox" selecting a "Column" given a "TableViewBase" parent context.
		public class ColumnChooser : SmoListBox<smo.Column, smo.TableViewBase>
		{
			protected override smo.Column GetSelectedFromParent(string name)
			{
				return this.ParentContext.Columns[this.selectedValue];
			}

			override protected void PopulateItems()
			{
				foreach (Column column in this.ParentContext.Columns)
				{
					this.items.Add(column.Name);
				}
			}
		}

		// An abstract control allowing the selection of an SMO object retrieved through enumeration.
		// Type P is the parent SMO class, e.g., "Server", and type T is the type selected by the
		// implementing class, e.g., "Database". When the "ParentContext" is set, the "ComboBox" is
		// populated.
		public abstract class SmoListBox<T, P> : RulesUserControl<P>
			where T : smo.SqlSmoObject
			where P : smo.SqlSmoObject
		{
			// Default constructor.
			public SmoListBox()
				: base()
			{
			}

			// Gets the currently selected SMO object.
			public T Selected
			{
				get
				{
					if (null != this.ParentContext)
					{
						return GetSelectedFromParent(this.selectedValue);
					}
					else
					{
						return null;
					}
				}
			}

			public override void AddControls(SubscriptionConditionControl parent, Dictionary<string, EventHandler> handlers)
			{
				DropDownList dropDownList = new DropDownList();
				dropDownList.AutoPostBack = true;
				dropDownList.Width = 140;

				int selectedIndex = 0;
				dropDownList.Items.Add(String.Empty);
				int i = 0;
				foreach (string item in items)
				{
					++i;
					if (item == selectedValue) selectedIndex = i;
					dropDownList.Items.Add(item);
				}
				dropDownList.SelectedIndex = selectedIndex;
				dropDownList.SelectedIndexChanged += new EventHandler(dropDownList_SelectedIndexChanged);

				parent.Controls.Add(dropDownList);
			}

			protected override void UpdateParentContext()
			{
				this.items.Clear();
				if (null != this.ParentContext)
				{
					this.PopulateItems();
				}
			}

			void dropDownList_SelectedIndexChanged(object sender, EventArgs e)
			{
				DropDownList list = (DropDownList)sender;
				this.selectedValue = list.SelectedValue;

				if (0 != string.Compare(this.selectedValue, this.previousValue, StringComparison.Ordinal))
				{
					if (null != this.SelectedChanged)
					{
						this.SelectedChanged(this, new SmoEventArgs<T>(this.Selected));
					}
				}

				this.previousValue = this.selectedValue;
			}

			abstract protected T GetSelectedFromParent(string name);

			// Event raised when an SMO object is selected using this control.
			public event EventHandler<SmoEventArgs<T>> SelectedChanged;

			// Method called when the "ParentContext" has changed.
			protected abstract void PopulateItems();

			private string previousValue;
			protected List<string> items = new List<string>();
			protected string selectedValue = string.Empty;
		}
	}
}
