using System;
using System.Diagnostics;

namespace nft.core.schedule
{
    public enum TimeUnit {
        Ticks, Minute, Hour, Day, Week, Month, Years
    }

    /// <summary>
	/// Time instant.
	/// </summary>
	[Serializable]
	public class Time : IComparable
	{
		internal protected Time(long _ticks)
		{
            this.ticks = _ticks;
		}

        public static Time Create(Date d, TimeOfDay t){
            return Create(d, t, Calendar.ActiveCalendar.TimeTickConverter);
        }

        public static Time Create(Date d) {
            return Create(d, Calendar.ActiveCalendar.TimeTickConverter);
        }

        public static Time Create( Date d, TimeOfDay t, ITimeTickConverter c)
		{
            return new Time(c.ToTicks(d) + c.ToTicks(t));
		}

        public static Time Create(Date d, ITimeTickConverter c)
		{
            return new Time(c.ToTicks(d));
		}

		/// <summary>
		/// Current time in seconds from 01/01/01 00:00am
		/// </summary>
		protected long ticks;
		public virtual long Ticks { get { return ticks; } set { ticks = value; } }

		/// <summary> Returns a string formatter for the display. </summary>
		public override string ToString() {
			return string.Format("Time:[{0}/{1}/{2}({3}) {4,2:d}:{5,2:d}:{6,2:d}]",
				Year, Month, Day, DayOfTheWeek,
				Hour, Minute, Second  );
		}

        public string ToString(string format) {
            throw new NotImplementedException();
        }

        internal protected virtual ITimeTickConverter Converter {
            get {
                return Calendar.ActiveCalendar.TimeTickConverter;
            }
        }

		/// <summary>
		/// Total minutes from the start of the game.
		/// Use field 'ticks' instead of the property 'Ticks',
		/// so that extended class can set offset to ticks->Ticks
		/// </summary>
        public long TotalMinutes {
            get { return ticks / Converter.MinutelyTicks; }
        }
        public long TotalHours {
            get { return ticks / Converter.HourlyTicks; }
        }
        public long TotalDays {
            get { return ticks / Converter.DailyTicks; }
        }
        public long TotalMonths {
            get { return ticks / Converter.MonthlyTicksAve; }
        }
        public long TotalYears {
            get { return ticks / Converter.YearlyTicksAve; }
        }

        public long GetUnitValue(TimeUnit result_unit) {
            long lr = Converter.TicksForUnit(result_unit);
            return ticks / lr;
        }

        public long GetUnitValue(TimeUnit trim_range, TimeUnit result_unit) {
            long lm = Converter.TicksForUnit(trim_range);
            long lr = Converter.TicksForUnit(result_unit);
            Debug.Assert(lm>lr);
            return (ticks % lm) / lr;
        }

		#region Get the current date/time
		/// <summary>
		/// the current year. from 1.
		/// </summary>
		public int Year { get { return Converter.GetYear(ticks); } }
		/// <summary>
		/// the current month. from 1.
		/// </summary>
		public int Month { get { return Converter.GetMonth(ticks); } }
		/// <summary>
		/// the current day of the month. from 1.
		/// </summary>
		public int Day { get { return Converter.GetDay(ticks); } }

		public int Hour { get { return Converter.GetHour(ticks); } }
		public int Minute { get { return Converter.GetMinute(ticks); } }
		public int Second { get { return Converter.GetSecond(ticks); } }
		#endregion

		/// <summary>
		/// the current day of the week. from 0 to 6.
		/// </summary>
		public DayOfWeek DayOfTheWeek
		{ get { return (DayOfWeek)indexOfWeek; } }

		protected int indexOfWeek{ get{ return (int)(Converter.GetDayOfWeek(ticks)); } }

		public bool IsWeekend {
			get {
				DayOfWeek dow = DayOfTheWeek;
				return dow==DayOfWeek.Sunday || dow==DayOfWeek.Saturday;
			}
		}

        public Date ToDate() {
            return Converter.ToDate(ticks);
            //return new Date(Year,Month,Day);
        }

        /// <summary>
        /// Get time of day.
        /// Cut off Date informations.
        /// </summary>
        /// <returns></returns>
        public TimeOfDay GetTimeOfTheDay() {
            return Converter.ToTimeOfDay(ticks);
        }

		/// <summary>
		/// Get 0 o'clock of the day. 
		/// Cut off ticks smaller than 'DAY'.
		/// </summary>
		/// <returns></returns> 		 
		public Time GetZeroOfClock()
		{
			return new Time(ticks - (ticks % Converter.DailyTicks));
		}

		/// <summary>
        /// Cut off YEAR counts.
		/// </summary>
		/// <returns></returns>
        public Time GetDayOfYear()
		{
            Date d = Converter.ToDate(ticks);
            Date d2 = new Date(0, d.Month, d.Day);
            return new Time(Converter.ToTicks(d2));
		}

		#region IComparable メンバ

		public int CompareTo(object obj)
		{			
			return Math.Sign(Ticks-((Time)obj).Ticks);
		}

		#endregion
        
        #region 比較演算子<,>など のオーバーロード
        public static bool operator <(Time x, Time y) {
            return x.CompareTo(y) < 0;
        }

        public static bool operator <=(Time x, Time y) {
            return x.CompareTo(y) <= 0;
        }

        public static bool operator >(Time x, Time y) {
            return x.CompareTo(y) > 0;
        }

        public static bool operator >=(Time x, Time y) {
            return x.CompareTo(y) >= 0;
        }
        #endregion
    
		#region Equals and GetHashCode
		public override bool Equals(object obj)
		{
			Time t = obj as Time;
			if(t!=null)
				return t.Ticks==Ticks;
			return false;
		}

		public override int GetHashCode()
		{
			return (int)(Ticks/Converter.MinutelyTicks);
		}
		#endregion

		#region operators

		public static TimeLength operator - ( Time ta, Time tb ) 
		{
            Debug.Assert(ta.Converter == tb.Converter);
            return new TimeLength(ta.Ticks - tb.Ticks );
		}
		public static Time operator + ( Time ta, TimeLength tb ) {
            Debug.Assert(ta.Converter == tb.Converter);
            return new Time(ta.Ticks + tb.Ticks);
		}

		public static implicit operator long( Time ta )
		{
			return ta.Ticks;
		}

		public static implicit operator DateTime( Time ta )
		{
            Date d = ta.Converter.ToDate(ta.Ticks);
            // beause this class treat no leap year, the Ticks cannot compatible to that of DataTime.
			return new DateTime(d.Year,d.Month,d.Day,ta.Hour,ta.Minute,ta.Second);
		}

		#endregion

	}
}
