package oving4.stopwatch;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class StopWatchTest {

	private StopWatch stopWatch;

	@BeforeEach
	public void beforeEach() {
		stopWatch = new StopWatch();
	}

	@Test
	@DisplayName("Check that a newly created StopWatch object has correct values")
	public void testConstructor() {
		assertFalse(stopWatch.isStarted());
		assertFalse(stopWatch.isStopped());
		assertEquals(0, stopWatch.getTicks());
		assertEquals(-1, stopWatch.getTime());
		assertEquals(-1, stopWatch.getLapTime());
		assertEquals(-1, stopWatch.getLastLapTime());
	}

	@Test
	@DisplayName("Check that tick() without start does not change the time")
	public void testTicksWithoutStart() {
		stopWatch.tick(1);
		assertEquals(-1, stopWatch.getTime());
		assertEquals(1, stopWatch.getTicks());

		stopWatch.tick(4);
		assertEquals(-1, stopWatch.getTime());
		assertEquals(5, stopWatch.getTicks());
	}

	@Test
	@DisplayName("Start and stop the StopWatch and check that the time is correct")
	public void testStartTickStop() {
		stopWatch.start();
		assertEquals(0, stopWatch.getTime());
		assertEquals(0, stopWatch.getTicks());
		assertTrue(stopWatch.isStarted());
		assertFalse(stopWatch.isStopped());

		assertThrows(IllegalStateException.class, () -> {
			stopWatch.start();
		}, "Cannot start an already running StopWatch");

		stopWatch.tick(3);
		assertEquals(3, stopWatch.getTime());
		assertEquals(3, stopWatch.getTicks());
		assertTrue(stopWatch.isStarted());
		assertFalse(stopWatch.isStopped());

		stopWatch.tick(5);
		assertEquals(8, stopWatch.getTime());
		assertEquals(8, stopWatch.getTicks());
		assertTrue(stopWatch.isStarted());
		assertFalse(stopWatch.isStopped());

		stopWatch.stop();
		assertEquals(8, stopWatch.getTime());
		assertEquals(8, stopWatch.getTicks());
		assertTrue(stopWatch.isStarted());
		assertTrue(stopWatch.isStopped());

		assertThrows(IllegalStateException.class, () -> {
			stopWatch.stop();
		}, "Cannot stop a StopWatch that is already stopped");
	}

	@Test
	@DisplayName("Start and stop the StopWatch, and call tick() while it is not started")
	public void testTickStartTickStopTick() {
		stopWatch.tick(2);
		assertEquals(-1, stopWatch.getTime());
		assertEquals(2, stopWatch.getTicks());
		assertFalse(stopWatch.isStarted());
		assertFalse(stopWatch.isStopped());

		stopWatch.start();
		assertEquals(0, stopWatch.getTime());
		assertEquals(2, stopWatch.getTicks());
		assertTrue(stopWatch.isStarted());
		assertFalse(stopWatch.isStopped());

		stopWatch.tick(3);
		assertEquals(3, stopWatch.getTime());
		assertEquals(5, stopWatch.getTicks());
		assertTrue(stopWatch.isStarted());
		assertFalse(stopWatch.isStopped());

		stopWatch.tick(5);
		assertEquals(8, stopWatch.getTime());
		assertEquals(10, stopWatch.getTicks());
		assertTrue(stopWatch.isStarted());
		assertFalse(stopWatch.isStopped());

		stopWatch.stop();
		assertEquals(8, stopWatch.getTime());
		assertEquals(10, stopWatch.getTicks());
		assertTrue(stopWatch.isStarted());
		assertTrue(stopWatch.isStopped());

		stopWatch.tick(3);
		assertEquals(8, stopWatch.getTime());
		assertEquals(13, stopWatch.getTicks());
		assertTrue(stopWatch.isStarted());
		assertTrue(stopWatch.isStopped());

		assertThrows(IllegalArgumentException.class, () -> {
			stopWatch.tick(-1);
		}, "Time should not be able to go backward");
	}

	@Test
	@DisplayName("Check that laps work as expected")
	public void testLaps() {
		assertThrows(IllegalStateException.class, () -> {
			stopWatch.lap();
		}, "Should not be able to start a new lap without starting the StopWatch");

		stopWatch.start();
		assertEquals(0, stopWatch.getTime());
		assertEquals(0, stopWatch.getLapTime());
		assertEquals(-1, stopWatch.getLastLapTime());

		stopWatch.tick(3);
		assertEquals(3, stopWatch.getTime());
		assertEquals(3, stopWatch.getLapTime());
		assertEquals(-1, stopWatch.getLastLapTime());

		stopWatch.lap();
		assertEquals(3, stopWatch.getTime());
		assertEquals(0, stopWatch.getLapTime());
		assertEquals(3, stopWatch.getLastLapTime());

		stopWatch.tick(5);
		assertEquals(8, stopWatch.getTime());
		assertEquals(5, stopWatch.getLapTime());
		assertEquals(3, stopWatch.getLastLapTime());

		stopWatch.stop();
		assertEquals(8, stopWatch.getTime());
		assertEquals(0, stopWatch.getLapTime());
		assertEquals(5, stopWatch.getLastLapTime());

		assertThrows(IllegalStateException.class, () -> {
			stopWatch.lap();
		}, "Should not be able to start a new lap with a stopped StopWatch");
	}
}
