namespace ZetaResourceEditor.UI.Helper.ExtendedWebBrowser
{
	#region Using directives.
	// ----------------------------------------------------------------------

	using System;
	using System.Collections.Generic;
	using System.Security.Permissions;
	using System.Text;
	using System.Windows.Forms;
	using System.Text.RegularExpressions;
	using Zeta.VoyagerLibrary.Common;
	using Zeta.VoyagerLibrary.Logging;
	using System.Diagnostics;
	using System.Runtime.InteropServices;
	using System.Runtime.InteropServices.ComTypes;

	// ----------------------------------------------------------------------
	#endregion

	/////////////////////////////////////////////////////////////////////////

	/// <summary>
	/// Extended web browser control with several enhancements.
	/// </summary>
	[ComVisible( true )]
	public class ExtendedWebBrowserControl :
		WebBrowser
	{
		#region Extended support to set text correctly, even when not fully initialized.
		// ------------------------------------------------------------------

		public Uri LastNavigatedUrl
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets | sets the HTML contents of the page 
		/// displayed in the WebBrowser control.
		/// </summary>
		public new virtual string DocumentText
		{
			get
			{
				return base.DocumentText;
			}
			set
			{
				if ( !IsDisposed )
				{
					// Set BOTH to get the OnDocumentCompleted event raised.
					base.DocumentText = value;
					SetDocumentHtml( value );
				}
			}
		}

		public override string Text
		{
			get
			{
				return DocumentText;
			}
			set
			{
				DocumentText = value;
			}
		}

		/*
		public string Charset
		{
			get
			{
				if ( DomDocument == null )
				{
					return string.Empty;
				}
				else
				{
					return DomDocument.charset;
				}
			}
			set
			{
				if ( !IsDisposed )
				{
					SetCharset( value );
				}
			}
		}
		*/

		private int _documentCompleteCount;

		/// <summary>
		/// Raises the DocumentCompleted event.
		/// </summary>
		protected override void OnDocumentCompleted(
			WebBrowserDocumentCompletedEventArgs e )
		{
			IsLoaded = true;

			base.OnDocumentCompleted( e );

			if ( _pendingCallsToOnDocumentComplete > 0 )
			{
				_pendingCallsToOnDocumentComplete--;
			}

			_documentCompleteCount++;
			if ( EventSink != null )
			{
				EventSink.OnDocumentComplete( this, _documentCompleteCount );
			}

			// If some delayed text need to be set.
			if ( !string.IsNullOrEmpty( _postPoneSetDocumentHtml ) )
			{
				var html = _postPoneSetDocumentHtml;
				_postPoneSetDocumentHtml = string.Empty;

				// Causes another call to OnDocumentComplete,
				// when fully loaded.
				SetDocumentHtml( html );
			}

			/*
			if ( !string.IsNullOrEmpty( postPoneSetCharset ) )
			{
				string charset = postPoneSetCharset;
				postPoneSetCharset = string.Empty;

				SetCharset( charset );
			}
			*/
		}

		/// <summary>
		/// Indicated whether fully initialized.
		/// </summary>
		protected bool IsReady
		{
			get
			{
				return Document != null;
			}
		}

		/*
		private void SetCharset(
			string charset )
		{
			// if not yet ready, postpone until ready.
			if ( !IsReady && postPoneSetCharset != charset )
			{
				postPoneSetCharset = charset;
			}
			else if ( IsReady )
			{
				if ( charset == null )
				{
					charset = string.Empty;
				}

				DomDocument.charset = charset;

				postPoneSetCharset = string.Empty;
			}
		}
		*/

		/// <summary>
		/// Put a HTML code into the main window's HTML view.
		/// </summary>
		private void SetDocumentHtml(
			string html )
		{
			// if not yet ready, postpone until ready.
			if ( !IsReady && _postPoneSetDocumentHtml != html )
			{
				_postPoneSetDocumentHtml = html;
			}
			else if ( IsReady )
			{
				// need to do this since html could be a pointer to 
				// PostPoneSetDocumentHtml.

				// --

				_pendingCallsToOnDocumentComplete++;
				try
				{
					if ( html == null )
					{
						html = string.Empty;
					}

					var documentEncoding = GetHtmlEncoding( html );

					if ( !string.IsNullOrEmpty( documentEncoding ) )
					{
						if ( Document != null )
						{
							if ( string.Compare( Document.Encoding, documentEncoding, true ) != 0 )
							{
								Document.Encoding = documentEncoding;
							}
						}
					}

					if ( Document != null )
					{
						Document.Write( html );
					}
					base.DocumentText = html;
				}
				finally
				{
					_pendingCallsToOnDocumentComplete--;
				}

				_postPoneSetDocumentHtml = string.Empty;
			}
		}

		/// <summary>
		/// Gets the HTML encoding.
		/// </summary>
		/// <param name="html">The HTML.</param>
		/// <returns></returns>
		protected virtual string GetHtmlEncoding(
			string html )
		{
			return DetectEncodingName( html );
		}

		/// <summary>
		/// <meta http-equiv="Content-Type" content="text/html; charset=utf-8"&gridTemplate;.
		/// </summary>
		private const string HtmlContentEncodingPattern =
			@"<meta\s+http-equiv\s*=\s*[""'\s]?Content-Type\b.*?charset\s*=\s*([^""'\s>]*)";

		/// <summary>
		/// Detects the name of the encoding.
		/// </summary>
		/// <param name="html">The HTML.</param>
		/// <returns></returns>
		private static string DetectEncodingName(
			string html )
		{
			if ( string.IsNullOrEmpty( html ) )
			{
				return null;
			}
			else
			{
				// Find.
				var match = Regex.Match(
					html,
					HtmlContentEncodingPattern,
					RegexOptions.Singleline |
					RegexOptions.IgnoreCase );

				var groups = GetEffectiveMatchGroups( match );

				return !match.Success || groups.Count < 2 ? null : groups[1].Value;
			}
		}

		/// <summary>
		/// The Match.Groups collection seems to sometimes collect
		/// unsuccessfull entries. Don't count those.
		/// </summary>
		/// <param name="match">The match.</param>
		/// <returns></returns>
		public static List<Group> GetEffectiveMatchGroups(
			Match match )
		{
			var result = new List<Group>();

			if ( match != null && match.Success )
			{
				foreach ( Group group in match.Groups )
				{
					if ( group.Success )
					{
						result.Add( group );
					}
				}
			}

			return result;
		}

		/// <summary>
		/// Helper variables for storing infos to set when finally loaded.
		/// </summary>
		private string _postPoneSetDocumentHtml = string.Empty;
		/*
		private string postPoneSetCharset = string.Empty;
		*/
		private int _pendingCallsToOnDocumentComplete;

		// ------------------------------------------------------------------
		#endregion

		#region Public methods.
		// ------------------------------------------------------------------

		/// <summary>
		///
		/// </summary>
		public ExtendedWebBrowserControl()
		{
			ScriptErrorsSuppressed = true;
		}

		/// <summary>
		/// Shows a blank page.
		/// </summary>
		public void Clear()
		{
			if ( IsReady )
			{
				Navigate( @"about:blank" );
			}
		}

		/// <summary>
		/// 
		/// </summary>
		public void CreateNewDocument()
		{
			DocumentText = string.Empty;
		}

		// ------------------------------------------------------------------
		#endregion

		#region Reading and writing values from a script to the web browser.
		// ------------------------------------------------------------------

		/// <summary>
		/// This can be called from JavaScript that is being loaded into
		/// the HTML control. See "WebBrowser.ObjectForScripting" in the 
		/// documentation manual for further details.
		/// </summary>
		/// <remarks>
		/// Although this method is public, it should not be called from
		/// your C# code but only from your JavaScript on the HTML page.
		/// </remarks>
		public virtual string GetValueFromScript(
			string key )
		{
			var args = new ScriptValueGetEventArgs( key );

			OnScriptValueGet( args );

			return args.Value;
		}

		/// <summary>
		/// This can be called from JavaScript that is being loaded into
		/// the HTML control. See "WebBrowser.ObjectForScripting" in the 
		/// documentation manual for further details.
		/// </summary>
		/// <remarks>
		/// Although this method is public, it should not be called from
		/// your C# code but only from your JavaScript on the HTML page.
		/// </remarks>
		public virtual void SetValueFromScript(
			string key,
			string value )
		{
			var args = new ScriptValueSetEventArgs( key, value );

			OnScriptValueSet( args );
		}

		/// <summary>
		/// Subscribable event. Called when a JavaScript within the 
		/// web browser wants to get a value.
		/// </summary>
		public event EventHandler<ScriptValueGetEventArgs> ScriptValueGet;

		/// <summary>
		/// Subscribable event. Called when a JavaScript within the 
		/// web browser wants to set a value.
		/// </summary>
		public event EventHandler<ScriptValueSetEventArgs> ScriptValueSet;

		/// <summary>
		/// 
		/// </summary>
		protected virtual void OnScriptValueGet(
			ScriptValueGetEventArgs args )
		{
			if ( ScriptValueGet != null )
			{
				ScriptValueGet( this, args );
			}
		}

		/// <summary>
		/// 
		/// </summary>
		protected virtual void OnScriptValueSet(
			ScriptValueSetEventArgs args )
		{
			if ( ScriptValueSet != null )
			{
				ScriptValueSet( this, args );
			}
		}

		// ------------------------------------------------------------------
		#endregion

		#region Public properties.
		// ------------------------------------------------------------------

		/// <summary>
		/// True when the html source is completely loaded 
		/// (either from memory or from file).
		/// </summary>
		protected bool IsLoaded
		{
			get;
			private set;
		}

		/// <summary>
		/// Get/set the event sink that receives notifications.
		/// </summary>
		/// <value>The event sink.</value>
		public IExtendedWebBrowserEventSink EventSink
		{
			get;
			set;
		}

		/// <summary>
		/// Gets the default encoding.
		/// </summary>
		/// <value>The default encoding.</value>
		public static string DefaultEncoding
		{
			get
			{
				return @"unicode";
			}
		}

		/// <summary>
		/// Get the body tag HTML text from the clipboard (if any).
		/// Returns null if currently no HTML text on the clipboard.
		/// </summary>
		public virtual string DocumentTextBody
		{
			get
			{
				return ExtractHtmlBody( DocumentText );
			}
			set
			{
				var container = DocumentTextNonBody;

				if ( string.IsNullOrEmpty( container ) ||
					!container.Contains( @"{content}" ) )
				{
					DocumentText = value;
				}
				else
				{
					DocumentText =
						container.Replace( @"{content}", value );
				}
			}
		}

		/// <summary>
		/// Extracts the HTML body.
		/// </summary>
		/// <param name="htmlCode">The HTML code.</param>
		/// <returns></returns>
		public static string ExtractHtmlBody(
			string htmlCode )
		{
			if ( string.IsNullOrEmpty( htmlCode ) )
			{
				return htmlCode;
			}
			else
			{
				var regex = new Regex(
					@".*?<body[^>]*>(.*?)</body>",
					RegexOptions.Singleline |
						RegexOptions.IgnoreCase );

				// TODO: Precompile in external DLL, since it seems to be very slow.
				var m = regex.Match( htmlCode );

				return m.Success ? m.Groups[1].Value : htmlCode;
			}
		}

		/// <summary>
		/// Returns the automation object for the web browser
		/// </summary>
		public object Application
		{
			get
			{
				return WebBrowser2.Application;
			}
		}

		/// <summary>
		/// Gets the web browser 2.
		/// </summary>
		/// <value>The web browser 2.</value>
		public UnsafeNativeMethods.IWebBrowser2 WebBrowser2
		{
			get;
			private set;
		}

		// ------------------------------------------------------------------
		#endregion

		#region Extended, error-free clipboard HTML handling.
		// ------------------------------------------------------------------

		/// <summary>
		/// Get the selected HTML text from the clipboard (if any).
		/// Returns null if currently no HTML text on the clipboard.
		/// </summary>
		public static string ClipboardHtmlText
		{
			get
			{
				if ( Clipboard.ContainsText( TextDataFormat.Html ) )
				{
					string htmlCode;
					byte[] originalBuffer;

					GetHtmlFromClipboard( out htmlCode, out originalBuffer );

					//split Html to htmlInfo (and htmlSource)
					var htmlInfo = htmlCode.Substring( 0, htmlCode.IndexOf( '<' ) - 1 );

					const string startFragmentText = @"StartFragment:";
					const string endFragmentText = @"EndFragment:";

					//get Fragment positions
					var tmp = htmlInfo.Substring( htmlInfo.IndexOf( startFragmentText ) +
						startFragmentText.Length );
					tmp = tmp.Substring( 0, tmp.IndexOf( '\r' ) );
					var posStartSelection = Convert.ToInt32( tmp );

					tmp = htmlInfo.Substring( htmlInfo.IndexOf( endFragmentText ) +
						endFragmentText.Length );
					tmp = tmp.Substring( 0, tmp.IndexOf( '\r' ) );
					var posEndSelection = Convert.ToInt32( tmp );

					// get Fragment. Always UTF-8 as of spec.
					var s = Encoding.UTF8.GetString(
						originalBuffer,
						posStartSelection,
						posEndSelection - posStartSelection );

					return s;
				}
				else
				{
					return null;
				}
			}
		}

		/// <summary>
		/// Get the body tag HTML text from the clipboard (if any).
		/// Returns null if currently no HTML text on the clipboard.
		/// </summary>
		public static string ClipboardHtmlTextBody
		{
			get
			{
				var htmlCode = ClipboardHtmlText;

				if ( string.IsNullOrEmpty( htmlCode ) )
				{
					return htmlCode;
				}
				else
				{
					var regex = new Regex(
						@".*?<body[^>]*>(.*?)</body>",
						RegexOptions.Singleline |
						RegexOptions.IgnoreCase );

					var m = regex.Match( htmlCode );

					if ( m.Success )
					{
						var groups =
							GetEffectiveMatchGroups(
							m );

						return groups[1].Value;
					}
					else
					{
						return htmlCode;
					}
				}
			}
		}

		/// <summary>
		/// Access the HTML text on the clipboard (if any).
		/// Returns null if currently no HTML text on the clipboard.
		/// </summary>
		public static string ClipboardHtmlTextComplete
		{
			get
			{
				if ( Clipboard.ContainsText( TextDataFormat.Html ) )
				{
					string htmlCode;
					byte[] originalBuffer;

					GetHtmlFromClipboard( out htmlCode, out originalBuffer );

					return htmlCode;
				}
				else
				{
					return null;
				}
			}
		}

		/// <summary>
		/// See http://66.249.93.104/search?q=cache:yfQWT9XlYogJ:www.eggheadcafe.com/aspnet_answers/NETFrameworkNETWindowsForms/Apr2006/post26606306.asp+IDataObject+html+utf-8&hl=de&gl=de&ct=clnk&cd=1&client=firefox-a
		/// See http://bakamachine.blogspot.com/2006/05/workarond-for-dataobject-html.html
		/// </summary>
		/// <remarks>Added 2006-06-12, Uwe Keim.</remarks>
		/// <returns></returns>
		private static void GetHtmlFromClipboard(
			out string htmlCode,
			out byte[] originalBuffer )
		{
			originalBuffer = GetHtmlFromDataObject( Clipboard.GetDataObject() );
			htmlCode = Encoding.UTF8.GetString( originalBuffer );
		}

		/// <summary>
		/// Extracts data of type Dataformat.Html from an IdataObject data container
		/// This method shouldn't throw any exception but writes relevant exception informations in the debug window
		/// </summary>
		/// <param name="data">IdataObject data container</param>
		/// <returns>A byte[] array with the decoded string or null if the method fails</returns>
		/// <remarks>Added 2006-06-12, Uwe Keim.</remarks>
		private static byte[] GetHtmlFromDataObject(
			System.Windows.Forms.IDataObject data )
		{
			var interopData =
				(System.Runtime.InteropServices.ComTypes.IDataObject)data;

			var format =
				new FORMATETC
				{
					cfFormat = ((short)DataFormats.GetFormat( DataFormats.Html ).Id),
					dwAspect = DVASPECT.DVASPECT_CONTENT,
					lindex = (-1),
					tymed = TYMED.TYMED_HGLOBAL
				};

			STGMEDIUM stgmedium;
			stgmedium.tymed = TYMED.TYMED_HGLOBAL;
			stgmedium.pUnkForRelease = null;

			//try
			//{
			var queryResult = interopData.QueryGetData( ref format );
			//}
			//catch ( Exception exp )
			//{
			//	Debug.WriteLine( "HtmlFromIDataObject.GetHtml . QueryGetData(ref format) threw an exception: "
			//		+ Environment.NewLine + exp.ToString() );
			//	return null;
			//}

			if ( queryResult != 0 )
			{
// ReSharper disable InvocationIsSkipped
				Debug.WriteLine(
					string.Format(
					@"HtmlFromIDataObject.GetHtml . QueryGetData(ref format) returned a code != 0 code: {0}",
					queryResult ) );
// ReSharper restore InvocationIsSkipped
				return null;
			}

			//try
			//{
			interopData.GetData( ref format, out stgmedium );
			//}
			//catch ( Exception exp )
			//{
			//	System.Diagnostics.Debug.WriteLine( "HtmlFromIDataObject.GetHtml . GetData(ref format, out stgmedium) threw this exception: "
			//		+ Environment.NewLine + exp.ToString() );
			//	return null;
			//}

			if ( stgmedium.unionmember == IntPtr.Zero )
			{
// ReSharper disable InvocationIsSkipped
				Debug.WriteLine(
					@"HtmlFromIDataObject.GetHtml . stgmedium.unionmember returned an IntPtr pointing to zero" );
// ReSharper restore InvocationIsSkipped
				return null;
			}

			var pointer = stgmedium.unionmember;

			var handleRef = new HandleRef( null, pointer );

			byte[] rawArray;

			try
			{
				var ptr1 = new IntPtr( GlobalLock( handleRef ) );

				var length = GlobalSize( handleRef );

				rawArray = new byte[length];

				Marshal.Copy( ptr1, rawArray, 0, length );
			}
			//catch ( Exception exp )
			//{
			//	Debug.WriteLine( "HtmlFromIDataObject.GetHtml . Html Import threw an exception: " + Environment.NewLine + exp.ToString() );
			//}
			finally
			{
				GlobalUnlock( handleRef );

			}

			return rawArray;
		}

		[DllImport( @"kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true, SetLastError = true )]
		private static extern int GlobalLock( HandleRef handle );

		[DllImport( @"kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true, SetLastError = true )]
		private static extern bool GlobalUnlock( HandleRef handle );

		[DllImport( @"kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true, SetLastError = true )]
		private static extern int GlobalSize( HandleRef handle );

		// ------------------------------------------------------------------
		#endregion

		#region Private variables.
		// ------------------------------------------------------------------

		private AxHost.ConnectionPointCookie _cookie;
		private WebBrowserExtendedEvents _events;

		/*
				private const bool _wantSetHtmlEditDesigner = false;
		*/

		// ------------------------------------------------------------------
		#endregion

		#region Public events.
		// ------------------------------------------------------------------

		/// <summary>
		/// Set a static global handler for commands, when you do not
		/// want to derive from this class or do not want to set the
		/// instance handler.
		/// </summary>
		public static event AppCommandEventHandler StaticAppCommand = null;

		/// <summary>
		/// Called when the static app command handler did not fire.
		/// </summary>
		public event AppCommandEventHandler AppCommand = null;

		/// <summary>
		/// Event handler indicating that the GUI needs to be updated
		/// (enabled/disable controls etc.).
		/// </summary>
		public event EventHandler UINeedsUpdate;

		/// <summary>
		/// Fires when downloading of a document begins
		/// </summary>
		public event EventHandler Downloading;

		/// <summary>
		/// Fires before navigation occurs in the given object (on either a 
		/// window or frameset element).
		/// </summary>
		public event EventHandler<BrowserExtendedNavigatingEventArgs> StartNavigate;

		/// <summary>
		/// Raised when a new window is to be created. Extends
		///  DWebBrowserEvents2::NewWindow2 with additional information about 
		/// the new window.
		/// </summary>
		public event EventHandler<BrowserExtendedNavigatingEventArgs> StartNewWindow;

		/// <summary>
		/// Raised when a navigate Error occurs. Extends
		/// DWebBrowserEvents2::NavigateError with additional information .
		/// </summary>
		public event EventHandler<BrowserExtendedNavigateErrorEventArgs> NavigateError;

		/// <summary>
		/// Fires when downloading is completed
		/// </summary>
		/// <remarks>
		/// Here you could start monitoring for script errors. 
		/// </remarks>
		public event EventHandler DownloadComplete;

		// ------------------------------------------------------------------
		#endregion

		#region Private properties.
		// ------------------------------------------------------------------

		/// <summary>
		/// The framework around the body text.
		/// </summary>
		protected virtual string DocumentTextNonBody
		{
			get
			{
				// Must override in derived class.
				return string.Empty;
			}
		}

		// ------------------------------------------------------------------
		#endregion

		#region Class WebBrowserExtendedEvents.
		// ------------------------------------------------------------------

		//This class will capture events from the WebBrowser
		private class WebBrowserExtendedEvents :
			UnsafeNativeMethods.DWebBrowserEvents2
		{
			private readonly ExtendedWebBrowserControl _browser;

			public WebBrowserExtendedEvents( ExtendedWebBrowserControl browser )
			{
				_browser = browser;
			}

			#region DWebBrowserEvents2 Members

			//Implement whichever events you wish
			public void BeforeNavigate2(
				object pDisp,
				ref object url,
				ref object flags,
				ref object targetFrameName,
				ref object postData,
				ref object headers,
// ReSharper disable RedundantAssignment
				ref bool cancel )
// ReSharper restore RedundantAssignment
			{
				var urlUri = new Uri( url.ToString() );

				string tFrame = null;
				if ( targetFrameName != null )
				{
					tFrame = targetFrameName.ToString();
				}

				var args =
					new BrowserExtendedNavigatingEventArgs( pDisp, urlUri, tFrame, UrlContexts.None );

				_browser.OnStartNavigate( args );

				cancel = args.Cancel;
				//pDisp = args.AutomationObject;
			}

			//The NewWindow2 event, used on Windows XP SP1 and below
// ReSharper disable RedundantAssignment
			public void NewWindow2( ref object pDisp, ref bool cancel )
// ReSharper restore RedundantAssignment
			{
				var args =
					new BrowserExtendedNavigatingEventArgs( pDisp, null, null, UrlContexts.None );
				_browser.OnStartNewWindow( args );
				cancel = args.Cancel;
				pDisp = args.AutomationObject;
			}

			// NewWindow3 event, used on Windows XP SP2 and higher
			public void NewWindow3(
				ref object ppDisp,
// ReSharper disable RedundantAssignment
				ref bool cancel,
// ReSharper restore RedundantAssignment
				uint dwFlags,
				string bstrUrlContext,
				string bstrUrl )
			{
				var args =
					new BrowserExtendedNavigatingEventArgs( ppDisp, new Uri( bstrUrl ), null, (UrlContexts)dwFlags );
				_browser.OnStartNewWindow( args );
				cancel = args.Cancel;
				ppDisp = args.AutomationObject;
			}

			// Fired when downloading begins
			public void DownloadBegin()
			{
				_browser.OnDownloading( EventArgs.Empty );
			}

			// Fired when downloading is completed
			public void DownloadComplete()
			{
				_browser.OnDownloadComplete( EventArgs.Empty );
			}

			#region Unused events

			// This event doesn't fire. 
			[DispId( 0x00000107 )]
			public void WindowClosing( bool isChildWindow, ref bool cancel )
			{
			}

			public void OnQuit()
			{

			}

			public void StatusTextChange( string text )
			{
			}

			public void ProgressChange( int progress, int progressMax )
			{
			}

			public void TitleChange( string text )
			{
			}

			public void PropertyChange( string szProperty )
			{
			}

			public void NavigateComplete2( object pDisp, ref object url )
			{
			}

			public void DocumentComplete( object pDisp, ref object url )
			{
			}

			public void OnVisible( bool visible )
			{
			}

			public void OnToolBar( bool toolBar )
			{
			}

			public void OnMenuBar( bool menuBar )
			{
			}

			public void OnStatusBar( bool statusBar )
			{
			}

			public void OnFullScreen( bool fullScreen )
			{
			}

			public void OnTheaterMode( bool theaterMode )
			{
			}

			public void WindowSetResizable( bool resizable )
			{
			}

			public void WindowSetLeft( int left )
			{
			}

			public void WindowSetTop( int top )
			{
			}

			public void WindowSetWidth( int width )
			{
			}

			public void WindowSetHeight( int height )
			{
			}

			public void SetSecureLockIcon( int secureLockIcon )
			{
			}

			public void FileDownload( ref bool cancel )
			{
			}

			public void NavigateError(
				object pDisp,
				ref object url,
				ref object frame,
				ref object statusCode,
// ReSharper disable RedundantAssignment
				ref bool cancel )
// ReSharper restore RedundantAssignment
			{
				var urlUri = new Uri( url.ToString() );

				var neStatus =
					(NavigateErrorStatusCode)(uint)ConvertHelper.ToInt64( statusCode );

				var args =
					new BrowserExtendedNavigateErrorEventArgs( pDisp, urlUri, neStatus, UrlContexts.None );

				_browser.OnNavigateError( args );

				cancel = args.Cancel;
			}

			public void PrintTemplateInstantiation( object pDisp )
			{
			}

			public void PrintTemplateTeardown( object pDisp )
			{
			}

			public void UpdatePageStatus( object pDisp, ref object nPage, ref object fDone )
			{
			}

			public void PrivacyImpactedStateChange( bool bImpacted )
			{
			}

			public void CommandStateChange( int command, bool enable )
			{
			}

			public void ClientToHostWindow( ref int cx, ref int cy )
			{
			}
			#endregion

			#endregion
		}

		// ------------------------------------------------------------------
		#endregion

		#region Private methods.
		// ------------------------------------------------------------------

		/// <summary>
		/// Raises the <see cref="Downloading"/> event
		/// </summary>
		/// <param name="e">Empty <see cref="EventArgs"/>
		/// <remarks>
		/// You could start an animation or a notification that downloading is 
		/// starting
		/// </remarks>
		protected void OnDownloading( EventArgs e )
		{
			if ( Downloading != null )
			{
				Downloading( this, e );
			}
		}

		/// <summary>
		/// Raises the <see cref="DownloadComplete"/> event
		/// </summary>
		/// <param name="e">Empty <see cref="EventArgs"/>
		protected virtual void OnDownloadComplete( EventArgs e )
		{
			if ( DownloadComplete != null )
			{
				DownloadComplete( this, e );
			}
		}

		/// <summary>
		/// Raises the <see cref="StartNewWindow"/> event
		/// </summary>
		/// <exception cref="ArgumentNullException">Thrown when BrowserExtendedNavigatingEventArgs is null</exception>
		protected void OnStartNewWindow( BrowserExtendedNavigatingEventArgs e )
		{
			if ( e == null )
			{
				throw new ArgumentNullException( "e" );
			}

			if ( StartNewWindow != null )
			{
				StartNewWindow( this, e );
			}
		}

		/// <summary>
		/// Raises the <see cref="StartNavigate"/> event
		/// </summary>
		/// <exception cref="ArgumentNullException">Thrown when BrowserExtendedNavigatingEventArgs is null</exception>
		protected void OnStartNavigate(
			BrowserExtendedNavigatingEventArgs e )
		{
			if ( e == null )
			{
				throw new ArgumentNullException( @"e" );
			}

			if ( StartNavigate != null )
			{
				StartNavigate( this, e );
			}
		}

		/// <summary>
		/// Raises the <see cref="NavigateError"/> event.
		/// </summary>
		/// <exception cref="ArgumentNullException">Thrown when 
		/// BrowserExtendedNavigateErrorEventArgs is null.</exception>
		protected void OnNavigateError(
			BrowserExtendedNavigateErrorEventArgs e )
		{
			if ( e == null )
			{
				throw new ArgumentNullException( @"e" );
			}

			if ( NavigateError != null )
			{
				NavigateError( this, e );
			}
		}

		/// <summary>
		/// This method will be called to give
		/// you a chance to create your own event sink.
		/// </summary>
		[PermissionSet( SecurityAction.LinkDemand, Name = @"FullTrust" )]
		protected override void CreateSink()
		{
			// Make sure to call the base class or the normal events won't fire.
			base.CreateSink();

			_events = new WebBrowserExtendedEvents( this );
			_cookie = new AxHost.ConnectionPointCookie(
				ActiveXInstance,
				_events,
				typeof( UnsafeNativeMethods.DWebBrowserEvents2 ) );
		}

		/// <summary>
		/// Detaches the event sink.
		/// </summary>
		[PermissionSet( SecurityAction.LinkDemand, Name = @"FullTrust" )]
		protected override void DetachSink()
		{
			if ( null != _cookie )
			{
				_cookie.Disconnect();
				_cookie = null;
			}

			base.DetachSink();
		}

		/// <summary>
		/// This method supports the .NET Framework
		/// infrastructure and is not intended
		/// to be used directly from your code.
		/// Called by the control when the underlying
		/// ActiveX control is created.
		/// </summary>
		/// <param name="nativeActiveXObject">Ein Objekt, das das zugrunde 
		/// liegende ActiveX-Steuerelement darstellt.</param>
		[PermissionSet( SecurityAction.LinkDemand, Name = @"FullTrust" )]
		protected override void AttachInterfaces(
			object nativeActiveXObject )
		{
			WebBrowser2 =
			  (UnsafeNativeMethods.IWebBrowser2)nativeActiveXObject;
			base.AttachInterfaces( nativeActiveXObject );
		}

		/// <summary>
		/// This method supports the .NET Framework infrastructure
		/// and is not intended to be used directly from your code.
		/// Called by the control when the underlying
		/// ActiveX control is discarded.
		/// </summary>
		[PermissionSet( SecurityAction.LinkDemand, Name = @"FullTrust" )]
		protected override void DetachInterfaces()
		{
			WebBrowser2 = null;
			base.DetachInterfaces();
		}

		/// <summary>
		/// Best practice, see C# MSDN documentation of the "lock" keyword.
		/// </summary>
		private static readonly object TypeLock = new object();

		/// <summary>
		/// 
		/// </summary>
		protected override void OnNavigated(
			WebBrowserNavigatedEventArgs e )
		{
			base.OnNavigated( e );

			IsLoaded = true;

			LastNavigatedUrl = e.Url;
		}

		/// <summary>
		/// 
		/// </summary>
		protected override void OnNavigating(
			WebBrowserNavigatingEventArgs e )
		{
			try
			{
				IsLoaded = false;

				var routing = EventHandled.Default;
				if ( EventSink != null )
				{
					routing = EventSink.OnNavigating( this, e );
				}

				if ( routing != EventHandled.StopRouting )
				{
					const string protocol = @"app:";
					if ( e.Url.ToString().StartsWith(
						protocol,
						StringComparison.InvariantCultureIgnoreCase ) )
					{
						OnAppCommand( e.Url );
						e.Cancel = true;
					}

					base.OnNavigating( e );
				}
			}
			catch ( Exception x )
			{
				// The web browser control seems to silently eat exceptions,
				// therefore log at least.
// ReSharper disable InvocationIsSkipped
				LogCentral.Current.LogDebug( x );
// ReSharper restore InvocationIsSkipped
				throw;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		protected virtual void OnAppCommand(
			Uri url )
		{
			HandleDefaultAppCommands(
				new AppCommandEventArgs( url ) );

			if ( AppCommand != null )
			{
				AppCommand( this, new AppCommandEventArgs( url ) );
			}

			lock ( TypeLock )
			{
				if ( StaticAppCommand != null )
				{
					StaticAppCommand( this, new AppCommandEventArgs( url ) );
				}
			}
		}

		/// <summary>
		/// Handles the default app commands.
		/// </summary>
		/// <param name="e">The AppCommandEventArgs
		/// instance containing the event data.</param>
		protected virtual void HandleDefaultAppCommands(
			AppCommandEventArgs e )
		{
			if ( e.CommandName == @"ShellExecuteFile" )
			{
				Process.Start( e.CommandParameters[@"FilePath"] );
			}
		}

		/*
				/// <summary>
				/// Gets the edit services.
				/// </summary>
				/// <returns></returns>
				private mshtml.IHTMLEditServices GetEditServices()
				{
					// an edit designer can only be attached,
					// when the document is ready and the designmode is on.
					Debug.Assert( IsLoaded, @"Document is not loaded." );
					Debug.Assert(
						DomDocument.designMode == @"On",
						string.Format(
						@"Design mode must be 'On' but is '{0}'.",
						DomDocument.designMode ) );

					var sp = (NativeMethods.IServiceProvider)DomDocument;

					object oEsp;

					sp.QueryService(
						NativeMethods.SID_SHTMLEditServices,
						NativeMethods.IID_IHTMLEditServices,
						out oEsp );

					var esp = (mshtml.IHTMLEditServices)oEsp;
					return esp;
				}
		*/

/*
		/// <summary>
		/// 
		/// </summary>
		private static void AddEditDesigner()
		{
			//if ( _wantSetHtmlEditDesigner )
			//{
			//    // An edit designer can only be attached,
			//    // when the document is ready and the designmode is on.
			//    Debug.Assert( IsLoaded, @"Document is not loaded." );
			//    Debug.Assert(
			//        DomDocument.designMode == @"On",
			//        string.Format(
			//        @"Design mode must be 'On' but is '{0}'.",
			//        DomDocument.designMode ) );

			//    mshtml.IHTMLEditDesigner designer = this;
			//    mshtml.IHTMLEditServices esp = GetEditServices();

			//    try
			//    {
			//        esp.AddDesigner( designer );
			//    }
			//    catch ( COMException x )
			//    {
			//        if ( x.ErrorCode == NativeMethods.E_INVALIDARG )
			//        {
			//            LogCentral.Current.LogError(
			//                @"[HTML-V] _com_error exception ocured during adding a designer. A known issue is a contained IFRAME tag.",
			//                x );
			//        }
			//        else
			//        {
			//            throw;
			//        }
			//    }
			//}
		}
*/

/*
		/// <summary>
		/// 
		/// </summary>
		private static void RemoveEditDesigner()
		{
			//if ( _wantSetHtmlEditDesigner )
			//{
			//    NativeMethods.IServiceProvider sp =
			//        DomDocument as NativeMethods.IServiceProvider;
			//    Debug.Assert( sp != null );

			//    object oEsp;

			//    sp.QueryService(
			//        NativeMethods.SID_SHTMLEditServices,
			//        NativeMethods.IID_IHTMLEditServices,
			//        out oEsp );

			//    mshtml.IHTMLEditServices esp = oEsp as mshtml.IHTMLEditServices;

			//    mshtml.IHTMLEditDesigner designer = this;
			//    try
			//    {
			//        Debug.Assert( esp != null );
			//        esp.RemoveDesigner( designer );
			//    }
			//    catch ( COMException x )
			//    {
			//        if ( x.ErrorCode == NativeMethods.E_INVALIDARG )
			//        {
			//            LogCentral.Current.LogError(
			//                @"[HTML-V] _com_error exception ocured during removing a designer. A known issue is a contained IFRAME tag.",
			//                x );
			//        }
			//        else
			//        {
			//            throw;
			//        }
			//    }
			//}
		}
*/

		/// <summary>
		/// Called when [update UI].
		/// </summary>
		protected virtual void OnUpdateUI()
		{
			if ( EventSink != null )
			{
				EventSink.OnUpdateUI( this );
			}

			if ( UINeedsUpdate != null )
			{
				UINeedsUpdate( this, EventArgs.Empty );
			}
		}

		// ------------------------------------------------------------------
		#endregion

		[StructLayout( LayoutKind.Sequential, CharSet = CharSet.Unicode )]
// ReSharper disable InconsistentNaming
		public struct OLECMDTEXT
// ReSharper restore InconsistentNaming
		{
			public uint cmdtextf;
			public uint cwActual;
			public uint cwBuf;
			[MarshalAs( UnmanagedType.ByValTStr, SizeConst = 100 )]
			public char rgwz;
		}

		[StructLayout( LayoutKind.Sequential )]
// ReSharper disable InconsistentNaming
		public struct OLECMD
// ReSharper restore InconsistentNaming
		{
			public uint cmdID;
			public uint cmdf;
		}

		// Interop definition for IOleCommandTarget. 
		[ComImport,
		Guid( @"b722bccb-4e68-101b-a2bc-00aa00404770" ),
		InterfaceType( ComInterfaceType.InterfaceIsIUnknown )]
		public interface IOleCommandTarget
		{
			//IMPORTANT: The order of the methods is critical here. You
			//perform early binding in most cases, so the order of the methods
			//here MUST match the order of their vtable layout (which is determined
			//by their layout in IDL). The interop calls key off the vtable ordering,
			//not the symbolic names. Therefore, if you switched these method declarations
			//and tried to call the Exec method on an IOleCommandTarget interface from your
			//application, it would translate into a call to the QueryStatus method instead.
			void QueryStatus( ref Guid pguidCmdGroup, UInt32 cCmds,
				[MarshalAs( UnmanagedType.LPArray, SizeParamIndex = 1 )] OLECMD[] prgCmds, ref OLECMDTEXT cmdText );
			void Exec( ref Guid pguidCmdGroup, uint nCmdId, uint nCmdExecOpt, ref object pvaIn, ref object pvaOut );
		}

/*
		private Guid _cmdGuid = new Guid( @"ED016940-BD5B-11CF-BA4E-00C04FD70816" );
*/

/*
		private enum MiscCommandTarget
		{
			Find = 1,
			ViewSource,
			Options
		}
*/
	}

	/////////////////////////////////////////////////////////////////////////
}