// $Author: asskoala $
// $Date: 2005/12/11 21:07:21 $
// $Revision: 1.1 $

using System;
using System.Collections;
using System.Drawing;

using Icarus.Graphics.Animation;

namespace Icarus.LUCIE
{
	/// <summary>
	/// Delegate for receiving information about clicks on LUCIE components.
	/// </summary>
	public delegate void ClickCallback(AbstractComponent sender);

	/// <summary>
	/// Delegate for receiving information about child additions and removals in LUCIE.
	/// </summary>
	public delegate void ChildCallback(AbstractComponent sender, AbstractComponent child);

	/// <summary>
	/// Delegate for receiving information about resizing a component.
	/// </summary>
	public delegate void ResizedCallback(AbstractComponent sender, Size oldSize);

	/// <summary>
	/// The base component class, from which all of the more specific controls, such as buttons and labels, will be derived.
	/// </summary>
	public abstract class AbstractComponent : AbstractRenderable
	{
		#region Private Members
		/// <summary>
		/// The bounds of the component relative to the parent component.
		/// </summary>
		private Rectangle mBounds;

		/// <summary>
		/// Whether or not this component is visible.
		/// </summary>
		private bool mIsVisible;

		/// <summary>
		/// The list of child components.
		/// </summary>
		private IList mChildren;
		#endregion

		#region Events
		/// <summary>
		/// Triggered whenever the component is clicked.
		/// </summary>
		public event ClickCallback Click;

		/// <summary>
		/// Triggers the click event.
		/// </summary>
		public void OnClick()
		{
			//Notify self:
			Clicked();

			//Notify others:
			if (Click != null) 
			{
				Click(this);
			}
		}

		/// <summary>
		/// Triggered whenever a child is added to or removed from this component.
		/// </summary>
		public event ChildCallback ChildrenChanged;

		/// <summary>
		/// Triggers the ChildrenChanged event.
		/// </summary>
		/// <param name="child"></param>
		public void OnChildrenChanged(AbstractComponent child)
		{
			if (ChildrenChanged != null)
			{
				ChildrenChanged(this, child);
			}
		}

		/// <summary>
		/// Triggered whenever the component's Size property is changed.
		/// </summary>
		public event ResizedCallback Resized;

		/// <summary>
		/// Triggers the Resized event.
		/// </summary>
		/// <param name="oldSize"></param>
		public void OnResized(Size oldSize)
		{
			if (Resized != null)
			{
				Resized(this, oldSize);
			}
		}
		#endregion

		#region Initialization and Cleanup
		/// <summary>
		/// Default constructor that creates a new component of zero size, at the origin, which is invisible.
		/// </summary>
		public AbstractComponent()
		{
			mBounds = new Rectangle(0, 0, 0, 0);
			mIsVisible = false;
			mChildren = new ArrayList();
		}
		#endregion

		#region Properties
		/// <summary>
		/// Whether or not this component is visible.
		/// </summary>
		public bool IsVisible
		{
			get { return mIsVisible; }
			set { mIsVisible = value; }
		}

		/// <summary>
		/// The location of the component.
		/// </summary>
		public Point Location
		{
			get { return mBounds.Location; }
			set { mBounds.Location = value; }
		}

		/// <summary>
		/// The size of the component. This will be set automagically. So get your grubby paws away from the setter.
		/// </summary>
		public Size Size
		{
			get { return mBounds.Size; }
			
			//TODO: Now, you know, .NET 2.0 supports this. We should swtich. We TOTALLY should.
			//protected//
			set 
			{
				Size oldSize = mBounds.Size;
				mBounds.Size = value; 
				this.OnResized(oldSize);
			}
		}

		/// <summary>
		/// Gets the bounds of the control.
		/// </summary>
		public Rectangle Bounds
		{
			get { return mBounds; }
		}

		/// <summary>
		/// The children of this component.
		/// </summary>
		public IList Children
		{
			get { return mChildren; }
		}
		#endregion

		#region Parents and Children
		/// <summary>
		/// Adds a child to this component.
		/// </summary>
		/// <param name="child"></param>
		public void AddChild(AbstractComponent child) 
		{
			if (!mChildren.Contains(child))
			{
				mChildren.Add(child);
				this.OnChildrenChanged(child);
			}
		}

		/// <summary>
		/// Removes the child from this component, if such child exists.
		/// </summary>
		/// <param name="child"></param>
		public void RemoveChild(AbstractComponent child)
		{
			if (mChildren.Contains(child))
			{
				mChildren.Remove(child);
				this.OnChildrenChanged(child);
			}
		}

		/// <summary>
		/// Returns the component at the specified coordinates, or null if no such component exists.
		/// </summary>
		/// <param name="position"></param>
		/// <returns></returns>
		public AbstractComponent FindChildAt(Point position)
		{
			AbstractComponent theChild = null;

			if (!this.mBounds.Contains(position))
			{
				//The position is out of my bounds, so no component there.
				return null;
			}

			position.Offset(-this.Location.X, -this.Location.Y);

			//We want to check the top components first, so we must iterate the list in reverse:
			for (int i = this.Children.Count - 1; i >= 0; i--)
			{
				AbstractComponent c = (AbstractComponent) this.Children[i];
				theChild = c.FindChildAt(position);
				if (theChild != null) break;
			}

			position.Offset(this.Location.X, this.Location.Y);

			if (theChild == null)
			{
				if (this.IsVisible)
					return this;
				else
					return null;
			}
			else
			{
				return theChild;
			}
		}
		#endregion

		#region IRenderable Members
		public override Size GetSize()
		{
			return this.Size;
		}

		public override void Render(Rectangle coordinates, IRenderParameters parameters)
		{
			if (this.IsVisible) 
			{
				coordinates.Offset(this.Location.X, this.Location.Y);

				RenderSelf(coordinates, parameters);

				//Render children:
				foreach (AbstractComponent c in mChildren)
				{
					c.Render(coordinates, parameters);
				}
				
				coordinates.Offset(-this.Location.X, -this.Location.Y);
			}
		}

		protected abstract void RenderSelf(Rectangle coordinates, IRenderParameters parameters);
		#endregion

		#region Virtual Functions
		/// <summary>
		/// Gets called when the control is clicked.
		/// </summary>
		public virtual void Clicked() { }
		#endregion
	}
}
