using System;
using System.Diagnostics;
using nft.core.game;

namespace nft.core.schedule
{
	/// <summary>
	/// Handles a clock event.
    /// </summary>
    /// <param name="sender"></param>
    /// <returns>The handler will be removed from the queue when false is returned.</returns>
    public delegate bool ClockHandler(Clock sender);

	/// <summary>
	/// Clock that governs the time of the world.
	/// 
	/// Because of the way Windows Forms work, this class is not self-sufficient.
	/// The main window needs to run a timr and periodically call the tick method
	/// of this class to make this class work.
	/// </summary>
	[Serializable]
	public class Clock : Time 
	{
		protected long OFFSET;

		// creatable only from the World class.
        internal Clock(long origin) : base(origin)
		{
			OFFSET = origin;
			OnTick = new EventHandler(NullHandler);
            dailyQueue = new ClockEventQueue(Converter.DailyTicks);
            weeklyQueue = new ClockEventQueue(Converter.DailyTicks*7);
            if (Converter.TreatLeapYears) {
                throw new NotImplementedException("Leap year is not supported yet.");
            } else {
                yearlyQueue = new ClockEventQueue(Converter.YearlyTicksAve);
            }
            oneshotQueue = new OneShotClockEventQueue();
		}
		
        /*
		public void SetTime( long t ) 
		{
			this.Ticks = t;
			// notify the time change
			//PictureManager.reset();
			//World.world.onAllVoxelUpdated();
		}
         */

		/// <summary>
		/// Ticks past from start.
		/// </summary>
        public long PastTicks
		{
			get	{ return ticks - OFFSET; }
		}

        public Time PastTime {
            get {
                return new Time(PastTicks);
            }
        }

		/// <summary>
		/// Handlers that are waiting for the clock notification.
		/// </summary>
		private readonly ClockEventQueue dailyQueue;
		private readonly ClockEventQueue weeklyQueue;
		private readonly ClockEventQueue yearlyQueue;
		private readonly ClockEventQueue oneshotQueue;

		#region handler maintainance
		/// <summary>
		/// Registers an one-shot timer, which will be fired after
		/// the specified time span.
		/// </summary>
		public void RegisterOneShot( ClockHandler handler, TimeLength time ) 
		{
			//Debug.Assert(time.Ticks>0);
			oneshotQueue.Add( this+time, handler );
		}

		/// <summary>
		/// Registers an one-shot timer, which will be fired at
		/// the specified time.
		/// </summary>
		public void RegisterOneShot( ClockHandler handler, Time time ) 
		{
			Debug.Assert(this<time);
			oneshotQueue.Add( time, handler);
		}

		/// <summary>
		/// Registers an one-shot timer, which will be fired at
		/// the specified time.
		/// </summary>
		public void RegisterOneShot( ClockHandler handler, TimeOfDay time ) 
		{
			RegisterOneShot( handler, UntilTheTime(time) );
		}

		/// <summary>
		/// Registers a daily-timer, which will be fired
		/// everyday at specified time.
		/// the value second of the 'time' is ignored.
		/// </summary>
		public void RegisterDailyHandler( ClockHandler handler, TimeOfDay time ) 
		{
			dailyQueue.Add( ToTime(time), handler );
		}

		/// <summary>
		/// Registers a weekly-timer, which will be fired
		/// everyweek at specified time.
		/// the value second of the 'time' is ignored.
		/// </summary>
		public void RegisterWeeklyHandler( ClockHandler handler, DayOfWeek week, TimeOfDay time ) 
		{
            Time tmp = Time.Create(new Date(0, 1, 1+(int)week), time, Converter);
            weeklyQueue.Add( tmp, handler );
		}

		/// <summary>
		/// Registers a yearly-timer, which will be fired
		/// everyyear at specified time.
		/// the value second and year of the 'time' is ignored.
		/// </summary>
		public void RegisterYearlyHandler( ClockHandler handler, Time time ) 
		{
			yearlyQueue.Add( time, handler );
		}

		/// <summary>
		/// Unregisters a daily repeated timer.
		/// </summary>
        [Obsolete]
		public void UnregisterDailyHandler( ClockHandler handler, TimeOfDay time ) 
		{
			dailyQueue.Remove(ToTime(time),handler);
		}

		/// <summary>
		/// Unregisters a weekly repeated timer.
		/// </summary>
        [Obsolete]
        public void UnregisterWeeklyHandler(ClockHandler handler, DayOfWeek week, TimeOfDay time) 
		{			
			weeklyQueue.Remove(ToTime(time),handler);
		}

		/// <summary>
		/// Unregisters a yearly repeated timer.
		/// </summary>
        [Obsolete]
        public void UnregisterYearlyHandler(ClockHandler handler, Time time) {
            Date d = time.Converter.ToDate(time);
            yearlyQueue.Remove(ToTime(new Date(0, d.Month, d.Day)), handler);
        }
		#endregion

		/// <summary>
		/// One-time call back at the end of a turn.
		/// 
		/// To get continuous call back after each end of turn,
		/// keep registering handlers at the end of each callback.
		/// </summary>
		[NonSerialized]
		public EventHandler OnTick;

		#region utility function 'Until???'
		/// <summary>
        /// create a time span object that represents the period until tomorrow's 0:00.
        /// if the current time is just 0:00, it returns "24hours" rather than 0.
		/// </summary>
		/// <returns></returns>
        public TimeLength UntilTomorrow() 
		{
            long t = Converter.DailyTicks + GetZeroOfClock().Ticks;
            return new TimeLength(t);
		}

		/// <summary>
        /// The shortest positive TimeLength to the time matched to the value. 
		/// </summary>
		/// <param name="time"></param>
		/// <returns></returns>
        public TimeLength UntilTheTime( TimeOfDay time )
		{
            TimeOfDay td = Converter.ToTimeOfDay(Ticks);
            if (td < time) {
                return new TimeLength(Ticks - Converter.ToTicks(time));
            } else {
                return new TimeLength(Ticks + Converter.DailyTicks - Converter.ToTicks(time));
            }
		}

        /// <summary>
        /// The shortest positive TimeLength to the time matched to the value.
        /// </summary>
        /// <param name="hour"></param>
        /// <param name="minute"></param>
        /// <returns></returns>
		public TimeLength UntilTheTime( int hour, int minute )
		{
            return UntilTheTime(new TimeOfDay(hour, minute));
		}

        // The shortest positive TimeLength to the time matched to the value.
        public TimeLength UntilTheDay(int month, int day)
		{
            Date d1 = Converter.ToDate(Ticks);
            Date d2 = new Date(d1.Year, month, day);
            if (d1 < d2) {
                return new TimeLength(Ticks - Converter.ToTicks(d2));
            } else {
                d2 = new Date(d1.Year + 1, month, day);
                return new TimeLength(Ticks - Converter.ToTicks(d2));
            }
		}
		#endregion

		/// <summary>
		/// Make the clock tick.
		/// </summary>
		public void Tick()  
		{
            ticks++;
            dailyQueue.Dispatch(this);
            weeklyQueue.Dispatch(this);
            yearlyQueue.Dispatch(this);
            oneshotQueue.Dispatch(this);
            OnTick(this, null);
		}

		/// <summary>
		/// Make the clock tick.
		/// </summary>
		public void Tick(long n)  
		{
            for (int i = 0; i < n; i++) {
                Tick();
            }
		}

        internal protected Time ToTime(Date date) {
            return new Time(Converter.ToTicks(date));
        }

		internal protected Time ToTime(TimeOfDay time) {
            return new Time(Converter.ToTicks(time));
        }

		public void NullHandler(object sender, EventArgs arg ){}
	}
}
