package oving7.observablelist;

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 java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class ObservableListTest {

	private int pos1;
	private int pos2;
	private ObservableList observableList;

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

		int i = 0;

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

			i++;
		}
	}

	private void addElementWithListener(int pos, int element) {
		pos1 = pos;
		observableList.addElement(pos, 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() {
		observableList = new ObservableList() {
			@Override
			public boolean acceptsElement(Object element) {
				return element instanceof Integer;
			}
		};
		pos1 = -1;
		pos2 = -1;
	}

	@Test
	@DisplayName("Test constructor")
	public void testConstructor() {
		assertEquals(0, observableList.size());
	}

	@Test
	@DisplayName("Test acceptance of elements")
	public void testAcceptsElement() {
		assertTrue(observableList.acceptsElement(5), "Testing that the list accepts integers");
		assertFalse(observableList.acceptsElement("5"),
				"Testing that the list does not accept strings");

		assertThrows(IllegalArgumentException.class, () -> {
			observableList.addElement("5");
		}, "Testing that the list cannot accept elements of type string");
	}

	@Test
	@DisplayName("Test adding elements")
	public void testAddElement() {
		observableList.addElement(5);
		ObservableListTest.checkObservableList(observableList, List.of(5),
				"Added 5 to an empty list");

		observableList.addElement(6);
		ObservableListTest.checkObservableList(observableList, List.of(5, 6),
				"Added 6 to the list [5]");

		observableList.addElement(0, 2);
		ObservableListTest.checkObservableList(observableList, List.of(2, 5, 6),
				"Added 2 at position 0 in the list [5, 6]");
	}

	@Test
	@DisplayName("Test listener")
	public void testListListener() {
		ObservableListListener listener = (list, pos) -> pos2 = pos;
		observableList.addObservableListListener(listener);

		this.addElementWithListener(0, 5);
		ObservableListTest.checkObservableList(observableList, List.of(5),
				"Added 5 to the list []");

		this.addElementWithListener(1, 6);
		ObservableListTest.checkObservableList(observableList, List.of(5, 6),
				"Added 6 to the list [5]");

		this.addElementWithListener(0, 2);
		ObservableListTest.checkObservableList(observableList, List.of(2, 5, 6),
				"Added 2 to the list [5, 6]");
	}
}
