// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// Copyright (c) 2007, 2008 Novell, Inc.
//
// Authors:
//	Andreia Gaita	<avidigal@novell.com>


using System;
using System.Drawing;
using System.ComponentModel;
using Mono.WebBrowser.DOM;

namespace System.Windows.Forms
{
	public sealed class HtmlElement
	{
		private EventHandlerList events;
		private Mono.WebBrowser.IWebBrowser webHost;
		internal IElement element;
		private WebBrowser owner;
		
		internal HtmlElement (WebBrowser owner, Mono.WebBrowser.IWebBrowser webHost, IElement element)
		{
			this.webHost = webHost;
			this.element = element;
			this.owner = owner;
		}

		internal EventHandlerList Events {
			get {
				if (events == null)
					events = new EventHandlerList();

				return events;
			}
		}

		#region Properties
		public HtmlElementCollection All {
			get {
				return new HtmlElementCollection (owner, webHost, this.element.All);
			}
		}

		// from http://www.w3.org/TR/html401/index/elements.html
		public bool CanHaveChildren {
			get {
				string tag = this.TagName;
				switch (tag.ToLowerInvariant ()) {
					case "area":
					case "base":
					case "basefont":
					case "br":
					case "col":
					case "frame":
					case "hr":
					case "img":
					case "input":
					case "isindex":
					case "link":
					case "meta":
					case "param":
						return false;
					default:
						return true;
				}
			}
		}

		public HtmlElementCollection Children {
			get {
				return new HtmlElementCollection (owner, webHost, this.element.Children);
			}
		}

		public Rectangle ClientRectangle {
			get { return new Rectangle (0, 0, this.element.ClientWidth, this.element.ClientHeight); }
		}
		
		public Rectangle OffsetRectangle {
			get { return new Rectangle (this.element.OffsetLeft, this.element.OffsetTop, this.element.OffsetWidth, this.element.OffsetHeight); }
		}

		public Rectangle ScrollRectangle {
			get { return new Rectangle (this.element.ScrollLeft, this.element.ScrollTop, this.element.ScrollWidth, this.element.ScrollHeight); }
		}

		public int ScrollLeft {
			get { return this.element.ScrollLeft; }
			set { this.element.ScrollLeft = value; }
		}

		public int ScrollTop {
			get { return this.element.ScrollTop; }
			set { this.element.ScrollTop = value; }
		}

		public HtmlElement OffsetParent {
			get { return new HtmlElement (owner, this.webHost, this.element.OffsetParent); }
		}

		public HtmlDocument Document {
			get {
				return new HtmlDocument (owner, webHost, element.Owner);
			}
		}

		public bool Enabled	{
			get { return !this.element.Disabled; }
			set { this.element.Disabled = !value; }
		}

		public string InnerHtml	{
			get { return this.element.InnerHTML; }
			set { this.element.InnerHTML = value; }
		}

		public string InnerText {
			get { return this.element.InnerText; }
			set { this.element.InnerText = value; }
		}

		public string Id {
			get { return GetAttribute("id"); }
			set { SetAttribute ("id", value); }
		}

		public string Name {
			get { return GetAttribute ("name"); }
			set { SetAttribute ("name", value); }
		}

		public HtmlElement FirstChild {
			get {
				return new HtmlElement (owner, webHost, (IElement)element.FirstChild); 
			}
		}

		public HtmlElement NextSibling {
			get { return new HtmlElement (owner, webHost, (IElement)element.Next); }
		}
		
		public HtmlElement Parent {
			get { return new HtmlElement (owner, webHost, (IElement)element.Parent); }
		}

		public string TagName {
			get { return element.TagName; }
		}

		public short TabIndex {
			get { return (short)element.TabIndex; }
			set { element.TabIndex = value; }
		}

		public object DomElement {
			get { throw new NotSupportedException ("Retrieving a reference to an mshtml interface is not supported. Sorry."); } 
		}

		public string OuterHtml {
			get { return this.element.OuterHTML; }
			set { this.element.OuterHTML = value; }
		}

		public string OuterText {
			get { return this.element.OuterText; }
			set { this.element.OuterText = value; }
		}

		public string Style {
			get { return this.element.Style; }
			set { this.element.Style = value; }
		}

		#endregion
		
		#region Methods
		public HtmlElement AppendChild (HtmlElement newElement)
		{
			IElement newChild = this.element.AppendChild (newElement.element);
			newElement.element = newChild;
			return newElement;
		}
		
		public void AttachEventHandler (string eventName, EventHandler eventHandler)
		{
			element.AttachEventHandler (eventName, eventHandler);
		}

		public void DetachEventHandler (string eventName, EventHandler eventHandler)
		{
			element.DetachEventHandler (eventName, eventHandler);
		}
		
		public void Focus ()
		{
			throw new NotImplementedException ();
		}

		public string GetAttribute (string attributeName)
		{
			return element.GetAttribute (attributeName);
		}

		public HtmlElementCollection GetElementsByTagName (string tagName)
		{
			Mono.WebBrowser.DOM.IElementCollection col = element.GetElementsByTagName (tagName);
			return new HtmlElementCollection (owner, webHost, col);
		}
		
		public override int GetHashCode ()
		{
			if (element == null)
				return 0;
			return element.GetHashCode ();
		}

		internal bool HasAttribute (string name)
		{
			return element.HasAttribute (name);
		}

		public HtmlElement InsertAdjacentElement (HtmlElementInsertionOrientation orient, HtmlElement newElement)
		{
			switch (orient) {
				case HtmlElementInsertionOrientation.BeforeBegin:
					element.Parent.InsertBefore (newElement.element, element);
					return newElement;
				case HtmlElementInsertionOrientation.AfterBegin:
					element.InsertBefore (newElement.element, element.FirstChild);
					return newElement;
				case HtmlElementInsertionOrientation.BeforeEnd:
					return this.AppendChild (newElement);
				case HtmlElementInsertionOrientation.AfterEnd:
					return this.AppendChild (newElement);
			}
			return null;
		}
		
		public object InvokeMember (string methodName)
		{
			return this.element.Owner.InvokeScript ("eval ('" + methodName + "()');");
		}

		public object InvokeMember (string methodName, params object[] parameter)
		{
			string[] strArgs = new string[parameter.Length];
			for (int i = 0; i < parameter.Length; i++) {
				strArgs[i] = parameter.ToString();
			}
			return this.element.Owner.InvokeScript ("eval ('" + methodName + "(" + String.Join (",", strArgs) + ")');");
		}
		
		public void RaiseEvent (string eventName) 
		{
			this.element.FireEvent (eventName);
		}

		public void RemoveFocus () 
		{
			this.element.Blur ();
		}

		public void ScrollIntoView (bool alignWithTop) 
		{
			this.element.ScrollIntoView (alignWithTop);
		}
		
		public void SetAttribute (string attributeName, string value)
		{
			this.element.SetAttribute (attributeName, value);
		}

		public override bool Equals (object obj)
		{
			return this == (HtmlElement) obj;
		}
		
		public static bool operator == (HtmlElement left, HtmlElement right)
		{
			if ((object)left == (object)right)
				return true;
			if ((object)left == null || (object)right == null)
				return false;
			return left.element.Equals (right.element); 
		}

		public static bool operator != (HtmlElement left, HtmlElement right)
		{
			return !(left == right);
		}
		#endregion

		#region Events
		private void OnClick (object sender, EventArgs e)
		{
			HtmlElementEventHandler eh = (HtmlElementEventHandler) Events[ClickEvent];
			if (eh != null) {
				HtmlElementEventArgs ev = new HtmlElementEventArgs ();
				eh (this, ev);
			}
		}

		private static object ClickEvent = new object ();
		public event HtmlElementEventHandler Click {
			add { 
				Events.AddHandler (ClickEvent, value);
				element.Click += new NodeEventHandler (OnClick);
			}
			remove { 
				Events.RemoveHandler (ClickEvent, value);
				element.Click -= new NodeEventHandler (OnClick);
			}
		}

		private void OnDoubleClick (object sender, EventArgs e)
		{
			HtmlElementEventHandler eh = (HtmlElementEventHandler) Events[DoubleClickEvent];
			if (eh != null) {
				HtmlElementEventArgs ev = new HtmlElementEventArgs ();
				eh (this, ev);
			}
		}

		private static object DoubleClickEvent = new object ();
		public event HtmlElementEventHandler DoubleClick {
			add {
				Events.AddHandler (DoubleClickEvent, value);
				element.DoubleClick += new NodeEventHandler (OnDoubleClick);
			}
			remove {
				Events.RemoveHandler (DoubleClickEvent, value);
				element.DoubleClick -= new NodeEventHandler (OnDoubleClick);
			}
		}

		private void OnMouseDown (object sender, EventArgs e)
		{
			HtmlElementEventHandler eh = (HtmlElementEventHandler) Events[MouseDownEvent];
			if (eh != null) {
				HtmlElementEventArgs ev = new HtmlElementEventArgs ();
				eh (this, ev);
			}
		}

		private static object MouseDownEvent = new object ();
		public event HtmlElementEventHandler MouseDown {
			add {
				Events.AddHandler (MouseDownEvent, value);
				element.MouseDown += new NodeEventHandler (OnMouseDown);
			}
			remove {
				Events.RemoveHandler (MouseDownEvent, value);
				element.MouseDown -= new NodeEventHandler (OnMouseDown);
			}
		}

		private void OnMouseUp (object sender, EventArgs e)
		{
			HtmlElementEventHandler eh = (HtmlElementEventHandler) Events[MouseUpEvent];
			if (eh != null) {
				HtmlElementEventArgs ev = new HtmlElementEventArgs ();
				eh (this, ev);
			}
		}

		private static object MouseUpEvent = new object ();
		public event HtmlElementEventHandler MouseUp {
			add {
				Events.AddHandler (MouseUpEvent, value);
				element.MouseUp += new NodeEventHandler (OnMouseUp);
			}
			remove {
				Events.RemoveHandler (MouseUpEvent, value);
				element.MouseUp -= new NodeEventHandler (OnMouseUp);
			}
		}

		private void OnMouseMove (object sender, EventArgs e)
		{
			HtmlElementEventHandler eh = (HtmlElementEventHandler) Events[MouseMoveEvent];
			if (eh != null) {
				HtmlElementEventArgs ev = new HtmlElementEventArgs ();
				eh (this, ev);
			}
		}

		private static object MouseMoveEvent = new object ();
		public event HtmlElementEventHandler MouseMove {
			add {
				Events.AddHandler (MouseMoveEvent, value);
				element.MouseMove += new NodeEventHandler (OnMouseMove);
			}
			remove {
				Events.RemoveHandler (MouseMoveEvent, value);
				element.MouseMove -= new NodeEventHandler (OnMouseMove);
			}
		}

		private void OnMouseOver (object sender, EventArgs e)
		{
			HtmlElementEventHandler eh = (HtmlElementEventHandler) Events[MouseOverEvent];
			if (eh != null) {
				HtmlElementEventArgs ev = new HtmlElementEventArgs ();
				eh (this, ev);
			}
		}

		private static object MouseOverEvent = new object ();
		public event HtmlElementEventHandler MouseOver {
			add { 
				Events.AddHandler (MouseOverEvent, value);
				element.MouseOver += new NodeEventHandler (OnMouseOver);
			}
			remove { 
				Events.RemoveHandler (MouseOverEvent, value);
				element.MouseOver -= new NodeEventHandler (OnMouseOver);
			}
		}
		
		private void OnMouseEnter (object sender, EventArgs e)
		{
			HtmlElementEventHandler eh = (HtmlElementEventHandler) Events[MouseEnterEvent];
			if (eh != null) {
				HtmlElementEventArgs ev = new HtmlElementEventArgs ();
				eh (this, ev);
			}
		}
		
		private static object MouseEnterEvent = new object ();
		public event HtmlElementEventHandler MouseEnter {
			add {
				Events.AddHandler (MouseEnterEvent, value);
				element.MouseEnter += new NodeEventHandler (OnMouseEnter);
			}
			remove {
				Events.RemoveHandler (MouseEnterEvent, value);
				element.MouseEnter -= new NodeEventHandler (OnMouseEnter);
			}
		}
		
		private void OnMouseLeave (object sender, EventArgs e)
		{
			HtmlElementEventHandler eh = (HtmlElementEventHandler) Events[MouseLeaveEvent];
			if (eh != null) {
				HtmlElementEventArgs ev = new HtmlElementEventArgs ();
				eh (this, ev);
			}
		}
		
		private static object MouseLeaveEvent = new object ();
		public event HtmlElementEventHandler MouseLeave {
			add {
				Events.AddHandler (MouseLeaveEvent, value);
				element.MouseLeave += new NodeEventHandler (OnMouseLeave);
			}
			remove {
				Events.RemoveHandler (MouseLeaveEvent, value);
				element.MouseLeave -= new NodeEventHandler (OnMouseLeave);
			}
		}

		private void OnKeyDown (object sender, EventArgs e)
		{
			HtmlElementEventHandler eh = (HtmlElementEventHandler) Events[KeyDownEvent];
			if (eh != null) {
				HtmlElementEventArgs ev = new HtmlElementEventArgs ();
				eh (this, ev);
			}
		}
		
		private static object KeyDownEvent = new object ();
		public event HtmlElementEventHandler KeyDown {
			add {
				Events.AddHandler (KeyDownEvent, value);
				element.KeyDown += new NodeEventHandler (OnKeyDown);
			}
			remove {
				Events.RemoveHandler (KeyDownEvent, value);
				element.KeyDown -= new NodeEventHandler (OnKeyDown);
			}
		}

		private void OnKeyPress (object sender, EventArgs e)
		{
			HtmlElementEventHandler eh = (HtmlElementEventHandler) Events[KeyPressEvent];
			if (eh != null) {
				HtmlElementEventArgs ev = new HtmlElementEventArgs ();
				eh (this, ev);
			}
		}
		
		private static object KeyPressEvent = new object ();
		public event HtmlElementEventHandler KeyPress {
			add {
				Events.AddHandler (KeyPressEvent, value);
				element.KeyPress += new NodeEventHandler (OnKeyPress);
			}
			remove {
				Events.RemoveHandler (KeyPressEvent, value);
				element.KeyPress -= new NodeEventHandler (OnKeyPress);
			}
		}

		private void OnKeyUp (object sender, EventArgs e)
		{
			HtmlElementEventHandler eh = (HtmlElementEventHandler) Events[KeyUpEvent];
			if (eh != null) {
				HtmlElementEventArgs ev = new HtmlElementEventArgs ();
				eh (this, ev);
			}
		}
		
		private static object KeyUpEvent = new object ();
		public event HtmlElementEventHandler KeyUp {
			add {
				Events.AddHandler (KeyUpEvent, value);
				element.KeyUp += new NodeEventHandler (OnKeyUp);
			}
			remove {
				Events.RemoveHandler (KeyUpEvent, value);
				element.KeyUp -= new NodeEventHandler (OnKeyUp);
			}
		}

		private static object DragEvent = new object ();
		public event HtmlElementEventHandler Drag {
			add {
				Events.AddHandler (DragEvent, value);
			}
			remove {
				Events.RemoveHandler (DragEvent, value);
			}
		}

		private void OnDrag (object sender, EventArgs e)
		{
			HtmlElementEventHandler eh = (HtmlElementEventHandler) Events[DragEvent];
			if (eh != null) {
				HtmlElementEventArgs ev = new HtmlElementEventArgs ();
				eh (this, ev);
			}
		}


		private static object DragEndEvent = new object ();
		public event HtmlElementEventHandler DragEnd {
			add	{
				Events.AddHandler (DragEndEvent, value);
			}
			remove {
				Events.RemoveHandler (DragEndEvent, value);
			}
		}

		private void OnDragEnd (object sender, EventArgs e)
		{
			HtmlElementEventHandler eh = (HtmlElementEventHandler) Events[DragEndEvent];
			if (eh != null) {
				HtmlElementEventArgs ev = new HtmlElementEventArgs ();
				eh (this, ev);
			}
		}
		private static object DragLeaveEvent = new object ();
		public event HtmlElementEventHandler DragLeave {
			add {
				Events.AddHandler (DragLeaveEvent, value);
			}
			remove {
				Events.RemoveHandler (DragLeaveEvent, value);
			}
		}

		private void OnDragLeave (object sender, EventArgs e)
		{
			HtmlElementEventHandler eh = (HtmlElementEventHandler) Events[DragLeaveEvent];
			if (eh != null) {
				HtmlElementEventArgs ev = new HtmlElementEventArgs ();
				eh (this, ev);
			}
		}
		private static object DragOverEvent = new object ();
		public event HtmlElementEventHandler DragOver {
			add {
				Events.AddHandler (DragOverEvent, value);
			}
			remove {
				Events.RemoveHandler (DragOverEvent, value);
			}
		}

		private void OnDragOver (object sender, EventArgs e)
		{
			HtmlElementEventHandler eh = (HtmlElementEventHandler) Events[DragOverEvent];
			if (eh != null) {
				HtmlElementEventArgs ev = new HtmlElementEventArgs ();
				eh (this, ev);
			}
		}
		private static object FocusingEvent = new object ();
		public event HtmlElementEventHandler Focusing {
			add {
				Events.AddHandler (FocusingEvent, value);
				((INode)element).OnFocus += new NodeEventHandler (OnFocusing);
			}
			remove {
				Events.RemoveHandler (FocusingEvent, value);
				((INode)element).OnFocus -= new NodeEventHandler (OnFocusing);
			}
		}

		private void OnFocusing (object sender, EventArgs e)
		{
			HtmlElementEventHandler eh = (HtmlElementEventHandler) Events[FocusingEvent];
			if (eh != null) {
				HtmlElementEventArgs ev = new HtmlElementEventArgs ();
				eh (this, ev);
			}
		}

		private static object GotFocusEvent = new object ();
		public event HtmlElementEventHandler GotFocus {
			add {
				Events.AddHandler (GotFocusEvent, value);
				((INode)element).OnFocus += new NodeEventHandler (OnGotFocus);
			}
			remove {
				Events.RemoveHandler (GotFocusEvent, value);
				((INode)element).OnFocus -= new NodeEventHandler (OnGotFocus);
			}
		}

		private void OnGotFocus (object sender, EventArgs e)
		{
			HtmlElementEventHandler eh = (HtmlElementEventHandler) Events[GotFocusEvent];
			if (eh != null) {
				HtmlElementEventArgs ev = new HtmlElementEventArgs ();
				eh (this, ev);
			}
		}
		private static object LosingFocusEvent = new object ();
		public event HtmlElementEventHandler LosingFocus {
			add {
				Events.AddHandler (LosingFocusEvent, value);
				((INode)element).OnBlur += new NodeEventHandler (OnLosingFocus);
			}
			remove {
				Events.RemoveHandler (LosingFocusEvent, value);
				((INode)element).OnBlur -= new NodeEventHandler (OnLosingFocus);
			}
		}

		private void OnLosingFocus (object sender, EventArgs e)
		{
			HtmlElementEventHandler eh = (HtmlElementEventHandler) Events[LosingFocusEvent];
			if (eh != null) {
				HtmlElementEventArgs ev = new HtmlElementEventArgs ();
				eh (this, ev);
			}
		}
		private static object LostFocusEvent = new object ();
		public event HtmlElementEventHandler LostFocus {
			add {
				Events.AddHandler (LostFocusEvent, value);
				((INode)element).OnBlur += new NodeEventHandler (OnLostFocus);
			}
			remove {
				Events.RemoveHandler (LostFocusEvent, value);
				((INode)element).OnBlur -= new NodeEventHandler (OnLostFocus);
			}
		}

		private void OnLostFocus (object sender, EventArgs e)
		{
			HtmlElementEventHandler eh = (HtmlElementEventHandler) Events[LostFocusEvent];
			if (eh != null) {
				HtmlElementEventArgs ev = new HtmlElementEventArgs ();
				eh (this, ev);
			}
		}

		#endregion
	}
}
