using System;
using System.Collections.Generic;

namespace InABox.Core
{
    /// <summary>
    ///     Custom string formatter for TimeSpan that allows easy retrieval of Total segments.
    /// </summary>
    /// <example>
    ///     TimeSpan myTimeSpan = new TimeSpan(27, 13, 5);
    ///     string.Format("{0:th,###}h {0:mm}m {0:ss}s", myTimeSpan) -> "27h 13m 05s"
    ///     string.Format("{0:TH}", myTimeSpan) -> "27.2180555555556"
    ///     NOTE: myTimeSpan.ToString("TH") does not work.  See Remarks.
    /// </example>
    /// <remarks>
    ///     Due to a quirk of .NET Framework (up through version 4.5.1),
    ///     <code>TimeSpan.ToString(format, new TimeSpanFormatter())</code> will not work; it will always call
    ///     TimeSpanFormat.FormatCustomized() which takes a DateTimeFormatInfo rather than an
    ///     IFormatProvider/ICustomFormatter.  DateTimeFormatInfo, unfortunately, is a sealed class.
    /// </remarks>
    public class TimeSpanFormatter : IFormatProvider, ICustomFormatter
    {
        /// <summary>
        ///     Used to create a wrapper format string with the specified format.
        /// </summary>
        private const string DefaultFormat = "{{0:{0}}}";

        /// <summary>
        ///     Determines whether the specified format is looking for a total, and formats it accordingly.
        ///     If not, returns the default format for the given
        ///     <para>format</para>
        ///     of a TimeSpan.
        /// </summary>
        /// <returns>
        ///     The formatted string for the given TimeSpan.
        /// </returns>
        /// <remarks>
        ///     ICustomFormatter.Format implementation.
        /// </remarks>
        public string Format(string format, object arg, IFormatProvider formatProvider)
        {
            // only apply our format if there is a format and if the argument is a TimeSpan
            if (string.IsNullOrWhiteSpace(format) ||
                formatProvider != this || // this should always be true, but just in case...
                !(arg is TimeSpan) ||
                arg == null)
                // return the default for whatever our format and argument are
                return GetDefault(format, arg);

            var span = (TimeSpan)arg;

            var formatSegments = format.Split(new[] { ',' }, 2);
            var tsFormat = formatSegments[0].ToLower();
            if (!string.IsNullOrWhiteSpace(tsFormat))
            {
                var results = new List<string>();
                var segments = tsFormat.Replace("\\", "").Split(':');
                try
                {
                    foreach (var segment in segments)
                        if (segment.Equals("d"))
                            results.Add(span.Days.ToString());
                        else if (segment.Equals("dd"))
                            results.Add(span.Days.ToString("D2"));
                        else if (segment.Equals("h"))
                            results.Add(span.Hours.ToString());
                        else if (segment.Equals("hh"))
                            results.Add(span.Hours.ToString("D2"));
                        else if (segment.Equals("hhh"))
                            results.Add(((int)span.TotalHours).ToString());
                        else if (segment.Equals("mm"))
                            results.Add(span.Minutes.ToString("D2"));
                        else if (segment.Equals("ss"))
                            results.Add(span.Seconds.ToString("D2"));
                        else if (segment.Equals("zzz"))
                            results.Add(span.Milliseconds.ToString("D3"));
                    return string.Join(":", results);
                }
                catch
                {
                    return GetDefault(format, arg);
                }
            }

            return span.ToString();
        }

        /// <remarks>
        ///     IFormatProvider.GetFormat implementation.
        /// </remarks>
        public object GetFormat(Type formatType)
        {
            // Determine whether custom formatting object is requested. 
            if (formatType == typeof(ICustomFormatter)) return this;

            return null;
        }

        /// <summary>
        ///     Returns the formatted value when we don't know what to do with their specified format.
        /// </summary>
        private string GetDefault(string format, object arg)
        {
            return string.Format(string.Format(DefaultFormat, format), arg);
        }
    }
}