//// <fragments>
#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.Text;
using System.Text.RegularExpressions;

#endregion

namespace Spring.Util
{
	/// <summary>
	/// Support matching of file system paths in a manner similar to that of the
	/// <see href="http://nant.sourceforge.net">NAnt</see> <c>FileSet</c>.
	/// </summary>
	/// <remarks>
	/// <p>
	/// Any (back)slashes are converted to forward slashes.
	/// </p>
	/// </remarks>
	/// <example>
	/// <code>
	/// // true
	/// PathMatcher.Match("c:/*.bat", @"c:\autoexec.bat");
	/// PathMatcher.Match("c:\fo*\*.bat", @"c:/foobar/autoexec.bat");
	/// PathMatcher.Match("c:\fo?\*.bat", @"c:/foo/autoexec.bat");
	/// // false
	/// PathMatcher.Match("c:\fo?\*.bat", @"c:/fo/autoexec.bat");
	/// </code>
	/// </example>
	/// <author>Federico Spinazzi</author>
	public sealed class PathMatcher
	{
		private const string AllFilesInThisDirectory = "*.*";

		/// <summary>
		/// Determines if a given path matches a <c>NAnt</c>-like pattern.
		/// </summary>
		/// <param name="pattern">
		/// A forward or back-slashed fileset-like pattern.
		/// </param>
		/// <param name="path">A forward or back-slashed full path.</param>
		/// <param name="ignoreCase">should the match consider the case</param>
		/// <returns>
		/// <see langword="true"/> if the path is matched by the pattern;
		/// otherwise <see langword="false"/>.
		/// </returns>
                //// <fragment name="match-method-nocase">
		public static bool Match(string pattern, string path, bool ignoreCase)
	        //// </fragment>
		{
            return Match(ToLower(pattern), ToLower(path));
		}

	    /// <summary>
		/// Determines if a given path matches a <c>NAnt</c>-like pattern.
		/// </summary>
		/// <param name="pattern">
		/// A forward or back-slashed fileset-like pattern.
		/// </param>
		/// <param name="path">A forward or back-slashed full path.</param>
		/// <returns>
		/// <see langword="true"/> if the path is matched by the pattern;
		/// otherwise <see langword="false"/>.
		/// </returns>
                //// <fragment name="match-method">
		public static bool Match(string pattern, string path)
	        //// </fragment>
		{
			pattern = ForwardifySlashes(pattern);
			path = ForwardifySlashes(path);

			if (MatchAll(pattern))
				return true;

			String regex = BuildRegex(pattern);
			return Regex.Match(path, regex).Success;
		}

		/// <summary>
		/// Replaces back(slashes) with forward slashes.
		/// </summary>
		/// <param name="path">
		/// The path or the pattern to modify.
		/// </param>
		/// <returns>A forward-slashed string.</returns>
		public static string ForwardifySlashes(string path)
		{
			return path.Replace("\\", "/");
		}

		/// <summary>
		/// Helper method to convert a <c>NAnt</c>-like pattern into the
		/// appropriate pattern for a regular expression.
		/// </summary>
		/// <param name="pattern">The <c>NAnt</c>-like pattern.</param>
		/// <returns>A regex-compatible pattern.</returns>
		public static String BuildRegex(string pattern)
		{
			if (AllFilesInThisDirectory.Equals(pattern))
			{
				return "^[^/]*$";
			}
			string[] parts = pattern.Split('/');
			int indexOfLastSplittedPart = parts.Length - 1;
			int indexOfTheCurrentSplittedPart = 0;
			StringBuilder regex = new StringBuilder();
			foreach (string currentSplittedPart in parts)
			{
				bool currentPartIsTheLast = indexOfTheCurrentSplittedPart == indexOfLastSplittedPart;
				string partToAdd = currentSplittedPart;
				switch (currentSplittedPart)
				{
					case "**":
						partToAdd = TranslateDoubleAsterisk(currentPartIsTheLast);
						break;
					default:
						partToAdd = TranslateDot(partToAdd);
						partToAdd = TranslateAsterisk(partToAdd);
						partToAdd = TranslateQuestionMark(partToAdd);
						partToAdd = TranslateLiteral(partToAdd, currentPartIsTheLast);
						break;
				}
				regex.Append(partToAdd);
				indexOfTheCurrentSplittedPart++;
			}
			return regex.ToString();
		}

        private static string ToLower(string pattern)
        {
            return pattern == null ? pattern : pattern.ToLower();
        }

        private static string TranslateLiteral(string part, bool isLastPart)
		{
			if (isLastPart)
			{
				return part + "$";
			}
			else
			{
				return part + "/?";
			}
		}

		private static string TranslateDoubleAsterisk(bool isLastPart)
		{
			string slashTerminated = "(.*/?(?<=/))";
			if (isLastPart)
				slashTerminated = String.Format("($|(?<=/){0}*)", slashTerminated);
			return slashTerminated;
		}

		private static string TranslateQuestionMark(string thisPart)
		{
			return thisPart.Replace("?", "[^./]");
		}

		private static string TranslateAsterisk(string thisPart)
		{
			return thisPart.Replace("*", "[^/]*");
		}

		private static string TranslateDot(string thisPart)
		{
			return thisPart.Replace(".", @"\.");
		}

		private static bool MatchAll(string pattern)
		{
			if (AllFilesInThisDirectory.Equals(pattern))
			{
				return false;
			}

			foreach (char c in pattern)
			{
				if (c != '*' && c != '/' && c != '.')
				{
					return false;
				}
			}
			return true;
		}

		#region Constructor (s) / Destructor

		// CLOVER:OFF

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

		// CLOVER:ON

		#endregion
	}
}
//// </fragments>