using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace Kaleida.ServiceMonitor.Framework
{
internal class DurationParser
{
private static readonly IList<DurationParser> parsers = new[]
{
new DurationParser("day", "days", new[]{"d"}, TimeSpan.FromDays),
new DurationParser("hr", "hrs", new[]{"h", "hour", "hours"}, TimeSpan.FromHours),
new DurationParser("min", "mins", new[]{"m", "minute", "minutes"}, TimeSpan.FromMinutes),
new DurationParser("sec", "secs", new[]{"s", "second", "seconds"}, TimeSpan.FromSeconds),
new DurationParser("ms", "ms", new[]{"ms", "millisecond", "milliseconds", "milli second", "milli seconds", "milli-second", "milli-seconds", ""}, TimeSpan.FromMilliseconds)
};
private readonly IList<Regex> patterns;
private readonly string canonicalSuffix;
private readonly string pluralCanonicalSuffix;
private readonly IEnumerable<string> permittedAlternativeSuffices;
private readonly Func<double, TimeSpan> converter;
public static bool CanParse(string text)
{
return parsers.Any(i => i.CanAccept(text));
}
public static TimeSpan ParseTimeSpan(string text)
{
return GetSpecificParser(text).BuildTimeSpan(text);
}
public static Duration ParseDuration(string text)
{
return GetSpecificParser(text).BuildDuration(text);
}
private static DurationParser GetSpecificParser(string text)
{
var parser = (from i in parsers
where i.CanAccept(text)
select i).FirstOrDefault();
if (parser == null)
{
var supportedSuffices = parsers.Select(i => i.CanonicalSuffix);
string validSuffices = string.Join(", ", supportedSuffices.Where(i => i.Trim() != ""));
throw new FormatException(string.Format("Cannot parse '{0}' as a duration. The following suffices may be used: {1}", text, validSuffices));
}
return parser;
}
public DurationParser(string canonicalSuffix, string pluralCanonicalSuffix, IList<string> permittedAlternativeSuffices, Func<double, TimeSpan> converter)
{
this.canonicalSuffix = canonicalSuffix;
this.pluralCanonicalSuffix = pluralCanonicalSuffix;
this.permittedAlternativeSuffices = permittedAlternativeSuffices;
this.converter = converter;
patterns = SupportedSuffices.Select(i => new Regex(@"^\s*(-?[0-9.,]+)\s*" + i + "$", RegexOptions.Compiled)).ToList();
}
public bool CanAccept(string text)
{
var preparedText = TrimAndLowercase(text);
return patterns.Any(i=>i.IsMatch(preparedText));
}
public Duration BuildDuration(string text)
{
var value = GetValue(text);
var suffix = GetSuffix(value);
return new Duration(value, suffix);
}
private string GetSuffix(double value)
{
const double epsilon = 0.00001;
var isSingular = Math.Abs(Math.Abs(value) - 1) < epsilon;
var suffix = isSingular ? canonicalSuffix : pluralCanonicalSuffix;
return suffix;
}
public TimeSpan BuildTimeSpan(string text)
{
var value = GetValue(text);
return converter(value);
}
public IEnumerable<string> SupportedSuffices
{
get { return new[] {canonicalSuffix, pluralCanonicalSuffix}.Concat(permittedAlternativeSuffices); }
}
public string CanonicalSuffix
{
get { return canonicalSuffix; }
}
private double GetValue(string text)
{
var preparedText = TrimAndLowercase(text);
var regex = patterns.FirstOrDefault(i => i.IsMatch(preparedText));
if(regex == null)
throw new InvalidOperationException(string.Format("Cannot parse '{0}' with the {1} DurationParser", text, canonicalSuffix));
var match = regex.Match(preparedText);
if (!match.Success || match.Groups.Count != 2)
throw new InvalidOperationException(String.Format("Expected '{0}' to product 2 groups using pattern '{1}'", text, regex));
double value;
if (!Double.TryParse(match.Groups[1].Value, out value))
throw new InvalidOperationException(String.Format("Couldn't convert '{0}' to a double", match.Groups[1].Value));
return value;
}
private static string TrimAndLowercase(string text)
{
return text.Trim().ToLowerInvariant();
}
}
}