package com.dhemery.express;

import org.jmock.Expectations;
import org.jmock.auto.Mock;
import org.jmock.integration.junit4.JUnitRuleMockery;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.jmock.lib.script.ScriptedAction.perform;

public class ClockPollTimerTests {
    @Rule
    public JUnitRuleMockery context = new JUnitRuleMockery();
    private final ManualClock clock = new ManualClock();
    private PollTimer timer;

    @Mock
    Sleeper sleeper;

    @Before
    public void setup() {
        timer = new ClockPollTimer(clock, sleeper);
    }

    @Test
    public void withNegativePollDuration_isExpiredOnStart() {
        PollingSchedule schedule = new PollingSchedule(Duration.ofSeconds(1), Duration.ofMinutes(-1));
        timer.start(schedule);
        assertThat(timer.isExpired(), is(true));
    }

    @Test
    public void withZeroPollDuration_isExpiredOnStart() {
        PollingSchedule schedule = new PollingSchedule(Duration.ofSeconds(1), Duration.ofMinutes(0));
        timer.start(schedule);
        assertThat(timer.isExpired(), is(true));
    }

    @Test
    public void withPositivePollDuration_isNotExpiredOnStart() {
        PollingSchedule schedule = new PollingSchedule(Duration.ofSeconds(1), Duration.ofMinutes(1));
        timer.start(schedule);
        assertThat(timer.isExpired(), is(false));
    }

    @Test
    public void isNotExpired_ifTimeElapsedSinceStart_isLessThanPollDuration() {
        PollingSchedule schedule = new PollingSchedule(Duration.ofSeconds(1), Duration.ofSeconds(3).plus(Duration.ofNanos(1)));
        givenThat(sleepingAdvancesTheClock());
        timer.start(schedule);
        timer.tick();
        timer.tick();
        timer.tick();
        assertThat(timer.isExpired(), is(false));
    }

    @Test
    public void isExpired_ifTimeElapsedSinceStart_isExactlyPollDuration() {
        PollingSchedule schedule = new PollingSchedule(Duration.ofSeconds(1), Duration.ofSeconds(3));
        givenThat(sleepingAdvancesTheClock());
        timer.start(schedule);
        timer.tick();
        timer.tick();
        timer.tick();
        assertThat(timer.isExpired(), is(true));
    }

    @Test
    public void isExpired_ifTimeElapsedSinceStart_exceedsPollDuration() {
        givenThat(sleepingAdvancesTheClock());
        PollingSchedule schedule = new PollingSchedule(Duration.ofSeconds(1), Duration.ofSeconds(3).minus(Duration.ofNanos(1)));
        timer.start(schedule);
        timer.tick();
        timer.tick();
        timer.tick();
        assertThat(timer.isExpired(), is(true));
    }

    @Test
    public void tick_sleepsUsingSleeper() {
        final Duration pollingInterval = Duration.ofSeconds(1);
        PollingSchedule schedule = new PollingSchedule(pollingInterval, Duration.ofSeconds(3));

        context.checking(new Expectations() {{
            exactly(3).of(sleeper).sleep(with(any(Duration.class)));
        }});

        timer.start(schedule);
        timer.tick();
        timer.tick();
        timer.tick();
    }

    @Test
    public void tick_sleepsUntilNextPollingIntervalBoundary() {
        final Duration pollingInterval = Duration.ofSeconds(1);
        PollingSchedule schedule = new PollingSchedule(pollingInterval, Duration.ofSeconds(3));
        Duration delayBetweenStartAndTick = Duration.ofMillis(123);
        Duration expectedSleepDuration = pollingInterval.minus(delayBetweenStartAndTick);

        context.checking(new Expectations() {{
            allowing(sleeper).sleep(expectedSleepDuration);
            will(perform("clock.advance($0)").where("clock", clock));
        }});

        timer.start(schedule);
        clock.advance(delayBetweenStartAndTick);
        timer.tick();
    }

    public static class ManualClock extends Clock {

        private Instant now = Instant.now();

        @Override
        public ZoneId getZone() {
            return null;
        }

        @Override
        public Clock withZone(ZoneId zone) {
            return null;
        }

        @Override
        public Instant instant() {
            return now;
        }

        public void advance(Duration amountToAdvance) {
            now = now.plus(amountToAdvance);
        }

    }

    private void givenThat(Expectations expectations) {
        context.checking(expectations);
    }

    private Expectations sleepingAdvancesTheClock() {
        return new Expectations() {{
            allowing(sleeper).sleep(with(any(Duration.class)));
            will(perform("clock.advance($0)").where("clock", clock));
        }};
    }
}

@@ -54,16 +54,69 @@
 ations;%0A
+import org.jmock.States;%0Aimport org.jmock.auto.Auto;%0A
 import o
@@ -131,24 +131,24 @@
 .auto.Mock;%0A
-
 import org.j
@@ -741,16 +741,50 @@
 sleeper;
+%0A    @Auto%0A    States sleeperType;
 %0A%0A    @B
@@ -867,16 +867,333 @@
 eeper);%0A
+        sleeperType.become(%22default%22);%0A        context.checking(new Expectations() %7B%7B%0A            allowing(sleeper).sleep(with(any(Duration.class)));%0A            when(sleeperType.is(%22default%22));%0A            will(perform(%22clock.advance($0)%22).where(%22clock%22, clock));%0A        %7D%7D);%0A        sleeperType.become(%22default%22);%0A
     %7D%0A%0A 
@@ -2197,55 +2197,8 @@
 ));%0A
-        givenThat(sleepingAdvancesTheClock());%0A
     
@@ -2539,55 +2539,8 @@
 ));%0A
-        givenThat(sleepingAdvancesTheClock());%0A
     
@@ -2776,55 +2776,8 @@
 ) %7B%0A
-        givenThat(sleepingAdvancesTheClock());%0A
     
@@ -3265,24 +3265,63 @@
 conds(3));%0A%0A
+        sleeperType.become(%22special%22);%0A
         cont
@@ -3931,24 +3931,63 @@
 tAndTick);%0A%0A
+        sleeperType.become(%22special%22);%0A
         cont
@@ -4326,17 +4326,16 @@
 Clock %7B%0A
-%0A
         
@@ -4744,24 +4744,24 @@
 ToAdvance);%0A
+
         %7D%0A%0A 
@@ -4761,356 +4761,8 @@
    %7D
-%0A%0A    %7D%0A%0A    private void givenThat(Expectations expectations) %7B%0A        context.checking(expectations);%0A    %7D%0A%0A    private Expectations sleepingAdvancesTheClock() %7B%0A        return new Expectations() %7B%7B%0A            allowing(sleeper).sleep(with(any(Duration.class)));%0A            will(perform(%22clock.advance($0)%22).where(%22clock%22, clock));%0A        %7D%7D;
 %0A   

Use JMock States to guide sleeper for ClockPollTimer tests