package eu.simstadt.regionchooser;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.junit.jupiter.api.AfterEach; // JUnit 5
import org.junit.jupiter.api.BeforeEach; // JUnit 5
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import picocli.CommandLine;


class RegionChooserCLITests
{
	final PrintStream originalOut = System.out;
	final PrintStream originalErr = System.err;
	final ByteArrayOutputStream out = new ByteArrayOutputStream();
	final ByteArrayOutputStream err = new ByteArrayOutputStream();

	private static final Path TEST_REPOSITORY = Paths.get("src/test/resources/testdata/");


	@TempDir
	Path tempDirectory;
	Path outGML;

	@BeforeEach
	public void setUp() {
		out.reset();
		err.reset();
		System.setOut(new PrintStream(out));
		System.setErr(new PrintStream(err));
		outGML = tempDirectory.resolve("output.gml");
	}

	@AfterEach
	public void restore() throws IOException {
		System.setOut(originalOut);
		System.setErr(originalErr);
		Files.deleteIfExists(outGML);
	}

	@Test
	void testNoInput() {
		new CommandLine(new RegionChooserCLI()).execute("");
		String expectedErr = "Missing required options: '--input=input.gml', '--output=output.gml', '--wkt=polygon.wkt'";
		assertTrue(err.toString().contains(expectedErr), err.toString() + " should contain " + expectedErr);
	}

	@Test
	void testGetVersion() {
		new CommandLine(new RegionChooserCLI()).execute("--version");
		String expectedVersion = RegionChooserUtils.getApplicationVersion();
		assertTrue(out.toString().contains(expectedVersion), out.toString() + " should contain " + expectedVersion);
	}

	@Test
	void testExtractRegionFromTwoCitygmls() throws IOException {
		String wktPolygon = "POLYGON((3512984.7003764412 5405148.310572891,3513038.6360455155 5405010.072163861,3513142.7277745553 5405004.02571992,3514204.1661769524 5405563.192081669,3514399.2818417274 5405720.905457244,3514291.6158155007 5405896.706492759,3512984.7003764412 5405148.310572891))";
		Path citygml1 = TEST_REPOSITORY.resolve("Stuttgart.proj/Stuttgart_LOD0_LOD1_small.gml");
		Path citygml2 = TEST_REPOSITORY.resolve("Stuttgart.proj/Stöckach_überarbeitete GML-NoBuildingPart.gml");
		Path inWKT = tempDirectory.resolve("polygon.wkt");
		try (BufferedWriter wkt = Files.newBufferedWriter(inWKT)) {
			wkt.write(wktPolygon);
		}
		assertFalse(Files.exists(outGML));
		new CommandLine(new RegionChooserCLI()).execute("--input=" + citygml1 + "," + citygml2, "--output=" + outGML,
				"--wkt=" + inWKT, "--epsg=31463", "--local");

		assertTrue(Files.exists(outGML));
		assertTrue(Files.size(outGML) > 600_000);
		assertEquals(20, countBuildings(outGML));
	}


	@Test
	void testExtractRegionFromTwoCitygmlsInWGS84() throws IOException {
		String wktPolygon = "POLYGON((9.18991539528286 48.79067456353269, 9.195580220722313 48.7873947720947, 9.172062612079735 48.78241812742368, 9.170946813129538 48.78569824412753, 9.18991539528286 48.79067456353269))";
		Path citygml1 = TEST_REPOSITORY.resolve("Stuttgart.proj/Stuttgart_LOD0_LOD1_small.gml");
		Path citygml2 = TEST_REPOSITORY.resolve("Stuttgart.proj/Stöckach_überarbeitete GML-NoBuildingPart.gml");
		Path inWKT = tempDirectory.resolve("polygon.wkt");
		try (BufferedWriter wkt = Files.newBufferedWriter(inWKT)) {
			wkt.write(wktPolygon);
		}
		assertFalse(Files.exists(outGML));
		new CommandLine(new RegionChooserCLI()).execute("--input=" + citygml1 + "," + citygml2,
				"--output=" + outGML, "--wkt=" + inWKT);
		String expectedLog = "EPSG:31463";
		assertTrue(err.toString().contains(expectedLog), err.toString() + " should contain " + expectedLog);
		assertTrue(Files.exists(outGML));
		assertTrue(Files.size(outGML) > 300_000);
		assertEquals(22, countBuildings(outGML));
	}

	@Test
	void testExtractRegionWithStandardInput() throws IOException {
		String wktPolygon = "POLYGON((-73.9959209576448 40.73286384885367, -73.996317924579 40.732359794090684, -73.9947515145143 40.7315061442504, -73.99422580154739 40.73214841515045, -73.9959209576448 40.73286384885367))";
		Path citygml = TEST_REPOSITORY.resolve("NewYork.proj/ManhattanSmall.gml");
		InputStream stdin = new ByteArrayInputStream(wktPolygon.getBytes(StandardCharsets.UTF_8));
		System.setIn(stdin);
		assertFalse(Files.exists(outGML));
		new CommandLine(new RegionChooserCLI()).execute("--input=" + citygml, "--output=" + outGML,
				"--wkt=-");
		String expectedLog = "EPSG:32118";
		assertTrue(err.toString().contains(expectedLog), err.toString() + " should contain " + expectedLog);
		assertTrue(Files.exists(outGML));
		assertEquals(2, countBuildings(outGML));
	}

	@Test
	void testExtractRegionWithStandardInputAndStandardOutput() throws IOException {
		String wktPolygon = "POLYGON((-73.99325421344473 40.730897087489666, -73.99359753619864 40.7304702545556, -73.99287870418264 40.7300800049056, -73.99244955074026 40.730592207101864, -73.99325421344473 40.730897087489666))";
		Path citygml = TEST_REPOSITORY.resolve("NewYork.proj/ManhattanSmall.gml");
		InputStream stdin = new ByteArrayInputStream(wktPolygon.getBytes(StandardCharsets.UTF_8));
		System.setIn(stdin);
		Path noOutput = Paths.get("-");
		Files.deleteIfExists(noOutput);
		assertFalse(Files.exists(noOutput));
		new CommandLine(new RegionChooserCLI()).execute("--input=" + citygml, "--output=-", "--wkt=-");
		String expectedLog = "EPSG:32118";
		assertTrue(err.toString().contains(expectedLog), err.toString() + " should contain " + expectedLog);
		String expectedBuilding = "uuid_0547df65-ae80-459e-bb15-c839c1a2e566";
		assertTrue(out.toString().contains(expectedBuilding), out.toString() + " should contain " + expectedBuilding);
		String expectedFooter = "</CityModel>";
		assertTrue(out.toString().contains(expectedFooter), out.toString() + " should contain " + expectedFooter);
		assertFalse(Files.exists(noOutput));
	}

	@Test
	void testExtractRegionWithMissingInput() throws IOException {
		String wktPolygon = "POLYGON((-73.9959209576448 40.73286384885367, -73.996317924579 40.732359794090684, -73.9947515145143 40.7315061442504, -73.99422580154739 40.73214841515045, -73.9959209576448 40.73286384885367))";
		Path citygml = TEST_REPOSITORY.resolve("NewYork.proj/ManhattanSmall.gml");
		Path inWKT = tempDirectory.resolve("polygon.wkt");
		try (BufferedWriter wkt = Files.newBufferedWriter(inWKT)) {
			wkt.write(wktPolygon);
		}
		new CommandLine(new RegionChooserCLI()).execute("--input=" + citygml, "--output=" + outGML,
				"--wkt=-");
		String expectedLog = "EPSG:32118";
		assertTrue(err.toString().contains(expectedLog), err.toString() + " should contain " + expectedLog);
		expectedLog = "Please provide \"POLYGON((x1 y1, x2 y2, ...))\" to standard input.";
		assertTrue(err.toString().contains(expectedLog), err.toString() + " should contain " + expectedLog);
		assertFalse(Files.exists(outGML));
	}

	private long countBuildings(Path outGML) throws IOException {
		return Files.readAllLines(outGML).stream().filter(line -> line.contains("bldg:Building gml:id=")).count();
	}
}

