// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Threading;
using System.Reactive.Disposables;
namespace System.Reactive.Concurrency
{
    /// 
    /// Represents an object that schedules units of work to run immediately on the current thread.
    /// 
    /// Singleton instance of this type exposed through this static property.
    public sealed class ImmediateScheduler : LocalScheduler
    {
        private static readonly ImmediateScheduler s_instance = new ImmediateScheduler();
        ImmediateScheduler()
        {
        }
        /// 
        /// Gets the singleton instance of the immediate scheduler.
        /// 
        public static ImmediateScheduler Instance
        {
            get { return s_instance; }
        }
        /// 
        /// Schedules an action to be executed.
        /// 
        /// The type of the state passed to the scheduled action.
        /// State passed to the action to be executed.
        /// Action to be executed.
        /// The disposable object used to cancel the scheduled action (best effort).
        ///  is null.
        public override IDisposable Schedule(TState state, Func action)
        {
            if (action == null)
                throw new ArgumentNullException("action");
            return action(new AsyncLockScheduler(), state);
        }
        /// 
        /// Schedules an action to be executed after dueTime.
        /// 
        /// The type of the state passed to the scheduled action.
        /// State passed to the action to be executed.
        /// Action to be executed.
        /// Relative time after which to execute the action.
        /// The disposable object used to cancel the scheduled action (best effort).
        ///  is null.
        public override IDisposable Schedule(TState state, TimeSpan dueTime, Func action)
        {
            if (action == null)
                throw new ArgumentNullException("action");
            var dt = Scheduler.Normalize(dueTime);
            if (dt.Ticks > 0)
            {
                ConcurrencyAbstractionLayer.Current.Sleep(dt);
            }
            return action(new AsyncLockScheduler(), state);
        }
        class AsyncLockScheduler : LocalScheduler
        {
            AsyncLock asyncLock;
            public override IDisposable Schedule(TState state, Func action)
            {
                if (action == null)
                    throw new ArgumentNullException("action");
                var m = new SingleAssignmentDisposable();
                if (asyncLock == null)
                    asyncLock = new AsyncLock();
                asyncLock.Wait(() =>
                {
                    if (!m.IsDisposed)
                        m.Disposable = action(this, state);
                });
                return m;
            }
            public override IDisposable Schedule(TState state, TimeSpan dueTime, Func action)
            {
                if (action == null)
                    throw new ArgumentNullException("action");
                if (dueTime.Ticks <= 0)
                    return Schedule(state, action);
                var timer = ConcurrencyAbstractionLayer.Current.StartStopwatch();
                var m = new SingleAssignmentDisposable();
                if (asyncLock == null)
                    asyncLock = new AsyncLock();
                asyncLock.Wait(() =>
                {
                    if (!m.IsDisposed)
                    {
                        var sleep = dueTime - timer.Elapsed;
                        if (sleep.Ticks > 0)
                        {
                            ConcurrencyAbstractionLayer.Current.Sleep(sleep);
                        }
                        if (!m.IsDisposed)
                            m.Disposable = action(this, state);
                    }
                });
                return m;
            }
        }
    }
}