#region License

/*
 * Copyright 2002-2004 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#endregion

#region Imports

using System;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;
using System.Security.Permissions;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

#endregion

namespace Spring.Web.UI.Controls
{
#if !NET_2_0
	/// <summary>
	/// This controls provides MultiView introduced with ASP.NET 2.0 for NET 1.1 Platform
	/// </summary>
	/// <author>Erich Eichinger</author>
	[
		DefaultEvent("ActiveViewChanged")
			, ToolboxData("<{0}:MultiView runat=\"server\"></{0}:MultiView>")
			, ParseChildren(false, null)
			, AspNetHostingPermission(SecurityAction.LinkDemand, Level=AspNetHostingPermissionLevel.Minimal)
			, AspNetHostingPermission(SecurityAction.InheritanceDemand, Level=AspNetHostingPermissionLevel.Minimal)]
	public class MultiView : Control
	{
		#region Constants

		/// <summary>
		/// Represents the command name associated with the next View control to display in a MultiView control. This field is read-only.
		/// </summary>
		public static readonly string NextViewCommandName = "NextView";
		/// <summary>
		/// Represents the command name associated with the previous View control to display in a MultiView control. This field is read-only.
		/// </summary>
		public static readonly string PreviousViewCommandName = "PrevView";
		/// <summary>
		/// Represents the command name associated with changing the active View control in a MultiView control, based on a specified View id. This field is read-only.
		/// </summary>
		public static readonly string SwitchViewByIDCommandName = "SwitchViewByID";
		/// <summary>
		/// Represents the command name associated with changing the active View control in a MultiView control based on a specified View index. This field is read-only.
		/// </summary>
		public static readonly string SwitchViewByIndexCommandName = "SwitchViewByIndex";

		#endregion

		#region Fields

		private static readonly FieldInfo s_fiControlState =
			typeof(Control).GetField("_controlState", BindingFlags.Instance | BindingFlags.NonPublic);


		private int _activeViewIndex = -1;
		private int _cachedActiveViewIndex = -1;
		private bool _controlStateApplied;
		private static readonly object _eventActiveViewChanged = new object();
		private bool _ignoreBubbleEvents;

		#endregion

		#region Construction

		/// <summary>
		/// Initializes a new instance of the <see cref="MultiView"/> class.
		/// </summary>
		public MultiView()
		{
			// intentionally left empty
		}

		#endregion

		#region Events

		/// <summary>
		/// Occurs when the active View control of a MultiView control changes between posts to the server.
		/// </summary>
		public event EventHandler ActiveViewChanged
		{
			add { base.Events.AddHandler(_eventActiveViewChanged, value); }
			remove { base.Events.RemoveHandler(_eventActiveViewChanged, value); }
		}

		#endregion

		#region Properties

		/// <summary>
		/// Gets or sets the index of the active View control within a MultiView control.
		/// </summary>
		[DefaultValue(-1)]
		public virtual int ActiveViewIndex
		{
			get
			{
				if (_cachedActiveViewIndex > -1)
				{
					return _cachedActiveViewIndex;
				}
				return _activeViewIndex;
			}
			set
			{
				if (value < -1)
				{
					throw new ArgumentOutOfRangeException("value",
						string.Format(
						"MultiView_ActiveViewIndex_less_than_minus_one",
						new object[] {value}));
				}
				if ((Views.Count == 0) && (ControlState < ControlState.ChildrenInitialized))
				{
					_cachedActiveViewIndex = value;
				}
				else
				{
					if (value >= Views.Count)
					{
						throw new ArgumentOutOfRangeException("value",
							string.Format(
							"MultiView_ActiveViewIndex_equal_or_greater_than_count",
							new object[] {value, Views.Count}));
					}
					int num = (_cachedActiveViewIndex != -1) ? -1 : _activeViewIndex;
					_activeViewIndex = value;
					_cachedActiveViewIndex = -1;
					if (((num != value) && (num != -1)) && (num < Views.Count))
					{
						Views[num].Active = false;
						if (ShouldTriggerViewEvent)
						{
							Views[num].OnDeactivate(EventArgs.Empty);
						}
					}
					if (((num != value) && (Views.Count != 0)) && (value != -1))
					{
						Views[value].Active = true;
						if (ShouldTriggerViewEvent)
						{
							Views[value].OnActivate(EventArgs.Empty);
							OnActiveViewChanged(EventArgs.Empty);
						}
					}
				}
			}
		}


		/// <summary>
		/// Gets the collection of View controls in the MultiView control.
		/// </summary>
		[
		PersistenceMode(PersistenceMode.InnerDefaultProperty)
		, Browsable(false)
		]
		public virtual ViewCollection Views
		{
			get { return (ViewCollection) Controls; }
		}

		/// <summary>
		/// Return the current lifecycle state of this control.
		/// </summary>
		private ControlState ControlState
		{
			get { return (ControlState) s_fiControlState.GetValue(this); }
		}

		#endregion

		/// <summary>
		/// Sets the specified View control to the active view within a MultiView control.
		/// </summary>
		/// <param name="view">A View control to set as the active view within a MultiView control.</param>
		public void SetActiveView(View view)
		{
			int index = Views.IndexOf(view);
			if (index < 0)
			{
				throw new HttpException(
					string.Format("MultiView_view_not_found", new object[] {(view == null) ? "null" : view.ID, ID}));
			}
			ActiveViewIndex = index;
		}

		/// <summary>
		/// Returns the current active View control within a MultiView control. 
		/// </summary>
		/// <returns>A View control that represents the active view within a MultiView control.</returns>
		public View GetActiveView()
		{
			int activeViewIndex = ActiveViewIndex;
			if (activeViewIndex >= Views.Count)
			{
				throw new Exception("MultiView_ActiveViewIndex_out_of_range");
			}
			if (activeViewIndex < 0)
			{
				return null;
			}
			View view = Views[activeViewIndex];
			if (!view.Active)
			{
				UpdateActiveView(activeViewIndex);
			}
			return view;
		}

		/// <summary>
		/// Mark this control to ignore <see cref="OnBubbleEvent"/>
		/// </summary>
		internal void IgnoreBubbleEvents()
		{
			_ignoreBubbleEvents = true;
		}

		/// <summary>
		/// Adds the element to the collection of child controls.
		/// </summary>
		protected override void AddParsedSubObject(object obj)
		{
			if (obj is View)
			{
				Controls.Add((Control) obj);
			}
			else if (!(obj is LiteralControl))
			{
				throw new HttpException(
					string.Format("MultiView_cannot_have_children_of_type", new object[] {obj.GetType().Name}));
			}
		}

		/// <summary>
		/// Creates a new <see cref="ViewCollection"/> instance.
		/// </summary>
		/// <returns></returns>
		protected override ControlCollection CreateControlCollection()
		{
			return new ViewCollection(this);
		}

		/// <summary>
		/// Restores view-state information from a previous page request.
		/// </summary>
		protected override void LoadViewState(object savedState)
		{
			base.LoadViewState(savedState);
			ActiveViewIndex = (int) ViewState["_activeViewIndex"];
			_controlStateApplied = true;
		}

		/// <summary>
		/// Saves view-state information to the page response.
		/// </summary>
		protected override object SaveViewState()
		{
			ViewState["_activeViewIndex"] = ActiveViewIndex;
			return base.SaveViewState();
		}

		/// <summary>
		/// Raises the <see cref="ActiveViewChanged"/> event.
		/// </summary>
		protected virtual void OnActiveViewChanged(EventArgs e)
		{
			EventHandler handler = (EventHandler) base.Events[_eventActiveViewChanged];
			if (handler != null)
			{
				handler(this, e);
			}
		}

		/// <summary>
		/// Captures know commands.
		/// </summary>
		protected override bool OnBubbleEvent(object source, EventArgs e)
		{
			if (!_ignoreBubbleEvents && (e is CommandEventArgs))
			{
				CommandEventArgs args = (CommandEventArgs) e;
				string commandName = args.CommandName;
				if (commandName == NextViewCommandName)
				{
					if (ActiveViewIndex < (Views.Count - 1))
					{
						ActiveViewIndex++;
					}
					else
					{
						ActiveViewIndex = -1;
					}
					return true;
				}
				if (commandName == PreviousViewCommandName)
				{
					if (ActiveViewIndex > -1)
					{
						ActiveViewIndex--;
					}
					return true;
				}
				if (commandName == SwitchViewByIDCommandName)
				{
					View view = FindControl((string) args.CommandArgument) as View;
					if ((view == null) || (view.Parent != this))
					{
						throw new HttpException(
							string.Format("MultiView_invalid_view_id",
							              new object[]
							              	{ID, (string) args.CommandArgument, SwitchViewByIDCommandName}));
					}
					SetActiveView(view);
					return true;
				}
				if (commandName == SwitchViewByIndexCommandName)
				{
					int num;
					try
					{
						num = int.Parse((string) args.CommandArgument, CultureInfo.InvariantCulture);
					}
					catch (FormatException)
					{
						throw new FormatException(
							string.Format("MultiView_invalid_view_index_format",
							              new object[] {(string) args.CommandArgument, SwitchViewByIndexCommandName}));
					}
					ActiveViewIndex = num;
					return true;
				}
			}
			return false;
		}

		/// <summary>
		/// Initialize this control.
		/// </summary>
		/// <param name="e"></param>
		protected override void OnInit(EventArgs e)
		{
			base.OnInit(e);
			if (_cachedActiveViewIndex > -1)
			{
				ActiveViewIndex = _cachedActiveViewIndex;
				_cachedActiveViewIndex = -1;
				GetActiveView();
			}
		}

		/// <summary>
		/// Handle removal of a view.
		/// </summary>
		/// <param name="ctl"></param>
		protected override void RemovedControl(Control ctl)
		{
			if (((View) ctl).Active && (ActiveViewIndex < Views.Count))
			{
				GetActiveView();
			}
			base.RemovedControl(ctl);
		}

		/// <summary>
		/// Render the currently active view.
		/// </summary>
		/// <param name="writer"></param>
		protected override void Render(HtmlTextWriter writer)
		{
			View activeView = GetActiveView();
			if (activeView != null)
			{
				activeView.RenderControl(writer);
			}
		}

		/// <summary>
		/// Switches from the currently active view to the specified view.
		/// </summary>
		/// <param name="activeViewIndex"></param>
		private void UpdateActiveView(int activeViewIndex)
		{
			for (int i = 0; i < Views.Count; i++)
			{
				View view = Views[i];
				if (i == activeViewIndex)
				{
					view.Active = true;
					if (ShouldTriggerViewEvent)
					{
						view.OnActivate(EventArgs.Empty);
					}
				}
				else if (view.Active)
				{
					view.Active = false;
					if (ShouldTriggerViewEvent)
					{
						view.OnDeactivate(EventArgs.Empty);
					}
				}
			}
		}

		/// <summary>
		/// Indicates if view events are ready to be raised.
		/// </summary>
		private bool ShouldTriggerViewEvent
		{
			get
			{
				if (_controlStateApplied)
				{
					return true;
				}
				if (this.Page != null)
				{
					return !this.Page.IsPostBack;
				}
				return false;
			}
		}
	}
#endif // !NET_2_0
}