package fr.uge.code.camp;

import static org.junit.jupiter.api.Assertions.*;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.List;

import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.io.TempDir;

class Session2Test {

	private static Path ensureInProjectOrShares(String file) throws IOException {
		var path = Paths.get(file);
		if (Files.exists(path)) {
			return path;
		}
		// Same relative path, but under folder "/mnt/shares/..." instead of "data"
		var relative = Paths.get("").relativize(path);
		var fallback = Paths.get("/mnt/shares/igm/prof/pivoteau/JCC/Session2").resolve(relative);

		if (!Files.exists(fallback)) {
			throw new NoSuchFileException("Missing file in both locations: " + path + " and " + fallback);
		}
		return fallback;
	}

	@Nested
	@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
	public final class LemursEx1 {

		@Test
		@Order(1)
		void testLemurs(@TempDir Path tempDir) throws IOException {

			assertThrows(NullPointerException.class, () -> Session2.lemurs(null));

			var test = tempDir.resolve("test.data");
			Files.write(test, List.of("3ABACBABAACACABC".split("")));
			assertEquals(2, Session2.lemurs(test));

			test = tempDir.resolve("test.data");
			Files.write(test, List.of("411223132131123122".split("")));
			assertEquals(2, Session2.lemurs(test));
		}

		@Test
		@Order(2)
		void testLemurs2(@TempDir Path tempDir) throws IOException {
			var test = tempDir.resolve("test.data");

			Files.write(test, List.of("1111111".split("")));
			assertEquals(3, Session2.lemurs(test));

			Files.write(test, List.of("2111".split("")));
			assertEquals(1, Session2.lemurs(test));

			Files.write(test, List.of("21111".split("")));
			assertEquals(1, Session2.lemurs(test));

			Files.write(test, List.of("211111".split("")));
			assertEquals(1, Session2.lemurs(test));

			Files.write(test, List.of("2111111".split("")));
			assertEquals(2, Session2.lemurs(test));

			Files.write(test, List.of("2123123".split("")));
			assertEquals(0, Session2.lemurs(test));

			Files.write(test, List.of("21231231".split("")));
			assertEquals(1, Session2.lemurs(test));

			Files.write(test, List.of("2123123122".split("")));
			assertEquals(2, Session2.lemurs(test));
		}

		@Test
		@Order(3)
		void testLemursFile(@TempDir Path tempDir) throws IOException {
			var path = ensureInProjectOrShares("data/lemurs1.data");
			var result = assertTimeoutPreemptively(Duration.ofMillis(10_000), () -> Session2.lemurs(path));
			assertEquals(10, result);

			var path2 = ensureInProjectOrShares("data/lemurs2.data");
			result = assertTimeoutPreemptively(Duration.ofMillis(10_000), () -> Session2.lemurs(path2));
			assertEquals(44, result);

			var path3 = ensureInProjectOrShares("data/lemurs3.data");
			result = assertTimeoutPreemptively(Duration.ofMillis(10_000), () -> Session2.lemurs(path3));
			assertEquals(969, result);
		}

		@Test
		@Order(4)
		void testLemursBig(@TempDir Path tempDir) throws IOException {
			var path = ensureInProjectOrShares("data/lemurs4.data");
			var result = assertTimeoutPreemptively(Duration.ofMillis(10_000), () -> Session2.lemurs(path));
			assertEquals(9826, result);
		}

		@Test
		@Order(5)
		void testLemursBig2(@TempDir Path tempDir) throws IOException {
			var path = ensureInProjectOrShares("data/lemurs5.data");
			var result = assertTimeoutPreemptively(Duration.ofMillis(2_000), () -> Session2.lemurs(path));
			assertEquals(81, result);
		}
	}

	@Nested
	@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
	public final class MiceEx2 {

		@Test
		@Order(1)
		void testMice(@TempDir Path tempDir) throws IOException {

			assertThrows(NullPointerException.class, () -> Session2.mice(null));

			var test = tempDir.resolve("test.data");
			Files.write(test, List.of("3;1 5;2 7;3 3;2 2;1 4;#;3 2;1 8;#;3 4;#;4 7;#;3 3;#;#;#;#;#".split(";")));
			assertEquals(List.of("12311243".split("")), Session2.mice(test));

			test = tempDir.resolve("test.data");
			Files.write(test, List.of("2;1 5;2 7;3 3;#;5 2;1 4;#;2 2;1 4;#;3 2;1 8;#;3 4;#;4 7;#;3 3;#;#".split(";")));
			assertEquals(List.of("12311154".split("")), Session2.mice(test));
		}

		@Test
		@Order(2)
		void testMice2(@TempDir Path tempDir) throws IOException {
			var test = tempDir.resolve("test.data");

			Files.write(test, List.of("1".split(";")));
			assertEquals(List.of(), Session2.mice(test));

			Files.write(test, List.of("1;a 1".split(";")));
			assertEquals(List.of(), Session2.mice(test));

			Files.write(test, List.of("1;a 1;#".split(";")));
			assertEquals(List.of("a"), Session2.mice(test));

			Files.write(test, List.of("1;a 1;#;".split(";")));
			assertEquals(List.of("a"), Session2.mice(test));

			Files.write(test, List.of("1;a 1;#;b 1".split(";")));
			assertEquals(List.of("a"), Session2.mice(test));

			Files.write(test, List.of("1;a 1;#;b 1;#".split(";")));
			assertEquals(List.of("a", "b"), Session2.mice(test));

			Files.write(test, List.of("1;a 1;b 1;b 1;#;#;#".split(";")));
			assertEquals(List.of("a", "b"), Session2.mice(test));
		}

		@SuppressWarnings("unchecked")
		static List<String> readList(Path file) throws IOException, ClassNotFoundException {
			try (var in = new ObjectInputStream(new BufferedInputStream(Files.newInputStream(file)))) {
				return (List<String>) in.readObject();
			}
		}

		@Test
		@Order(3)
		void testMiceFile(@TempDir Path tempDir) throws IOException, ClassNotFoundException {
			var path = ensureInProjectOrShares("data/mice1.data");
			var result = assertTimeoutPreemptively(Duration.ofMillis(10_000), () -> Session2.mice(path));
			var binPath = ensureInProjectOrShares("data/mice1.bin");
			assertEquals(readList(binPath), result);

			var path2 = ensureInProjectOrShares("data/mice2.data");
			result = assertTimeoutPreemptively(Duration.ofMillis(10_000), () -> Session2.mice(path2));
			binPath = ensureInProjectOrShares("data/mice2.bin");
			assertEquals(readList(binPath), result);

			var path3 = ensureInProjectOrShares("data/mice3.data");
			result = assertTimeoutPreemptively(Duration.ofMillis(10_000), () -> Session2.mice(path3));
			binPath = ensureInProjectOrShares("data/mice3.bin");
			assertEquals(readList(binPath), result);

		}

		@Test
		@Order(4)
		void testMiceBig(@TempDir Path tempDir) throws IOException, ClassNotFoundException {
			var path = ensureInProjectOrShares("data/mice4.data");
			var result = assertTimeoutPreemptively(Duration.ofMillis(10_000), () -> Session2.mice(path));
			var binPath = ensureInProjectOrShares("data/mice4.bin");
			assertEquals(readList(binPath), result);
		}

		@Test
		@Order(5)
		void testMiceBig2(@TempDir Path tempDir) throws IOException, ClassNotFoundException {
			var path = ensureInProjectOrShares("data/mice5.data");
			var result = assertTimeoutPreemptively(Duration.ofMillis(10_000), () -> Session2.mice(path));
			var binPath = ensureInProjectOrShares("data/mice5.bin");
			assertEquals(readList(binPath), result);
		}

		@Test
		@Order(6)
		void testMiceBig3(@TempDir Path tempDir) throws IOException, ClassNotFoundException {
			var path = ensureInProjectOrShares("data/mice6.data");
			var result = assertTimeoutPreemptively(Duration.ofMillis(10_000), () -> Session2.mice(path));
			var binPath = ensureInProjectOrShares("data/mice6.bin");
			assertEquals(readList(binPath), result);
		}

		@Test
		@Order(7)
		void testMiceBigBig(@TempDir Path tempDir) throws IOException, ClassNotFoundException {
			var path = ensureInProjectOrShares("data/mice7.data");
			var result = assertTimeoutPreemptively(Duration.ofMillis(30_000), () -> Session2.mice(path));
			var binPath = ensureInProjectOrShares("data/mice7.bin");
			assertEquals(readList(binPath), result);
		}
	}
	
	@Nested
	@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
	public final class FrogsEx3 {
		@Test
		@Order(7)
		void testFrogs(@TempDir Path tempDir) throws IOException, ClassNotFoundException {
			var path = ensureInProjectOrShares("data/frogs1.data");
			var result = assertTimeoutPreemptively(Duration.ofMillis(10_000), () -> Session2.frogs(path));
			assertEquals(22, result);
			
			var path2 = ensureInProjectOrShares("data/frogs2.data");
			result = assertTimeoutPreemptively(Duration.ofMillis(10_000), () -> Session2.frogs(path2));
			assertEquals(12, result);
			
			var path3 = ensureInProjectOrShares("data/frogs3.data");
			result = assertTimeoutPreemptively(Duration.ofMillis(10_000), () -> Session2.frogs(path3));
			assertEquals(306, result);
			
			var path4 = ensureInProjectOrShares("data/frogs4.data");
			result = assertTimeoutPreemptively(Duration.ofMillis(10_000), () -> Session2.frogs(path4));
			assertEquals(124, result);
			
			var path5 = ensureInProjectOrShares("data/frogs5.data");
			result = assertTimeoutPreemptively(Duration.ofMillis(10_000), () -> Session2.frogs(path5));
			assertEquals(31170, result);
			
			var path6 = ensureInProjectOrShares("data/frogs6.data");
			result = assertTimeoutPreemptively(Duration.ofMillis(10_000), () -> Session2.frogs(path6));
			assertEquals(12687, result);
			
			var path7 = ensureInProjectOrShares("data/frogs7.data");
			result = assertTimeoutPreemptively(Duration.ofMillis(10_000), () -> Session2.frogs(path7));
			assertEquals(1559854, result);
			
			var path8 = ensureInProjectOrShares("data/frogs8.data");
			result = assertTimeoutPreemptively(Duration.ofMillis(10_000), () -> Session2.frogs(path8));
			assertEquals(639900, result);
		}
	}
}
