using System;
using System.Text.RegularExpressions;

namespace Orciid.Core
{
	/// <summary>
	/// Interpret date strings
	/// </summary>
	/// <remarks>
	/// This class provides functionality to interpret a string representing a date or a period
	/// represented by a start date and an end date. The result is an integer.
	/// Currently the following formats are supported:
	/// <list type="bullet">
	/// <item>ISO 8601:2000(E), sections 5.2.1.1, .2 a) b), .4 a) b) c), 5.4.1 a) b), 5.5.1 a), 5.5.4.1</item>
	/// <item>Default US date format in the form MM/DD/YYYY, periods in the form MM/DD/YYYY-MM/DD/YYYY</item>
	/// <item>A simple year-only format, periods in the form YYYY-YYYY</item>
	/// </list>
	/// For the purpose of searching records, only the year part of a date is stored and searched.
	/// </remarks>
	public abstract class DateInterpreter
	{
		/// <summary>
		/// Date format
		/// </summary>
		/// <remarks>
		/// When a date is passed to be interpreted, a format has to be specified to allow 
		/// interpretation.
		/// </remarks>
		public enum Format: 
			int
		{
			/// <summary>
			/// ISO 8601 format
			/// </summary>
			/// <remarks>
			/// Date is expressed in full precision ISO 8601 format
			/// </remarks>
			ISO8601 = 1,
			/// <summary>
			/// ISO 8601 format
			/// </summary>
			/// <remarks>
			/// Date is expressed in reduced precision ISO 8601 format
			/// </remarks>
			ISO8601ReducedPrecision = 2,
			/// <summary>
			/// US default
			/// </summary>
			/// <remarks>
			/// Date is expressed in US default format MM/DD/YYYY[-MM/DD/YYYY]
			/// </remarks>
			USDefault = 3,
			/// <summary>
			/// Years only
			/// </summary>
			/// <remarks>
			/// Date is expressed as years only [-]YYYY[-[-]YYYY]
			/// </remarks>
			YearOnly = 4
		}

		/// <summary>
		/// Array of existing date formats
		/// </summary>
		/// <remarks>
		/// When a date is passed to be interpreted, a format has to be specified to allow 
		/// interpretation.  This array contains all available date formats.
		/// </remarks>
		public static Format[] Formats = new Format[]
			{ Format.ISO8601, Format.ISO8601ReducedPrecision, Format.USDefault, Format.YearOnly };
		
		/// <summary>
		/// Regular expressions for supported date formats
		/// </summary>
		/// <remarks>
		/// This array holds regular expressions to be used to determine start dates. Every
		/// regular expression can have three named groups:
		/// <list type="bullet">
		/// <item>'y': Year (required)</item>
		/// <item>'m': Month (optional)</item>
		/// <item>'d': Day (optional)</item>
		/// </list>
		/// </remarks>
		private static Regex[] startex = 
		{
			// ISO 8601:2000(E) date
			new Regex(@"^(?'y'([-+][0-9]*)?[0-9]{4})(?'dash'-?)(?'m'[0-9]{2})\k'dash'(?'d'[0-9]{2})(T\S+)?(/\S+)?$"),
			// ISO 8601:2000(E) date, reduced precision
			new Regex(@"^(?'y'([-+][0-9]*)?[0-9]{4})(-(?'m'[0-9]{2})(-(?'d'[0-9]{2}))?)?(T\S+)?(/\S+)?$"),
			// default U.S. date format
			new Regex(@"^(?'m'[0-9]{1,2})/(?'d'[0-9]{1,2})/(?'y'[0-9]{4})(-[0-9]{1,2}/[0-9]{1,2}/[0-9]{4})?$"),
			// simple year ranges
			new Regex(@"^(?'y'-?[0-9]+)(--?[0-9]+)?$")
		};

		/// <summary>
		/// Regular expressions for supported date formats
		/// </summary>
		/// <remarks>
		/// This array holds regular expressions to be used to determine end dates in strings
		/// representing periods of time. Every regular expression can have three named groups:
		/// <list type="bullet">
		/// <item>'y': Year (required)</item>
		/// <item>'m': Month (optional)</item>
		/// <item>'d': Day (optional)</item>
		/// </list>
		/// </remarks>
		private static Regex[] endex = 
		{
			// ISO 8601:2000(E) date
			new Regex(@"^\S+/(?'y'([-+][0-9]*)?[0-9]{4})(?'dash'-?)(?'m'[0-9]{2})\k'dash'(?'d'[0-9]{2})(T\S+)?$"),
			// ISO 8601:2000(E) date, reduced precision
			new Regex(@"^\S+/(?'y'([-+][0-9]*)?[0-9]{4})(-(?'m'[0-9]{2})(-(?'d'[0-9]{2}))?)?(T\S+)?$"),
			// default U.S. date format
			new Regex(@"^[0-9]{1,2}/[0-9]{1,2}/[0-9]{4}-(?'m'[0-9]{1,2})/(?'d'[0-9]{1,2})/(?'y'[0-9]{4})$"),
			// simple year ranges
			new Regex(@"^-?[0-9]+-(?'y'-?[0-9]+)$")
		};
		
		/// <summary>
		/// Interpret individual date in given format
		/// </summary>
		/// <remarks>
		/// Interprets a string holding a date or period in one of the supported formats and returns
		/// an integer representation of the date or dates
		/// </remarks>
		/// <param name="date">A string representing a date or a period (start and end date)</param>
		/// <param name="format">The format the date parameter is in</param>
		/// <param name="start">A reference to the integer variable which will hold the start year</param>
		/// <param name="end">A reference to the integer variable which will hold the end year</param>
		/// <returns><c>true</c> if the date could be interpreted: the start year and the end year are set. For
		/// single dates both are the same. If the end year is less than the start year in the date 
		/// parameter, they are switched, so start is always less or equal end. If the date cannot
		/// be interpreted, <c>false</c> is returned.</returns>
		public static bool Interpret(string date, Format format, out int start, out int end)
		{
			start = int.MinValue;
			end = int.MinValue;
			if (date != null)
			{
				Match m = startex[(int)format - 1].Match(date);
				if (!m.Success) return false;
				start = Int32.Parse(m.Groups["y"].Value);
				m = endex[(int)format - 1].Match(date);
				if (m.Success)
				{
					end = Int32.Parse(m.Groups["y"].Value);
					if (start > end) 
					{
						int temp = start;
						start = end;
						end = temp;
					}
				}
				else
					end = start;
				return true;
			}
			return false;
		}
	}
}
