//
// System.Web.SiteMapProvider
//
// Authors:
//	Ben Maurer (bmaurer@users.sourceforge.net)
//	Lluis Sanchez Gual (lluis@novell.com)
//
// (C) 2003 Ben Maurer
// (C) 2005-2009 Novell, Inc (http://www.novell.com)
//

//
// 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.
//

using System.ComponentModel;
using System.Collections;
using System.Collections.Specialized;
using System.Text;
using System.Configuration.Provider;
using System.Web.Util;
using System.Globalization;
using System.Web.Configuration;

namespace System.Web
{
	public abstract class SiteMapProvider : ProviderBase
	{
		static readonly object siteMapResolveEvent = new object ();
		
		internal object this_lock = new object ();
		
		bool enableLocalization;
		SiteMapProvider parentProvider;
		SiteMapProvider rootProviderCache;
		bool securityTrimming;
		object resolveLock = new Object();
		bool resolving;

		EventHandlerList events = new EventHandlerList ();
		
		public event SiteMapResolveEventHandler SiteMapResolve {
			add { events.AddHandler (siteMapResolveEvent, value); }
			remove { events.RemoveHandler (siteMapResolveEvent, value); }
		}
		
		protected virtual void AddNode (SiteMapNode node)
		{
			AddNode (node, null);
		}
		
		internal protected virtual void AddNode (SiteMapNode node, SiteMapNode parentNode)
		{
			throw new NotImplementedException ();
		}

		public virtual SiteMapNode FindSiteMapNode (HttpContext context)
		{
			if (context == null)
				return null;

			HttpRequest req = context.Request;
			if (req == null)
				return null;
			
			SiteMapNode ret = this.FindSiteMapNode (req.RawUrl);
			if (ret == null)
				ret = this.FindSiteMapNode (req.Path);
			return ret;
		}

		public abstract SiteMapNode FindSiteMapNode (string rawUrl);
		
		public virtual SiteMapNode FindSiteMapNodeFromKey (string key)
		{
			/* msdn2 says this function always returns
			 * null, but it seems to just call
			 * FindSiteMapNode(string rawUrl) */
			return FindSiteMapNode (key);
		}

		public abstract SiteMapNodeCollection GetChildNodes (SiteMapNode node);
		
		public virtual SiteMapNode GetCurrentNodeAndHintAncestorNodes (int upLevel)
		{
			if (upLevel < -1) throw new ArgumentOutOfRangeException ("upLevel");

			return CurrentNode;
		}
		
		public virtual SiteMapNode GetCurrentNodeAndHintNeighborhoodNodes (int upLevel, int downLevel)
		{
			if (upLevel < -1) throw new ArgumentOutOfRangeException ("upLevel");
			if (downLevel < -1) throw new ArgumentOutOfRangeException ("downLevel");
			
			return CurrentNode;
		}

		public abstract SiteMapNode GetParentNode (SiteMapNode node);
		
		public virtual SiteMapNode GetParentNodeRelativeToCurrentNodeAndHintDownFromParent (int walkupLevels, int relativeDepthFromWalkup)
		{
			if (walkupLevels < 0) throw new ArgumentOutOfRangeException ("walkupLevels");
			if (relativeDepthFromWalkup < 0) throw new ArgumentOutOfRangeException ("relativeDepthFromWalkup");
			
			SiteMapNode node = GetCurrentNodeAndHintAncestorNodes (walkupLevels);
			for (int n=0; n<walkupLevels && node != null; n++)
				node = GetParentNode (node);
				
			if (node == null) return null;

			HintNeighborhoodNodes (node, 0, relativeDepthFromWalkup);
			return node;
		}
		
		public virtual SiteMapNode GetParentNodeRelativeToNodeAndHintDownFromParent (SiteMapNode node, int walkupLevels, int relativeDepthFromWalkup)
		{
			if (walkupLevels < 0) throw new ArgumentOutOfRangeException ("walkupLevels");
			if (relativeDepthFromWalkup < 0) throw new ArgumentOutOfRangeException ("relativeDepthFromWalkup");
			if (node == null) throw new ArgumentNullException ("node");
			
			HintAncestorNodes (node, walkupLevels);
			for (int n=0; n<walkupLevels && node != null; n++)
				node = GetParentNode (node);
				
			if (node == null) return null;
			
			HintNeighborhoodNodes (node, 0, relativeDepthFromWalkup);
			return node;
		}
		
		protected internal abstract SiteMapNode GetRootNodeCore ();
		
		protected static SiteMapNode GetRootNodeCoreFromProvider (SiteMapProvider provider)
		{
			return provider.GetRootNodeCore ();
		}
		
		public virtual void HintAncestorNodes (SiteMapNode node, int upLevel)
		{
			if (upLevel < -1) throw new ArgumentOutOfRangeException ("upLevel");
			if (node == null) throw new ArgumentNullException ("node");
		}
		
		public virtual void HintNeighborhoodNodes (SiteMapNode node, int upLevel, int downLevel)
		{
			if (upLevel < -1) throw new ArgumentOutOfRangeException ("upLevel");
			if (downLevel < -1) throw new ArgumentOutOfRangeException ("downLevel");
			if (node == null) throw new ArgumentNullException ("node");
		}
		
		protected virtual void RemoveNode (SiteMapNode node)
		{
			throw new NotImplementedException ();
		}

		public override void Initialize (string name, NameValueCollection attributes)
		{
			base.Initialize (name, attributes);
			if (attributes ["securityTrimmingEnabled"] != null)
				securityTrimming = (bool) Convert.ChangeType (attributes ["securityTrimmingEnabled"], typeof (bool));
		}
		
		[MonoTODO ("need to implement cases 2 and 3")]
		public virtual bool IsAccessibleToUser (HttpContext context, SiteMapNode node)
		{
			if (context == null) throw new ArgumentNullException ("context");
			if (node == null) throw new ArgumentNullException ("node");

			if (!SecurityTrimmingEnabled)
				return true;

			/* The node is accessible (according to msdn2) if:
			 *
			 * 1. The Roles exists on node and the current user is in at least one of the specified roles.
			 *
			 * 2. The current thread has an associated WindowsIdentity that has file access to the requested URL and
			 * the URL is located within the directory structure for the application.
			 *
			 * 3. The current user is authorized specifically for the requested URL in the authorization element for
			 * the current application and the URL is located within the directory structure for the application. 
			*/

			/* 1. */
			IList roles = node.Roles;
			if (roles != null && roles.Count > 0) {
				foreach (string rolename in roles)
					if (rolename == "*" || context.User.IsInRole (rolename))
						return true;
			}
			
			/* 2. */
			/* XXX */

			/* 3. */
			string url = node.Url;
			if(!String.IsNullOrEmpty(url)) {
				// TODO check url is located within the current application

				if (VirtualPathUtility.IsAppRelative (url) || !VirtualPathUtility.IsAbsolute (url))
					url = VirtualPathUtility.Combine (VirtualPathUtility.AppendTrailingSlash (HttpRuntime.AppDomainAppVirtualPath), url);

				AuthorizationSection config = (AuthorizationSection) WebConfigurationManager.GetSection (
					"system.web/authorization",
					url);
				if (config != null)
					return config.IsValidUser (context.User, context.Request.HttpMethod);
			}

			return false;
		}
		
		public virtual SiteMapNode CurrentNode {
			get {
				if (HttpContext.Current != null) {
					SiteMapNode ret = ResolveSiteMapNode (HttpContext.Current);
					if (ret != null) return ret;
					return FindSiteMapNode (HttpContext.Current);
				} else
					return null;
			}
		}
		
		public virtual SiteMapProvider ParentProvider {
			get { return parentProvider; }
			set { parentProvider = value; }
		}
		
		public virtual SiteMapProvider RootProvider {
			get {
				lock (this_lock) {
					if (rootProviderCache == null) {
						SiteMapProvider current = this;
						while (current.ParentProvider != null)
							current = current.ParentProvider;
						
						rootProviderCache = current;
					}
				}
				return rootProviderCache;
			}
		}
		
		protected SiteMapNode ResolveSiteMapNode (HttpContext context)
		{
			SiteMapResolveEventHandler eh = events [siteMapResolveEvent] as SiteMapResolveEventHandler;

			if (eh != null) {
				lock (resolveLock) {
					if (resolving)
						return null;
					resolving = true;
					SiteMapResolveEventArgs args = new SiteMapResolveEventArgs (context, this);
					SiteMapNode r = eh (this, args);
					resolving = false;
					return r;
				}
			} else
				return null;
		}
		
		public bool EnableLocalization {
			get { return enableLocalization; }
			set { enableLocalization = value; }
		}
		
		public bool SecurityTrimmingEnabled {
			get { return securityTrimming; }
		}

		string resourceKey;
		public string ResourceKey {
			get { return resourceKey; }
			set { resourceKey = value; }
		}

		public virtual SiteMapNode RootNode {
			get {
				SiteMapNode node = GetRootNodeCore ();
				return ReturnNodeIfAccessible (node);
			}
		}

		internal static SiteMapNode ReturnNodeIfAccessible (SiteMapNode node)
		{
			if (node.IsAccessibleToUser (HttpContext.Current))
				return node;
			else
				throw new InvalidOperationException (); /* need
									 * a
									 * message
									 * here */
		}
	}
}


