using System;
using System.Threading;
namespace Wayne.Lib
{
    /// <summary>
    /// Timer factory
    /// </summary>
    public class WayneTimerFactory : ITimerFactory
    {
        /// <summary>
        /// Creates a timer
        /// </summary>
        /// <param name="id"></param>
        /// <param name="parentEntity"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        public ITimer Create(int id, IIdentifiableEntity parentEntity, string name)
        {
            return new WayneTimer(id, parentEntity, name);
        }

        /// <summary>
        /// Creates a timer
        /// </summary>
        /// <typeparam name="TState"></typeparam>
        /// <param name="id"></param>
        /// <param name="parentEntity"></param>
        /// <param name="name"></param>
        /// <param name="state"></param>
        /// <returns></returns>
        public ITimer<TState> Create<TState>(int id, IIdentifiableEntity parentEntity, string name, TState state)
        {
            return new WayneTimer<TState>(id, parentEntity, name, state);
        }
    }

    /// <summary>
    /// Changes the due time and interval of a timer.
    /// </summary>
    internal abstract class WayneTimerChanger : ITimerChanger
    {
        private bool disposed;
        private readonly Timer timer;

        protected WayneTimerChanger(int id, IIdentifiableEntity parentEntity, string name, object state)
        {
            Id = id;
            ParentEntity = parentEntity;
            EntitySubType = name;
            timer = new Timer(TimerCallback, state, Timeout.Infinite, Timeout.Infinite);
        }

        ~WayneTimerChanger()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (disposed)
                return;
            disposed = true;
            if (disposing)
                timer.Dispose();
        }

        public int Id { get; private set; }
        public string EntityType { get { return "WayneTimer"; } }

        /// <summary>
        /// This is used by the logger and should never be set by inheriting classes
        /// </summary>
        public string FullEntityName { get; set; }
        public string EntitySubType { get; private set; }
        public IIdentifiableEntity ParentEntity { get; private set; }

        public bool Change(TimeSpan? dueTime, TimeSpan? period)
        {
            if (!dueTime.HasValue || !period.HasValue)
            {
                return timer.Change(dueTime.HasValue ? (long)dueTime.Value.TotalMilliseconds : Timeout.Infinite,
                    period.HasValue ? (long)period.Value.TotalMilliseconds : Timeout.Infinite);
            }
            return timer.Change(dueTime.Value, period.Value);
        }

        protected abstract void TimerCallback(object state);
    }

    internal class WayneTimer : WayneTimerChanger, ITimer
    {
        public WayneTimer(int id, IIdentifiableEntity parentEntity, string name)
            : base(id, parentEntity, name, null)
        {
        }

        public event TimoutCallback OnTimeout;

        protected override void TimerCallback(object state)
        {
            TimoutCallback onTimeout = OnTimeout;
            if (onTimeout != null)
                onTimeout(this, EventArgs.Empty);
        }
    }

    internal class WayneTimer<TState> : WayneTimerChanger, ITimer<TState>
    {
        public WayneTimer(int id, IIdentifiableEntity parentEntity, string name, TState state)
            : base(id, parentEntity, name, state)
        {
        }

        public event TimoutCallback<TState> OnTimeout;

        protected override void TimerCallback(object state)
        {
            TimoutCallback<TState> onTimeout = OnTimeout;
            if (onTimeout != null)
                onTimeout(this, new EventArgs<TState>((TState)state));
        }
    }
}