using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace MusicMetaTagger.Core.Utility
{
	public static class StringUtility
	{
		private static readonly Regex ParenthesesRegex = new Regex(
			@"[\(\[]"
			+ @"    (?>"
			+ @"        [^()\[\]]+ "
			+ @"        |    [\(\[] (?"
			+ @"<number>)"
			+ @"        |    [\)\]] (?<-number>)"
			+ @"    )*"
			+ @"    (?("
			+ @"number)(?!))"
			+ @"[\)\]]",
			RegexOptions.IgnoreCase
			| RegexOptions.CultureInvariant
			| RegexOptions.IgnorePatternWhitespace
			| RegexOptions.Compiled
			);

		private static readonly Regex SubtitleRegex = new Regex(
			@"(:|/|&).*",
			RegexOptions.IgnoreCase
			| RegexOptions.CultureInvariant
			| RegexOptions.IgnorePatternWhitespace
			| RegexOptions.Compiled
			);

		/// <summary>
		///   Any character that is not in this class: [a-zA-Z0-9], one or more repetitions
		/// </summary>
		private static readonly Regex NonAlphaNumericRegex = new Regex(
			@"[^a-zA-Z0-9]+",
			RegexOptions.IgnoreCase
			| RegexOptions.CultureInvariant
			| RegexOptions.IgnorePatternWhitespace
			| RegexOptions.Compiled
			);

		private static readonly Func<string, string>[] StringCleaners =
		{
			s => s.Replace(@"/", "-"),
			s => ParenthesesRegex.Replace(s, String.Empty),
			s => SubtitleRegex.Replace(s, String.Empty),
			s => NonAlphaNumericRegex.Replace(s, String.Empty)
		};

		/// <summary>
		/// Iteratively cleans string until given function returns results
		/// </summary>
		public static IEnumerable<TResult> ProgressiveCleaner<TResult>(Func<string, IEnumerable<TResult>> getResults, string str)
		{
			return
				StringCleaners
					.Select((func, count) =>
					        StringCleaners // progressively take more cleaners and apply to string
					        	.Take(count + 1).Aggregate(str, (s, f) => f(s)))
					.Where(s => !string.IsNullOrEmpty(s))
					.Select(getResults) // stop when we get results
					.FirstOrDefault(results => results.Any()) ?? new TResult[0];
		}

		public static string CleanId(string idStr)
		{
			return idStr.Replace(" ", "").ToLower();
		}

		/// <summary>
		///   Determines whether the specified number string is numeric.
		/// </summary>
		/// <param name="numberString"> The number string. </param>
		/// <returns> <c>true</c> if the specified number string is numeric; otherwise, <c>false</c> . </returns>
		public static bool IsNumeric(string numberString)
		{
			var ca = numberString.ToCharArray();
			return ca.All(Char.IsNumber);
		}

		private static readonly Regex TimeRegex
			= new Regex(@"(?<Minutes>\d*):(?<Seconds>\d{2})|(?<Seconds>\d{2})");

		/// <summary>
		///   Converts a string in mm:ss format to a TimeSpan object.
		/// </summary>
		/// <param name="timeString"> string in mm:ss format </param>
		/// <returns> TimeSpan object </returns>
		public static TimeSpan ConvertTimeSpan(string timeString)
		{
			if (String.IsNullOrEmpty(timeString))
				return new TimeSpan();

			var match = TimeRegex.Match(timeString);
			var minutes = "0";
			if (match.Groups["Minutes"].Captures.Count > 0
			    && match.Groups["Minutes"].Captures[0].Value.Length > 0)
			{
				minutes = match.Groups["Minutes"].Captures[0].Value;
			}
			var seconds = match.Groups["Seconds"].Captures[0].Value;

			return new TimeSpan(0, 0, Convert.ToInt32(minutes), Convert.ToInt32(seconds));
		}

		/// <summary>
		///   Returns score for the similarity between two strings
		/// </summary>
		/// <returns> Score between 0 and 1, with 1 being exact match </returns>
		public static double Similarity(string s1, string s2)
		{
			if (String.IsNullOrEmpty(s1))
				throw new ArgumentNullException("s1");
			if (String.IsNullOrEmpty(s2))
				throw new ArgumentNullException("s2");

			var distance = LevenshteinDistance.Compute(s1, s2);
			var similarity = 1.0 - 1.0*distance/Math.Max(s1.Length, s2.Length);
			if (ContainsString(s1, s2))
				similarity = similarity + (1.0 - similarity)/2;
			return similarity;
		}

		/// <summary>
		///   Determines whether STR1 contains STR2, or vice versa.
		/// </summary>
		public static bool ContainsString(string str1, string str2)
		{
			return str1.ToUpper().Contains(str2.ToUpper()) || str2.ToUpper().Contains(str1.ToUpper());
		}

		public static string ConcatToString(string original, string additional)
		{
			if (!String.IsNullOrEmpty(original))
				return original + ", " + additional;
			return additional;
		}
	}
}