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.CleanupMode;
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;
		}
		var projectPath = Paths.get("Session2").resolve(file);
		if (Files.exists(projectPath)) {
			return projectPath;
		}
		// 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 {

		private static Duration timeoutFor(String fileName) {
			return switch (fileName) {
			case "frogs00.data", "frogs01.data", "frogs02.data", "frogs1.data", "frogs2.data", "frogs3.data" ->
				Duration.ofMillis(100);
			default -> Duration.ofMillis(10_000);
			};
		}
		
		@Test
		void testFrogCorrectness(@TempDir Path tempDir) throws IOException {

	        var test = List.of(new String[]{
	                "F----",
	                "R----",
	                "O----",
	                "G----",
	                "-GORF"
	        }); 
	        var testFile = tempDir.resolve("test1.data");
	        Files.write(testFile, test);
	        assertEquals(2, Session2.frogs(testFile));

	        test = List.of(new String[]{
	                "G----",
	                "OO---",
	                "R-R--",
	                "F--F-"
	        }); 
	        testFile = tempDir.resolve("test2.data");
	        Files.write(testFile, test);
	        assertEquals(2, Session2.frogs(testFile));

	        test = List.of(new String[]{
	                "GF---",
	                "FFROG",
	                "FROO-",
	                "F--GG"
	        }); 
	        testFile = tempDir.resolve("test3.data");
	        Files.write(testFile, test);
	        assertEquals(2, Session2.frogs(testFile));
	        
	        test = List.of(new String[]{
	                "---GF",
	                "--OR-",
	                "-RO--",
	                "FG---"
	        });
	        testFile = tempDir.resolve("test4.data");
	        Files.write(testFile, test);
	        assertEquals(2, Session2.frogs(testFile));
	    }
		
		@Test
		@Order(7)
		void testFrogs() throws IOException {
			Path dir = ensureInProjectOrShares("data/frogs");
			var files = Files.list(dir)
					.filter(path -> path.getFileName().toString().matches("frogs\\d+\\.data"))
					.sorted()
					.toList();

			for (var dataPath : files) {
				var fileName = dataPath.getFileName().toString();
				if (fileName.startsWith("._")) {
					continue;
				}
				var outPath = dir.resolve(fileName.replace(".data", ".out"));

				int expected = Integer.parseInt(Files.readString(outPath).trim());
				var result = assertTimeoutPreemptively(timeoutFor(fileName), () -> Session2.frogs(dataPath),
						"timeout with file " + dataPath);
				assertEquals(expected, result, "problem with file " + dataPath.getFileName());
			}
		}
	}
	
	
}
