using System;
using Xunit;

namespace Server.Tests
{
    [Collection("Sequential Tests")]
    public class TimerTests : IClassFixture<ServerFixture>
    {
        [Theory]
        [InlineData(0L, 8L)]
        [InlineData(80L, 80L)]
        [InlineData(65L, 72L)]
        [InlineData(32767L, 32768L)]
        [InlineData(32768L, 32768L)]
        [InlineData(32833L, 32840L)]
        [InlineData(134217729L, 134217736L)]
        public void TestVariousTimes(long ticks, long expectedTicks)
        {
            var timerTicks = new TimerTicks();
            void action()
            {
                Assert.Equal(expectedTicks, timerTicks.Ticks);
                timerTicks.ExecutedCount++;
            }

            Timer.Init(timerTicks.Ticks);

            Timer.StartTimer(TimeSpan.FromMilliseconds(ticks), action);

            var tickCount = expectedTicks / 8;

            for (int i = 1; i <= tickCount; i++)
            {
                timerTicks.Ticks = i * 8;

                Timer.Slice(timerTicks.Ticks);
            }

            Assert.Equal(1, timerTicks.ExecutedCount);
        }

        [Theory]
        [InlineData(1000L, 1000L, 30000L, 30000L, 2)]
        public void TestIntervals(long delay, long expectedDelayTicks, long interval, long expectedIntervalTicks, int count)
        {
            var timerTicks = new TimerTicks();

            Timer.Init(timerTicks.Ticks);

            Timer.StartTimer(TimeSpan.FromMilliseconds(delay), TimeSpan.FromMilliseconds(interval), count, action);

            var tickCount = (expectedDelayTicks + (expectedIntervalTicks * count - 1)) / 8;

            for (int i = 1; i <= tickCount; i++)
            {
                timerTicks.Ticks = i * 8;

                Timer.Slice(timerTicks.Ticks);
            }

            Assert.Equal(count, timerTicks.ExecutedCount);
            return;

            void action()
            {
                timerTicks.ExpectedTicks += timerTicks.ExecutedCount++ == 0 ? expectedDelayTicks : expectedIntervalTicks;
                Assert.Equal(timerTicks.ExpectedTicks, timerTicks.Ticks);
            }
        }

        [Fact]
        public void TestTimerStartedOnTick()
        {
            var timerTicks = new TimerTicks();

            Timer.Init(timerTicks.Ticks);

            var timer = new SelfRunningTimer(timerTicks);
            timer.Start();

            Timer.Slice(128);
            Assert.Equal(1, timerTicks.ExecutedCount);
            Timer.Slice(256);
            Assert.Equal(2, timerTicks.ExecutedCount);
        }

        private class TimerTicks
        {
            public long ExpectedTicks;
            public long Ticks;
            public int ExecutedCount;
        }

        private class SelfRunningTimer : Timer
        {
            private readonly TimerTicks _timerTicks;
            public SelfRunningTimer(TimerTicks ticks) : base(TimeSpan.FromMilliseconds(100)) => _timerTicks = ticks;

            protected override void OnTick()
            {
                if (_timerTicks.ExecutedCount++ == 0)
                {
                    Delay = TimeSpan.FromMilliseconds(100);
                    Start();
                }
            }
        }
    }
}
