using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Security.Cryptography;
using System.IO;
using System.Globalization;
using System.Text.RegularExpressions;

namespace CssSpriteGenerator
{
    public class UrlUtils
    {
        private static bool HaveSameHost(Uri uri1, Uri uri2)
        {
            bool urlIsExtern =
                (uri1.Host == uri2.Host) ||
                (uri1.Port == uri2.Port);

            return urlIsExtern;
        }

        /// <summary>
        /// Generates a hash over a string.
        /// 
        /// You would do this essentially when the string is long and you want a shorter string
        /// that will likely be different for each input string.
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        public static string StringHash(string s)
        {
            // Create hash
            MD5 md5 = MD5.Create();
            byte[] urlsHashBytes = md5.ComputeHash(Encoding.ASCII.GetBytes(s));

            string stringHash = BitConverter.ToString(urlsHashBytes);
            return stringHash;
        }

        /// <summary>
        /// Maps a url on a file path on the file system.
        /// Handles absolute urls while running under Cassini.
        /// 
        /// If the file sits on an external domain, this method will assume it is not on the server.
        /// TODO:
        /// In most cases this is correct, but you could have a situation where the images sit on
        /// a cookieless domain that points to the main domain - so the files are actually on the server.
        /// Solution would be to allow user to create a "white" list of cookieless domain in use in the web.config 
        /// configuration and pass that to this method.
        /// 
        /// This method assumes that the url is that of a static file and removes query strings.
        /// It removes any query string from the url, just in case the user has used a query string to force
        /// the browser to reload the file, for example if it is a new version. Specifically, it assumes that the
        /// query string has no influence on the actual contents that goes to the browser when the file is served.
        /// It removes the query string, because HttpServerUtility.MapPath can't handle query strings.
        /// </summary>
        /// <param name="url"></param>
        /// <param name="throwExceptionOnMissingFile">
        /// If true, a FileNotFoundException will be thrown if the file doesn't exist.
        /// If false, the method will not check wether the file exists.
        /// </param>
        /// <returns>
        /// The file path. Null if the file path somehow couldn't be generated, or if the file doesn't exist.
        /// </returns>
        public static string MapPath(string url, bool throwExceptionOnMissingFile)
        {
            return MapPath(url, throwExceptionOnMissingFile, HttpContext.Current.Request.Url);
        }

        public static string MapPath(string url, bool throwExceptionOnMissingFile, Uri baseUri)
        {
            HttpContext currentContext = HttpContext.Current;
            HttpServerUtility hsu = currentContext.Server;
            string filePath = null;

            try
            {
                try
                {
                    Uri absoluteUri = new Uri(baseUri, url);

                    // If the url is extern, return null now.
                    if (!HaveSameHost(absoluteUri, baseUri))
                    {
                        return null;
                    }

                    string absoluteUrlPath = absoluteUri.AbsolutePath;

                    // The url may contain for example %20 to indicate a space in the image path.
                    string decodedAbsoluteUrlPath = hsu.UrlDecode(absoluteUrlPath);
                    filePath = hsu.MapPath(decodedAbsoluteUrlPath);
                }
                catch (InvalidOperationException)
                {
                    // You get an InvalidOperationException if the url is absolute (starts with /)
                    // and the site runs under Cassini. In that case, the absolute url would denote a 
                    // path outside the site's root folder.
                    //
                    // Don't check on this beforehand, because that would take CPU cycles in the production site,
                    // while CPU cycles in development are cheap, so extra exceptions are no problem.

                    if (url.StartsWith("/"))
                    {
                        string urlTilde = "~" + url;
                        string resolvedUrl = VirtualPathUtility.ToAbsolute(urlTilde);
                        filePath = hsu.MapPath(resolvedUrl);
                    }
                    else
                    {
                        // re-throw the same exception
                        throw;
                    }
                }
            }
            catch (Exception e)
            {
                if (throwExceptionOnMissingFile)
                {
                    throw new FileNotFoundException(
                        string.Format("Url \"{0}\" cannot be mapped to file path. See inner exception.", url), url, e);
                }
            }

            if (filePath == null) { return null; }

            if (throwExceptionOnMissingFile)
            {
                // Check if file exists

                if (!File.Exists(filePath))
                {
                    throw new FileNotFoundException(
                        string.Format("File path {0} was derived from url {1}, but no file exists at {0}", filePath, url), filePath);
                }
            }

            return filePath;
        }


        /// <summary>
        /// Takes the file path of an image and returns its image type.
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public static ImageType ImageTypeUrl(string filePath)
        {
            int idxExtension = filePath.LastIndexOf('.');

            if (idxExtension == -1)
            {
                throw new Exception("ImageTypeUrl - cannot find extension in: " + filePath);
            }

            string threeLetterExtension = SafeSubstring(filePath, idxExtension, 4);

            if (string.Compare(threeLetterExtension, ".gif", true, CultureInfo.InvariantCulture) == 0)
                return ImageType.Gif;
            else if (string.Compare(threeLetterExtension, ".jpg", true, CultureInfo.InvariantCulture) == 0)
                return ImageType.Jpg;
            else if (string.Compare(threeLetterExtension, ".png", true, CultureInfo.InvariantCulture) == 0)
                return ImageType.Png;

            // If we get here, return unknown image type
            return ImageType.Null;
        }

        /// <summary>
        /// Returns the extension based on the image type.
        /// </summary>
        /// <param name="imageType"></param>
        /// <returns></returns>
        public static string ExtensionForImageType(ImageType imageType)
        {
            string extension = null;

            switch (imageType)
            {
                case ImageType.Gif:
                    extension = ".gif";
                    break;

                case ImageType.Jpg:
                    extension = ".jpg";
                    break;

                case ImageType.Png:
                    extension = ".png";
                    break;

                default:
                    throw new Exception("ExtensionForImageType - unknown file type: " + imageType.ToString());
            }

            return extension;
        }

        /// <summary>
        /// Returns true if the file path is that of a static image.
        /// A static image is a .gif, .png or .jpg.
        /// 
        /// A dynamic image would be for example .aspx
        /// </summary>
        /// <param name="filePath"></param>
        public static bool IsStaticFileType(string filePath)
        {
            ImageType imageType = ImageTypeUrl(filePath);

            bool isStaticFileType =
                (imageType == ImageType.Gif) ||
                (imageType == ImageType.Jpg) ||
                (imageType == ImageType.Png);

            return isStaticFileType;
        }


        /// <summary>
        /// Safe version of Substring. 
        /// If startIndex is outside the string, returns empty string.
        /// If length is too long, returns whatever characters are available from startIndex.
        /// </summary>
        /// <param name="s"></param>
        /// <param name="startIndex"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        public static string SafeSubstring(string s, int startIndex, int length)
        {
            try
            {
                return s.Substring(startIndex, length);
            }
            catch (Exception e)
            {
                if ((startIndex < 0) || (length < 0))
                {
                    throw new Exception("SafeSubstring - startIndex: " + startIndex.ToString() + ", length: " + length.ToString(), e);
                }

                // If exception happened because startIndex outside the string, return empty string.
                if (startIndex >= s.Length) { return ""; }

                // If the startIndex was inside the string, exception happened because length was too long.
                // Just return whatever characters are available.
                return s.Substring(startIndex);
            }
        }

        /// <summary>
        /// Escapes a string for regular expressions.
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        public static string EscapedForRegex(string s)
        {
            return Regex.Escape(s).Replace(":", @"\:");
        }
    }
}
