package oving7.observablelist;

import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class ObservableHighscoreListTest {

	private int pos1;
	private int pos2;
	private ObservableHighscoreList highscoreList;

	private static void checkHighscoreList(String contextMessage, ObservableHighscoreList list,
			List<Integer> elements) {
		assertEquals(elements.size(), list.size(),
				contextMessage + " -> Testing the length of the highscore list");

		int i = 0;

		for (int element : elements) {
			assertEquals(element, list.getElement(i),
					contextMessage + " -> Testing that the element at position " + i + " matches");

			i++;
		}
	}

	private void addResultWithListener(int pos, int element) {
		pos1 = pos;
		highscoreList.addResult(element);

		// Check that the position that was changed is the same as the one sent to the listener
		assertEquals(pos1, pos2, "Added " + element + " at position " + pos
				+ " -> Testing the position received by the listener");
	}

	@BeforeEach
	public void setUp() {
		highscoreList = new ObservableHighscoreList(3);
		pos1 = -1;
		pos2 = -1;
	}

	@Test
	@DisplayName("Test constructor")
	public void testConstructor() {
		assertEquals(0, highscoreList.size(), "Testing initialization of the highscore list");
	}

	@Test
	@DisplayName("Add results (simple)")
	public void testAddElementSimple() {
		highscoreList.addResult(5);
		ObservableHighscoreListTest.checkHighscoreList("Added 5 to an empty list", highscoreList,
				List.of(5));

		highscoreList.addResult(6);
		ObservableHighscoreListTest.checkHighscoreList("Added 6 to the list [5]", highscoreList,
				List.of(5, 6));

		highscoreList.addResult(2);
		ObservableHighscoreListTest.checkHighscoreList("Added 2 to the list [5, 6]", highscoreList,
				List.of(2, 5, 6));
	}

	@Test
	@DisplayName("Add results - list becomes too long")
	public void testAddElementMoreThanMax() {
		highscoreList.addResult(5);
		highscoreList.addResult(6);
		highscoreList.addResult(2);
		ObservableHighscoreListTest.checkHighscoreList("Added 5, 6, and 2 to the list",
				highscoreList, List.of(2, 5, 6));

		highscoreList.addResult(3);
		ObservableHighscoreListTest.checkHighscoreList("Added 3 to the list [2, 5, 6]",
				highscoreList, List.of(2, 3, 5));

		highscoreList.addResult(7);
		ObservableHighscoreListTest.checkHighscoreList("Added 7 to the list [2, 3, 5]",
				highscoreList, List.of(2, 3, 5));
	}

	@Test
	@DisplayName("Add two identical elements")
	public void testAddElementDuplicate() {
		highscoreList.addResult(5);
		highscoreList.addResult(6);
		highscoreList.addResult(2);
		ObservableHighscoreListTest.checkHighscoreList("Added 5, 6, and 2 to the list",
				highscoreList, List.of(2, 5, 6));

		highscoreList.addResult(2);
		ObservableHighscoreListTest.checkHighscoreList("Added 2 to the list [2, 5, 6]",
				highscoreList, List.of(2, 2, 5));
	}

	@Test
	@DisplayName("Test listeners (simple)")
	public void testListListenersSimple() {
		// Mock a listener
		ObservableListListener listener = (list, pos) -> pos2 = pos;
		highscoreList.addObservableListListener(listener);

		this.addResultWithListener(0, 5);
		ObservableHighscoreListTest.checkHighscoreList("Added 5 to the list []", highscoreList,
				List.of(5));

		this.addResultWithListener(1, 6);
		ObservableHighscoreListTest.checkHighscoreList("Added 6 to the list [5]", highscoreList,
				List.of(5, 6));

		this.addResultWithListener(0, 2);
		ObservableHighscoreListTest.checkHighscoreList("Added 2 to the list [5, 6]", highscoreList,
				List.of(2, 5, 6));
	}

	@Test
	@DisplayName("With listener - list becomes too long")
	public void testListListenerMoreThanMax() {
		// Mock a listener
		ObservableListListener listener = (list, pos) -> pos2 = pos;
		highscoreList.addObservableListListener(listener);

		highscoreList.addResult(5);
		highscoreList.addResult(6);
		highscoreList.addResult(2);
		ObservableHighscoreListTest.checkHighscoreList("Added 5, 6, and 2 to the list",
				highscoreList, List.of(2, 5, 6));

		this.addResultWithListener(1, 3);
		ObservableHighscoreListTest.checkHighscoreList("Added 3 to the list [2, 5, 6]",
				highscoreList, List.of(2, 3, 5));

		// Reset pos2 since the next element falls outside the list and is therefore not updated by
		// itself and sent to the listener
		pos2 = -1;
		this.addResultWithListener(-1, 7);
		ObservableHighscoreListTest.checkHighscoreList("Added 7 to the list [2, 3, 5]",
				highscoreList, List.of(2, 3, 5));
	}

	@Test
	@DisplayName("With listener - two identical elements")
	public void testListListenerDuplicate() {
		// Mock a listener
		ObservableListListener listener = (list, pos) -> pos2 = pos;
		highscoreList.addObservableListListener(listener);

		highscoreList.addResult(5);
		highscoreList.addResult(6);
		highscoreList.addResult(2);
		ObservableHighscoreListTest.checkHighscoreList("Added 5, 6, and 2 to the list",
				highscoreList, List.of(2, 5, 6));

		this.addResultWithListener(1, 2);
		ObservableHighscoreListTest.checkHighscoreList("Added 2 to the list [2, 5, 6]",
				highscoreList, List.of(2, 2, 5));
	}
}
