// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.ComponentModel;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
namespace System.Reactive.PlatformServices
{
    /// 
    /// (Infrastructure) Provides access to the local system clock.
    /// 
    [EditorBrowsable(EditorBrowsableState.Never)]
    public class DefaultSystemClock : ISystemClock
    {
        /// 
        /// Gets the current time.
        /// 
        public DateTimeOffset UtcNow
        {
            get { return DateTimeOffset.UtcNow; }
        }
    }
    internal class DefaultSystemClockMonitor : PeriodicTimerSystemClockMonitor
    {
        private static readonly TimeSpan DEFAULT_PERIOD = TimeSpan.FromSeconds(1);
        public DefaultSystemClockMonitor()
            : base(DEFAULT_PERIOD)
        {
        }
    }
    /// 
    /// (Infrastructure) Monitors for system clock changes based on a periodic timer.
    /// 
    [EditorBrowsable(EditorBrowsableState.Never)]
    public class PeriodicTimerSystemClockMonitor : INotifySystemClockChanged
    {
        private readonly TimeSpan _period;
        private readonly SerialDisposable _timer;
        private DateTimeOffset _lastTime;
        private EventHandler _systemClockChanged;
        private const int SYNC_MAXRETRIES = 100;
        private const double SYNC_MAXDELTA = 10;
        private const int MAXERROR = 100;
        /// 
        /// Creates a new monitor for system clock changes with the specified polling frequency.
        /// 
        /// Polling frequency for system clock changes.
        public PeriodicTimerSystemClockMonitor(TimeSpan period)
        {
            _period = period;
            _timer = new SerialDisposable();
        }
        /// 
        /// Event that gets raised when a system clock change is detected.
        /// 
        public event EventHandler SystemClockChanged
        {
            add
            {
                NewTimer();
                _systemClockChanged += value;
            }
            remove
            {
                _systemClockChanged -= value;
                _timer.Disposable = Disposable.Empty;
            }
        }
        private void NewTimer()
        {
            _timer.Disposable = Disposable.Empty;
            var n = 0;
            do
            {
                _lastTime = SystemClock.UtcNow;
                _timer.Disposable = ConcurrencyAbstractionLayer.Current.StartPeriodicTimer(TimeChanged, _period);
            } while (Math.Abs((SystemClock.UtcNow - _lastTime).TotalMilliseconds) > SYNC_MAXDELTA && ++n < SYNC_MAXRETRIES);
            if (n >= SYNC_MAXRETRIES)
                throw new InvalidOperationException(Strings_Core.FAILED_CLOCK_MONITORING);
        }
        private void TimeChanged()
        {
            var now = SystemClock.UtcNow;
            var diff = now - (_lastTime + _period);
            if (Math.Abs(diff.TotalMilliseconds) >= MAXERROR)
            {
                var scc = _systemClockChanged;
                if (scc != null)
                    scc(this, new SystemClockChangedEventArgs(_lastTime + _period, now));
                NewTimer();
            }
            else
            {
                _lastTime = SystemClock.UtcNow;
            }
        }
    }
}