#region License

/*
 * Copyright © 2002-2005 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.Web;
using System.Web.UI;

#endregion

namespace Spring.Util
{
    /// <summary>
    /// Miscellaneous web utility methods.
    /// </summary>
    /// <author>Aleksandar Seovic</author>
    public sealed class WebUtils
    {
        #region Constructor (s) / Destructor

        // CLOVER:OFF

        /// <summary>
        /// Creates a new instance of the <see cref="Spring.Util.WebUtils"/> class.
        /// </summary>
        /// <remarks>
        /// <p>
        /// This is a utility class, and as such exposes no public constructors.
        /// </p>
        /// </remarks>
        private WebUtils()
        {}

        // CLOVER:ON

        #endregion

        /// <summary>
        /// Default protocol used for resolving resources in web applications
        /// </summary>
        internal static readonly string DEFAULT_RESOURCE_PROTOCOL = "web";

        /// <summary>
        /// Extracts the bare ASPX page name without any extension from the
        /// supplied <paramref name="url"/>.
        /// </summary>
        /// <example>
        /// <p>
        /// Examples of what would be returned from this method given a url would be:
        /// </p>
        /// <p>
        /// <list type="bullet">
        /// <item><description>'Login.aspx' => 'Login'</description></item>
        /// <item><description>'~/Login.aspx' => 'Login'</description></item>
        /// <item><description>'~/B2B/SignUp.aspx' => 'SignUp'</description></item>
        /// <item><description>'B2B/Foo/FooServices.aspx' => 'FooServices'</description></item>
        /// </list>
        /// </p>
        /// </example>
        /// <param name="url">The full URL to the ASPX page.</param>
        /// <returns>
        /// The bare ASPX page name without any extension.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">
        /// If the supplied <paramref name="url"/> is <see langword="null"/> or
        /// contains only whitespace character(s).
        /// </exception>
        public static string GetPageName(string url)
        {
            AssertUtils.ArgumentHasText(url, "url");
            int lastSlash = url.LastIndexOf('/');
            int lastDot = url.LastIndexOf('.');
            if (lastDot < lastSlash)
            {
                lastDot = -1;
            }
            if (lastDot < 0)
            {
                int length = url.Length - lastSlash - 1;
                return url.Substring(lastSlash + 1, length);
            }

            return url.Substring(lastSlash + 1, lastDot - lastSlash - 1);
        }

        /// <summary>
        /// Returns only the directory portion of a virtual path
        /// </summary>
        /// <remarks>
        /// The returned path is guaranteed to always have a leading and a trailing slash.<br/>
        /// If a path does not end with a file-extension it is assumed to be a directory
        /// </remarks>
        public static string GetVirtualDirectory(string virtualPath)
        {
            AssertUtils.ArgumentNotNull(virtualPath, "virtualPath");

            if (virtualPath.Length == 0)
            {
                return "/";
            }
            
            if (virtualPath[0] != '/')
            {
                virtualPath = "/" + virtualPath;
            }

            if (virtualPath[virtualPath.Length - 1] != '/')
            {
                int iDot = virtualPath.LastIndexOf('.');
                int iSlash = virtualPath.LastIndexOf('/');
                if (iDot < iSlash) iDot = -1;
                if (iDot > -1)
                {
                    virtualPath = virtualPath.Substring(0, iSlash + 1);
                }
                else
                {
                    virtualPath = virtualPath + "/";
                }
            }
            return virtualPath;
        }

        /// <summary>
        /// Returns absolute path that can be referenced within plain HTML.
        /// </summary>
        /// <remarks>
        /// <p>
        /// If relative path starts with '/' (forward slash), no concatenation will occur
        /// and it will be assumed that the relative path specified is indeed the absolute path
        /// and will be returned verbatim.</p>
        /// <p>
        /// Otherwise, relative path will be appended to the application path, while making sure that 
        /// path separators are not duplicated between the paths.</p>
        /// </remarks>
        /// <param name="applicationPath">Application path.</param>
        /// <param name="relativePath">Relative path to combine with the application path.</param>
        /// <returns>Absolute path.</returns>
        public static string CreateAbsolutePath(string applicationPath, string relativePath)
        {
            if (StringUtils.HasLength(relativePath))
            {
                if (relativePath.StartsWith("/"))
                {
                    return relativePath;
                }

                if (relativePath.StartsWith("~/"))
                {
                    relativePath = relativePath.Substring(2);
                }
            }

            applicationPath = (applicationPath == null) ? "" : applicationPath.TrimEnd('/');

            return string.Format("{0}/{1}", applicationPath, relativePath);
        }

        /// <summary>
        /// Combines a rooted base path with a relative path.
        /// </summary>
        /// <param name="rootPath">Must be a path starting with '/'</param>
        /// <param name="relativePath">the path to be combined. May start with basepath Placeholder '~'</param>
        /// <returns>the combined path</returns>
        /// <remarks>
        /// If relativePath starts with '~', rootPath is ignored and '~' resolves to the current AppDomain's application virtual path<br/>
        /// If relativePath start with '/', rootPath is ignored and relativePath is returned as-is.
        /// </remarks>
        public static string CombineVirtualPaths(string rootPath, string relativePath)
        {
            AssertUtils.ArgumentHasText(rootPath, "rootPath");
            if (rootPath[0] != '/')
            {
                throw new ArgumentException("RootPath must start with '/'", "rootPath");
            }

            string combinedPath = relativePath;

            if (combinedPath.StartsWith("~/"))
            {
                combinedPath = VirtualEnvironment.ApplicationVirtualPath.TrimEnd('/') + relativePath.Substring(1);
            }

            if (!combinedPath.StartsWith("/"))
            {
                combinedPath = GetVirtualDirectory(rootPath).TrimEnd('/') + '/' + relativePath;
            }

            // TODO: reduce contained directory upwalks here

            return combinedPath;
        }

        /// <summary>
        /// Gets the application-relative virtual path portion of the given absolute URL.
        /// </summary>
        /// <param name="url">the absolute url</param>
        /// <returns>the url relative to the current application's virtual path</returns>
        public static string GetAppRelativePath(string url)
        {
            string appPath = VirtualEnvironment.ApplicationVirtualPath;
            return GetRelativePath(appPath, url);
        }

        /// <summary>
        /// Gets the virtual path portion of the given absolute URL 
        /// relative to the given base path.
        /// </summary>
        /// <remarks>
        /// Base path comparison is done case insensitive.
        /// </remarks>
        /// <param name="basePath">the absolute base path</param>
        /// <param name="url">the absolute url</param>
        /// <returns>the url relative to the given basePath</returns>
        public static string GetRelativePath(string basePath, string url)
        {
            // strip application path from url
            string appPath = basePath.TrimEnd('/');
            string appRelativeVirtualPath = url;
            if (appRelativeVirtualPath.ToLower().StartsWith(appPath.ToLower()))
            {
                appRelativeVirtualPath = appRelativeVirtualPath.Substring(appPath.Length);
            }
            return appRelativeVirtualPath;            
        }

        ///<summary>
        /// Returns the 'logical' parent of the specified control. Technically when dealing with masterpages and control hierarchy,
        /// the order goes controls-&gt;masterpage-&gt;page. But one often wants the more logical order controls-&gt;page-&gt;masterpage.
        ///</summary>
        ///<param name="control">the control, who's parent is to be determined.</param>
        ///<returns>the logical parent or <c>null</c> if the top of the hierarchy is reached.</returns>
        /// <exception cref="ArgumentNullException">if <paramref name="control"/> is <c>null</c></exception>
        public static Control GetLogicalParent(Control control)
        {
            AssertUtils.ArgumentNotNull(control, "control");

            // to determine "correct" order of bubbling control->page->masterpage, 
            // the trick below is necessary because technically the hierarchy goes 
            // control->masterpage->page
            if (control is System.Web.UI.Page)
            {
#if !NET_2_0
                control = ((Spring.Web.UI.Page)control).Master;
#else
                control = ((System.Web.UI.Page)control).Master;
#endif
            }
            else if (IsMaster(control))
            {
                control = null;
            }
            else if (IsMaster(control.Parent))
            {
                control = control.Page;  
            }
            else
            {
                control = control.Parent;
            }
            return control;
        }

        private static bool IsMaster(Control control)
        {
#if !NET_2_0
            return (control is Spring.Web.UI.MasterPage);
#else
            return (control is System.Web.UI.MasterPage);
#endif            
        }

        /// <summary>
        /// Encode <paramref name="value"/> for use in URLs.
        /// </summary>
        /// <param name="value">the text to be encoded.</param>
        /// <returns>the url-encoded <paramref name="value"/></returns>
        /// <remarks>
        /// This method may be used outside of a current request. If executed within a 
        /// request, <see cref="HttpServerUtility.UrlEncode(string)"/> is used.
        /// <see cref="HttpUtility.UrlEncode(string)"/> will be used otherwise.
        /// </remarks>
        public static string UrlEncode( string value )
        {
            HttpContext ctx = HttpContext.Current;
            return (ctx == null) ? HttpUtility.UrlEncode( value ) : ctx.Server.UrlEncode( value );
        }
    }
}