using System;
using System.Collections;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;

namespace Ajax
{
	public class Grid : DataGrid
	{
		HtmlForm FindForm()
		{
			Control control = this.Parent;
			while (control != null && !(control is HtmlForm))
			{
				control = control.Parent;
			}
			return control as HtmlForm;
		}

		protected override void OnInit(EventArgs e)
		{
			base.OnInit(e);

			Ajax.Manager.Register(this, Ajax.Debug.None);

			HtmlForm form = FindForm();
			if (form == null)
			{
				throw new ApplicationException("Ajax.Grid controls must be contained by a server-side form control!");
			}

			this.Page.RegisterClientScriptBlock(
				this.GetType().FullName, 
				string.Format(@"
<script language=""javascript"" type=""text/javascript"">
function AjaxGridCallBack(id, eventTarget) {{
	var theform;
	if (window.navigator.appName.toLowerCase().indexOf(""microsoft"") > -1) {{
		theform = document.{0};
	}} else {{
		theform = document.forms[""{0}""];
	}}
	var eventTargetControl = null;
	if (theform.__EVENTTARGET) {{
		eventTargetControl = theform.__EVENTTARGET;
		eventTargetControl.value = eventTarget;
	}} else {{
		var input = document.createElement(""input"");
		input.setAttribute(""name"", ""__EVENTTARGET"");
		input.setAttribute(""type"", ""hidden"");
		input.setAttribute(""value"", eventTarget);
		theform.appendChild(input);
		eventTargetControl = input;
	}}
	var result = Ajax.Grid.RenderGrid(id);
	eventTargetControl.value = """";
	if (result.value) {{
		theform.__VIEWSTATE.value = result.value[0];
		document.getElementById(id).parentNode.innerHTML = result.value[1];
	}} else {{
		alert(result.error);
	}}
}}
</script>", form.ClientID));
		}

		protected override void CreateChildControls()
		{
			base.CreateChildControls();
			ModifyTable();
		}

		protected override void OnLoad(EventArgs e)
		{
			base.OnLoad(e);
		}

		bool _renderDelimiters = false;

		const string _beginDelimiter = "<!--begin Ajax.Grid-->";
		const string _endDelimiter = "<!--end Ajax.Grid-->";

		const string _beginViewState = "<input type=\"hidden\" name=\"__VIEWSTATE\" value=\"";

		[Ajax.Method]
		public string[] RenderGrid()
		{
			StringWriter sw = new StringWriter();
			HtmlTextWriter htw = new HtmlTextWriter(sw);
			this._renderDelimiters = true;

			// I hate having to do this but I can't figure out any
			// other way to get the Page to render its ViewState.
			MethodInfo mi = typeof(Page).GetMethod("SavePageViewState", BindingFlags.Instance | BindingFlags.NonPublic);

			// If SavePageViewState wasn't found, we might be running under 2.0.
			// I thought SaveAllState would be its replacement but invoking this
			// method doesn't work.
			if (mi == null)
			{
				mi = typeof(Page).GetMethod("SaveAllState", BindingFlags.Instance | BindingFlags.NonPublic);
			}

			// If these methods weren't found. All hope is lost.
			if (mi != null)
			{
				mi.Invoke(this.Page, null);
			}

			// We have to render the whole page and not just the grid to
			// get the ViewState.
			this.Page.RenderControl(htw);
			htw.Close();

			this._renderDelimiters = false;

			string html = sw.GetStringBuilder().ToString();

			// Find the ViewState so we can return it.
			string viewState = null;
			int i = html.IndexOf(_beginViewState);
			if (i != -1)
			{
				viewState = html.Substring(i + _beginViewState.Length);
				viewState = viewState.Substring(0, viewState.IndexOf("\""));
			}

			// Now get the new rendering of the grid.
			string grid = html.Substring(html.IndexOf(_beginDelimiter) + _beginDelimiter.Length);
			grid = grid.Substring(0, grid.IndexOf(_endDelimiter));

			return new string[] { viewState, grid };
		}

		protected override void Render(HtmlTextWriter writer)
		{
			// This div is going to be used to contain the grid table.
			// Client-side JavaScript will be setting the innerHTML
			// property on this div when it needs to re-render the grid.
			writer.Write("<div>");

			// These delimiters are used to extract the grid from the
			// page source during a call back. They need to be located
			// inside the div so that we don't keep adding div after div
			// to the client DOM.
			if (this._renderDelimiters)
			{
				writer.Write(_beginDelimiter);
			}

			// Now we can render the actual grid.
			base.Render(writer);

			// This marks the end of the grid.
			if (this._renderDelimiters)
			{
				writer.Write(_endDelimiter);
			}

			// Close the div.
			writer.Write("</div>");
		}

		protected override void OnItemDataBound(DataGridItemEventArgs e)
		{
			// This method gets invoked as each row in the table is added.
			// We need to add id attributes to the row and add onclick
			// attributes to the links so they invoke client-side JavaScript
			// instead of posting back to the page.
			base.OnItemDataBound(e);
			ModifyRow(e.Item);
		}

		private void ModifyTable()
		{
			foreach (Control control in this.Controls)
			{
				if (control is Table)
				{
					Table table = control as Table;
					foreach (DataGridItem row in table.Rows)
					{
						ModifyRow(row);
					}
				}
			}
		}

		private void ModifyRow(DataGridItem item)
		{
			if (this.AllowSorting && item.ItemType == ListItemType.Header)
			{
				int columnIndex = 0;
				foreach (DataGridColumn column in this.Columns)
				{
					if (column.SortExpression != null && column.SortExpression.Length > 0)
					{
						ModifyCell(item, columnIndex, column.SortExpression);
					}
					++columnIndex;
				}
			}
			else if (item.ItemType == ListItemType.Item ||
				item.ItemType == ListItemType.AlternatingItem ||
				item.ItemType == ListItemType.EditItem)
			{
				string itemID = this.UniqueID + "_item" + item.ItemIndex;
				item.Attributes.Add("id", itemID);
				string dataKey = "";
				if (DataKeyField != null && DataKeyField != "")
				{
					dataKey = this.DataKeys[item.ItemIndex].ToString();
				}
				// Now we have to find each EditCommandColumn and modify its
				// corresponding table cell.
				int columnIndex = 0;
				foreach (DataGridColumn column in this.Columns)
				{
					if (column is EditCommandColumn)
					{
						ModifyCell(item, columnIndex, item.ItemIndex.ToString());
					}
					else if (column is ButtonColumn)
					{
						ModifyCell(item, columnIndex, item.ItemIndex.ToString());
					}
					++columnIndex;
				}
			}
		}

		private void ModifyCell(DataGridItem item, int columnIndex, string suffix)
		{
			TableCell cell = item.Controls[columnIndex] as TableCell;
			if (cell != null)
			{
				foreach (Control buttonControl in cell.Controls)
				{
					// Make sure the specified cell contains a DataGridLinkButton
					// or a Button. DataGridLinkButton is an internal class so we
					// have to check for its base class, LinkButton, instead.
					if (buttonControl is LinkButton || buttonControl is Button)
					{
						// The "button" that invokes the command could be a LinkButton
						// or a normal Button. These classes don't have a common base
						// class so we have to check for each one.
						LinkButton linkButton = buttonControl as LinkButton;
						if (linkButton != null)
						{
							linkButton.ID = linkButton.CommandName + "_" + suffix;
							linkButton.Attributes.Add(
								"onclick",
								string.Format("AjaxGridCallBack(\"{0}\", \"{1}\"); return false;", ClientID, linkButton.UniqueID));
						}
						else
						{
							Button pushButton = buttonControl as Button;
							if (pushButton != null)
							{
								pushButton.ID = pushButton.CommandName + "_" + suffix;
								pushButton.Attributes.Add(
									"onclick",
									string.Format("AjaxGridCallBack(\"{0}\", \"{1}\"); return false;", ClientID, pushButton.UniqueID));
							}
						}
					}
				}
			}
		}
	}
}
