using System;
using Csla.Properties;
namespace Csla
{
  /// 
  /// Provides a date data type that understands the concept
  /// of an empty date value.
  /// 
  /// 
  /// See Chapter 5 for a full discussion of the need for this
  /// data type and the design choices behind it.
  /// 
  [Serializable()]
  public struct SmartDate : IComparable
  {
    private DateTime _date;
    private bool _initialized;
    private EmptyValue _emptyValue;
    private string _format;
    private static string _defaultFormat;
    #region EmptyValue enum
    /// 
    /// Indicates the empty value of a
    /// SmartDate.
    /// 
    public enum EmptyValue
    {
      /// 
      /// Indicates that an empty SmartDate
      /// is the smallest date.
      /// 
      MinDate,
      /// 
      /// Indicates that an empty SmartDate
      /// is the largest date.
      /// 
      MaxDate
    }
    #endregion
    #region Constructors
    static SmartDate()
    {
      _defaultFormat = "d";
    }
    /// 
    /// Creates a new SmartDate object.
    /// 
    /// Indicates whether an empty date is the min or max date value.
    public SmartDate(bool emptyIsMin)
    {
      _emptyValue = GetEmptyValue(emptyIsMin);
      _format = null;
      _initialized = false;
      // provide a dummy value to allow real initialization
      _date = DateTime.MinValue;
      SetEmptyDate(_emptyValue);
    }
    /// 
    /// Creates a new SmartDate object.
    /// 
    /// Indicates whether an empty date is the min or max date value.
    public SmartDate(EmptyValue emptyValue)
    {
      _emptyValue = emptyValue;
      _format = null;
      _initialized = false;
      // provide a dummy value to allow real initialization
      _date = DateTime.MinValue;
      SetEmptyDate(_emptyValue);
    }
    /// 
    /// Creates a new SmartDate object.
    /// 
    /// 
    /// The SmartDate created will use the min possible
    /// date to represent an empty date.
    /// 
    /// The initial value of the object.
    public SmartDate(DateTime value)
    {
      _emptyValue = Csla.SmartDate.EmptyValue.MinDate;
      _format = null;
      _initialized = false;
      _date = DateTime.MinValue;
      Date = value;
    }
    /// 
    /// Creates a new SmartDate object.
    /// 
    /// The initial value of the object.
    /// Indicates whether an empty date is the min or max date value.
    public SmartDate(DateTime value, bool emptyIsMin)
    {
      _emptyValue = GetEmptyValue(emptyIsMin);
      _format = null;
      _initialized = false;
      _date = DateTime.MinValue;
      Date = value;
    }
    /// 
    /// Creates a new SmartDate object.
    /// 
    /// The initial value of the object.
    /// Indicates whether an empty date is the min or max date value.
    public SmartDate(DateTime value, EmptyValue emptyValue)
    {
      _emptyValue = emptyValue;
      _format = null;
      _initialized = false;
      _date = DateTime.MinValue;
      Date = value;
    }
    /// 
    /// Creates a new SmartDate object.
    /// 
    /// 
    /// The SmartDate created will use the min possible
    /// date to represent an empty date.
    /// 
    /// The initial value of the object (as text).
    public SmartDate(string value)
    {
      _emptyValue = EmptyValue.MinDate;
      _format = null;
      _initialized = true;
      _date = DateTime.MinValue;
      this.Text = value;
    }
    /// 
    /// Creates a new SmartDate object.
    /// 
    /// The initial value of the object (as text).
    /// Indicates whether an empty date is the min or max date value.
    public SmartDate(string value, bool emptyIsMin)
    {
      _emptyValue = GetEmptyValue(emptyIsMin);
      _format = null;
      _initialized = true;
      _date = DateTime.MinValue;
      this.Text = value;
    }
    /// 
    /// Creates a new SmartDate object.
    /// 
    /// The initial value of the object (as text).
    /// Indicates whether an empty date is the min or max date value.
    public SmartDate(string value, EmptyValue emptyValue)
    {
      _emptyValue = emptyValue;
      _format = null;
      _initialized = true;
      _date = DateTime.MinValue;
      this.Text = value;
    }
    private static EmptyValue GetEmptyValue(bool emptyIsMin)
    {
      if (emptyIsMin)
        return EmptyValue.MinDate;
      else
        return EmptyValue.MaxDate;
    }
    private void SetEmptyDate(EmptyValue emptyValue)
    {
      if (emptyValue == SmartDate.EmptyValue.MinDate)
        this.Date = DateTime.MinValue;
      else
        this.Date = DateTime.MaxValue;
    }
    #endregion
    #region Text Support
    /// 
    /// Sets the global default format string used by all new
    /// SmartDate values going forward.
    /// 
    /// 
    /// The default global format string is "d" unless this
    /// method is called to change that value. Existing SmartDate
    /// values are unaffected by this method, only SmartDate
    /// values created after calling this method are affected.
    /// 
    /// 
    /// The format string should follow the requirements for the
    /// .NET System.String.Format statement.
    /// 
    public static void SetDefaultFormatString(string formatString)
    {
      _defaultFormat = formatString;
    }
    /// 
    /// Gets or sets the format string used to format a date
    /// value when it is returned as text.
    /// 
    /// 
    /// The format string should follow the requirements for the
    /// .NET System.String.Format statement.
    /// 
    /// A format string.
    public string FormatString
    {
      get
      {
        if (_format == null)
          _format = _defaultFormat;
        return _format;
      }
      set
      {
        _format = value;
      }
    }
    /// 
    /// Gets or sets the date value.
    /// 
    /// 
    /// 
    /// This property can be used to set the date value by passing a
    /// text representation of the date. Any text date representation
    /// that can be parsed by the .NET runtime is valid.
    /// 
    /// When the date value is retrieved via this property, the text
    /// is formatted by using the format specified by the 
    ///  property. The default is the
    /// short date format (d).
    /// 
    /// 
    public string Text
    {
      get { return DateToString(this.Date, FormatString, _emptyValue); }
      set { this.Date = StringToDate(value, _emptyValue); }
    }
    #endregion
    #region Date Support
    /// 
    /// Gets or sets the date value.
    /// 
    public DateTime Date
    {
      get
      {
        if (!_initialized)
        {
          _date = DateTime.MinValue;
          _initialized = true;
        }
        return _date;
      }
      set
      {
        _date = value;
        _initialized = true;
      }
    }
    #endregion
    #region System.Object overrides
    /// 
    /// Returns a text representation of the date value.
    /// 
    public override string ToString()
    {
      return this.Text;
    }
    /// 
    /// Returns a text representation of the date value.
    /// 
    /// 
    /// A standard .NET format string.
    /// 
    public string ToString(string format)
    {
      return DateToString(this.Date, format, _emptyValue);
    }
    /// 
    /// Compares this object to another 
    /// for equality.
    /// 
    /// Object to compare for equality.
    public override bool Equals(object obj)
    {
      if (obj is SmartDate)
      {
        SmartDate tmp = (SmartDate)obj;
        if (this.IsEmpty && tmp.IsEmpty)
          return true;
        else
          return this.Date.Equals(tmp.Date);
      }
      else if (obj is DateTime)
        return this.Date.Equals((DateTime)obj);
      else if (obj is string)
        return (this.CompareTo(obj.ToString()) == 0);
      else
        return false;
    }
    /// 
    /// Returns a hash code for this object.
    /// 
    public override int GetHashCode()
    {
      return this.Date.GetHashCode();
    }
    #endregion
    #region DBValue
    /// 
    /// Gets a database-friendly version of the date value.
    /// 
    /// 
    /// 
    /// If the SmartDate contains an empty date, this returns .
    /// Otherwise the actual date value is returned as type Date.
    /// 
    /// This property is very useful when setting parameter values for
    /// a Command object, since it automatically stores null values into
    /// the database for empty date values.
    /// 
    /// When you also use the SafeDataReader and its GetSmartDate method,
    /// you can easily read a null value from the database back into a
    /// SmartDate object so it remains considered as an empty date value.
    /// 
    /// 
    public object DBValue
    {
      get
      {
        if (this.IsEmpty)
          return DBNull.Value;
        else
          return this.Date;
      }
    }
    #endregion
    #region Empty Dates
    /// 
    /// Gets a value indicating whether this object contains an empty date.
    /// 
    public bool IsEmpty
    {
      get
      {
        if (_emptyValue == EmptyValue.MinDate)
          return this.Date.Equals(DateTime.MinValue);
        else
          return this.Date.Equals(DateTime.MaxValue);
      }
    }
    /// 
    /// Gets a value indicating whether an empty date is the 
    /// min or max possible date value.
    /// 
    /// 
    /// Whether an empty date is considered to be the smallest or largest possible
    /// date is only important for comparison operations. This allows you to
    /// compare an empty date with a real date and get a meaningful result.
    /// 
    public bool EmptyIsMin
    {
      get { return (_emptyValue == EmptyValue.MinDate); }
    }
    #endregion
    #region Conversion Functions
    /// 
    /// Converts a string value into a SmartDate.
    /// 
    /// String containing the date value.
    /// A new SmartDate containing the date value.
    /// 
    /// EmptyIsMin will default to .
    /// 
    public static SmartDate Parse(string value)
    {
      return new SmartDate(value);
    }
    /// 
    /// Converts a string value into a SmartDate.
    /// 
    /// String containing the date value.
    /// Indicates whether an empty date is the min or max date value.
    /// A new SmartDate containing the date value.
    public static SmartDate Parse(string value, bool emptyIsMin)
    {
      return new SmartDate(value, emptyIsMin);
    }
    /// 
    /// Converts a text date representation into a Date value.
    /// 
    /// 
    /// An empty string is assumed to represent an empty date. An empty date
    /// is returned as the MinValue of the Date datatype.
    /// 
    /// The text representation of the date.
    /// A Date value.
    public static DateTime StringToDate(string value)
    {
      return StringToDate(value, true);
    }
        /// 
    /// Converts a text date representation into a Date value.
    /// 
    /// 
    /// An empty string is assumed to represent an empty date. An empty date
    /// is returned as the MinValue or MaxValue of the Date datatype depending
    /// on the EmptyIsMin parameter.
    /// 
    /// The text representation of the date.
    /// Indicates whether an empty date is the min or max date value.
    /// A Date value.
    public static DateTime StringToDate(string value, bool emptyIsMin)
    {
      return StringToDate(value, GetEmptyValue(emptyIsMin));
    }
    /// 
    /// Converts a text date representation into a Date value.
    /// 
    /// 
    /// An empty string is assumed to represent an empty date. An empty date
    /// is returned as the MinValue or MaxValue of the Date datatype depending
    /// on the EmptyIsMin parameter.
    /// 
    /// The text representation of the date.
    /// Indicates whether an empty date is the min or max date value.
    /// A Date value.
    public static DateTime StringToDate(string value, EmptyValue emptyValue)
    {
      DateTime tmp;
      if (String.IsNullOrEmpty(value))
      {
        if (emptyValue == EmptyValue.MinDate)
          return DateTime.MinValue;
        else
          return DateTime.MaxValue;
      }
      else if (DateTime.TryParse(value, out tmp))
        return tmp; 
      else
      {
        string ldate = value.Trim().ToLower();
        if (ldate == Resources.SmartDateT ||
            ldate == Resources.SmartDateToday ||
            ldate == ".")
          return DateTime.Now;
        if (ldate == Resources.SmartDateY ||
            ldate == Resources.SmartDateYesterday ||
            ldate == "-")
          return DateTime.Now.AddDays(-1);
        if (ldate == Resources.SmartDateTom ||
            ldate == Resources.SmartDateTomorrow ||
            ldate == "+")
          return DateTime.Now.AddDays(1);
        throw new ArgumentException(Resources.StringToDateException);
      }
    }
    /// 
    /// Converts a date value into a text representation.
    /// 
    /// 
    /// The date is considered empty if it matches the min value for
    /// the Date datatype. If the date is empty, this
    /// method returns an empty string. Otherwise it returns the date
    /// value formatted based on the FormatString parameter.
    /// 
    /// The date value to convert.
    /// The format string used to format the date into text.
    /// Text representation of the date value.
    public static string DateToString(
      DateTime value, string formatString)
    {
      return DateToString(value, formatString, true);
    }
    /// 
    /// Converts a date value into a text representation.
    /// 
    /// 
    /// Whether the date value is considered empty is determined by
    /// the EmptyIsMin parameter value. If the date is empty, this
    /// method returns an empty string. Otherwise it returns the date
    /// value formatted based on the FormatString parameter.
    /// 
    /// The date value to convert.
    /// The format string used to format the date into text.
    /// Indicates whether an empty date is the min or max date value.
    /// Text representation of the date value.
    public static string DateToString(
      DateTime value, string formatString, bool emptyIsMin)
    {
      return DateToString(value, formatString, GetEmptyValue(emptyIsMin));
    }
    /// 
    /// Converts a date value into a text representation.
    /// 
    /// 
    /// Whether the date value is considered empty is determined by
    /// the EmptyIsMin parameter value. If the date is empty, this
    /// method returns an empty string. Otherwise it returns the date
    /// value formatted based on the FormatString parameter.
    /// 
    /// The date value to convert.
    /// The format string used to format the date into text.
    /// Indicates whether an empty date is the min or max date value.
    /// Text representation of the date value.
    public static string DateToString(
      DateTime value, string formatString, EmptyValue emptyValue)
    {
      if (emptyValue == EmptyValue.MinDate)
      {
        if (value == DateTime.MinValue)
          return string.Empty;
      }
      else
      {
        if (value == DateTime.MaxValue)
          return string.Empty;
      }
      return string.Format("{0:" + formatString + "}", value);
    }
    #endregion
    #region Manipulation Functions
    /// 
    /// Compares one SmartDate to another.
    /// 
    /// 
    /// This method works the same as the DateTime.CompareTo method
    /// on the Date datetype, with the exception that it
    /// understands the concept of empty date values.
    /// 
    /// The date to which we are being compared.
    /// A value indicating if the comparison date is less than, equal to or greater than this date.
    public int CompareTo(SmartDate value)
    {
      if (this.IsEmpty && value.IsEmpty)
        return 0;
      else
        return _date.CompareTo(value.Date);
    }
    /// 
    /// Compares one SmartDate to another.
    /// 
    /// 
    /// This method works the same as the DateTime.CompareTo method
    /// on the Date datetype, with the exception that it
    /// understands the concept of empty date values.
    /// 
    /// The date to which we are being compared.
    /// A value indicating if the comparison date is less than, equal to or greater than this date.
    int IComparable.CompareTo(object value)
    {
      if (value is SmartDate)
        return CompareTo((SmartDate)value);
      else
        throw new ArgumentException(Resources.ValueNotSmartDateException);
    }
    /// 
    /// Compares a SmartDate to a text date value.
    /// 
    /// The date to which we are being compared.
    /// A value indicating if the comparison date is less than, equal to or greater than this date.
    public int CompareTo(string value)
    {
      return this.Date.CompareTo(StringToDate(value, _emptyValue));
    }
    /// 
    /// Compares a SmartDate to a date value.
    /// 
    /// The date to which we are being compared.
    /// A value indicating if the comparison date is less than, equal to or greater than this date.
    public int CompareTo(DateTime value)
    {
      return this.Date.CompareTo(value);
    }
    /// 
    /// Adds a TimeSpan onto the object.
    /// 
    /// Span to add to the date.
    public DateTime Add(TimeSpan value)
    {
      if (IsEmpty)
        return this.Date;
      else
        return this.Date.Add(value);
    }
    /// 
    /// Subtracts a TimeSpan from the object.
    /// 
    /// Span to subtract from the date.
    public DateTime Subtract(TimeSpan value)
    {
      if (IsEmpty)
        return this.Date;
      else
        return this.Date.Subtract(value);
    }
    /// 
    /// Subtracts a DateTime from the object.
    /// 
    /// Date to subtract from the date.
    public TimeSpan Subtract(DateTime value)
    {
      if (IsEmpty)
        return TimeSpan.Zero;
      else
        return this.Date.Subtract(value);
    }
    #endregion
    #region Operators
    /// 
    /// Equality operator
    /// 
    /// First object
    /// Second object
    /// 
    public static bool operator ==(SmartDate obj1, SmartDate obj2)
    {
      return obj1.Equals(obj2);
    }
    /// 
    /// Inequality operator
    /// 
    /// First object
    /// Second object
    /// 
    public static bool operator !=(SmartDate obj1, SmartDate obj2)
    {
      return !obj1.Equals(obj2);
    }
    /// 
    /// Equality operator
    /// 
    /// First object
    /// Second object
    /// 
    public static bool operator ==(SmartDate obj1, DateTime obj2)
    {
      return obj1.Equals(obj2);
    }
    /// 
    /// Inequality operator
    /// 
    /// First object
    /// Second object
    /// 
    public static bool operator !=(SmartDate obj1, DateTime obj2)
    {
      return !obj1.Equals(obj2);
    }
    /// 
    /// Equality operator
    /// 
    /// First object
    /// Second object
    /// 
    public static bool operator ==(SmartDate obj1, string obj2)
    {
      return obj1.Equals(obj2);
    }
    /// 
    /// Inequality operator
    /// 
    /// First object
    /// Second object
    /// 
    public static bool operator !=(SmartDate obj1, string obj2)
    {
      return !obj1.Equals(obj2);
    }
    /// 
    /// Addition operator
    /// 
    /// Original date/time
    /// Span to add
    /// 
    public static SmartDate operator +(SmartDate start, TimeSpan span)
    {
      return new SmartDate(start.Add(span), start.EmptyIsMin);
    }
    /// 
    /// Subtraction operator
    /// 
    /// Original date/time
    /// Span to subtract
    /// 
    public static SmartDate operator -(SmartDate start, TimeSpan span)
    {
      return new SmartDate(start.Subtract(span), start.EmptyIsMin);
    }
    /// 
    /// Subtraction operator
    /// 
    /// Original date/time
    /// Second date/time
    /// 
    public static TimeSpan operator -(SmartDate start, SmartDate finish)
    {
      return start.Subtract(finish.Date);
    }
    /// 
    /// Greater than operator
    /// 
    /// First object
    /// Second object
    /// 
    public static bool operator >(SmartDate obj1, SmartDate obj2)
    {
      return obj1.CompareTo(obj2) > 0;
    }
    /// 
    /// Less than operator
    /// 
    /// First object
    /// Second object
    /// 
    public static bool operator <(SmartDate obj1, SmartDate obj2)
    {
      return obj1.CompareTo(obj2) < 0;
    }
    /// 
    /// Greater than operator
    /// 
    /// First object
    /// Second object
    /// 
    public static bool operator >(SmartDate obj1, DateTime obj2)
    {
      return obj1.CompareTo(obj2) > 0;
    }
    /// 
    /// Less than operator
    /// 
    /// First object
    /// Second object
    /// 
    public static bool operator <(SmartDate obj1, DateTime obj2)
    {
      return obj1.CompareTo(obj2) < 0;
    }
    /// 
    /// Greater than operator
    /// 
    /// First object
    /// Second object
    /// 
    public static bool operator >(SmartDate obj1, string obj2)
    {
      return obj1.CompareTo(obj2) > 0;
    }
    /// 
    /// Less than operator
    /// 
    /// First object
    /// Second object
    /// 
    public static bool operator <(SmartDate obj1, string obj2)
    {
      return obj1.CompareTo(obj2) < 0;
    }
    /// 
    /// Greater than or equals operator
    /// 
    /// First object
    /// Second object
    /// 
    public static bool operator >=(SmartDate obj1, SmartDate obj2)
    {
      return obj1.CompareTo(obj2) >= 0;
    }
    /// 
    /// Less than or equals operator
    /// 
    /// First object
    /// Second object
    /// 
    public static bool operator <=(SmartDate obj1, SmartDate obj2)
    {
      return obj1.CompareTo(obj2) <= 0;
    }
    /// 
    /// Greater than or equals operator
    /// 
    /// First object
    /// Second object
    /// 
    public static bool operator >=(SmartDate obj1, DateTime obj2)
    {
      return obj1.CompareTo(obj2) >= 0;
    }
    /// 
    /// Less than or equals operator
    /// 
    /// First object
    /// Second object
    /// 
    public static bool operator <=(SmartDate obj1, DateTime obj2)
    {
      return obj1.CompareTo(obj2) <= 0;
    }
    /// 
    /// Greater than or equals operator
    /// 
    /// First object
    /// Second object
    /// 
    public static bool operator >=(SmartDate obj1, string obj2)
    {
      return obj1.CompareTo(obj2) >= 0;
    }
    /// 
    /// Less than or equals operator
    /// 
    /// First object
    /// Second object
    /// 
    public static bool operator <=(SmartDate obj1, string obj2)
    {
      return obj1.CompareTo(obj2) <= 0;
    }
    #endregion
  }
}