import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import javax.imageio.ImageIO;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.internal.Streams;
import com.google.gson.stream.JsonWriter;

import speiger.src.data.Collectable;
import speiger.src.data.Registry;
import speiger.src.data.TaggedImage;


public class Testing {
	
	public static void main(String...args) {
		applyPatches();
		downloadObjects();
	}
	
	private static void applyPatches() {
		patch(getOrigin().resolve("src/main/resources/assets/data/medals.json"), getOrigin().resolve("backup/medal-patch.txt"));
		patch(getOrigin().resolve("src/main/resources/assets/data/pswitches.json"), getOrigin().resolve("backup/pswitch-patch.txt"));
	}
	
	private static void patch(Path baseFile, Path patchFile) {
		Map<String, String> patches = new HashMap<>();
		try {
			for(String entry : Files.readAllLines(patchFile)) {
				String[] split = entry.split(" = ");
				if(split.length != 2) continue;
				patches.put(split[0], split[1]);
			}
			
			JsonObject obj = null;
			try(BufferedReader reader = Files.newBufferedReader(baseFile)) {
				obj = JsonParser.parseReader(reader).getAsJsonObject();
			}
			catch(Exception e) {
				e.printStackTrace();
				return;
			}
			for(JsonElement element : obj.getAsJsonArray("entries")) {
				JsonObject entry = element.getAsJsonObject();
				for(JsonElement image : entry.getAsJsonArray("images")) {
					JsonObject imageObj = image.getAsJsonObject();
					String replacement = patches.get(imageObj.get("url").getAsString().replace(" ", "%20"));
					if(replacement == null) {
						continue;
					}
					imageObj.addProperty("url", replacement);
					System.out.println("Successfully Patched to: "+replacement);
				}
			}
			try(JsonWriter writer = new JsonWriter(Files.newBufferedWriter(baseFile))) {
				writer.setIndent("\t");
				Streams.write(obj, writer);
			}
			catch(Exception e) {
				e.printStackTrace();
			}
			
		}
		catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	private static void downloadObjects() {
		Registry.INSTANCE.load();
		ExecutorService service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
		ExecutorCompletionService<DownloadedImage> queue = new ExecutorCompletionService<>(service);
		try(FileSystem system = FileSystems.newFileSystem(getOrigin().resolve("output/images.zip"), Map.of("create", "true"))) {
			int toDo = 0;
			for(Collectable collect : Registry.INSTANCE.collectables()) {
				for(TaggedImage image : collect.images().values()) {
					Path path = system.getPath(image.fileId().toString().replace("-", "_")+".jpg");
					if(Files.exists(path)) continue;
					queue.submit(new Task(image));
					toDo++;
				}
			}
			int total = toDo;
			service.shutdown();
			while(toDo > 0) {
				Future<DownloadedImage> future = null;
				while((future = queue.poll()) != null) {
					DownloadedImage image = future.get();
					toDo--;
					if(image.id() == null) continue;
					Path path = system.getPath(image.id().toString().replace("-", "_")+".jpg");
					try { Files.write(path, image.data()); }
					catch(Exception e) { e.printStackTrace(); }
					System.out.println("Downloaded: "+(total - toDo)+" / "+total);
				}
				Thread.sleep(50);
			}
		}
		catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	public static void convertObjects() throws Exception {
		Map<String, String> data = Map.of("medals.json", "medal", "panels.json", "panel", "pswitches.json", "pswitch");
		
		try(DirectoryStream<Path> files = Files.newDirectoryStream(Paths.get("D:\\Workspaces\\Java17\\MKW_Map\\src\\main\\resources\\assets\\data"))) {
			List<Path> result = new ArrayList<>();
			files.forEach(result::add);
			for(Path path : result) {
				JsonObject obj = convert(JsonParser.parseReader(Files.newBufferedReader(path)).getAsJsonObject().getAsJsonArray("entries"), data.get(path.getFileName().toString()));
				String name = path.getFileName().toString();
				name = name.substring(0, name.length()-5);
				System.out.println("Testing: "+name);
				try(JsonWriter writer = new JsonWriter(Files.newBufferedWriter(path.getParent().resolve(name+"new.json")))) {
					writer.setIndent("\t");
					Streams.write(obj, writer);
				}
				catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
		catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	private static JsonObject convert(JsonArray inputArray, String type) {
		JsonArray result = new JsonArray();
		for(JsonElement entry : inputArray) {
			JsonObject obj = entry.getAsJsonObject();
			String url = applyHTML(obj.get("name").getAsString(), type);
			Set<String> types = parseArray(obj.getAsJsonArray("imgs"));
			JsonObject newEntry = new JsonObject();
			newEntry.addProperty("name", obj.get("name").getAsString());
			newEntry.addProperty("id", UUID.randomUUID().toString());
			newEntry.add("position", convert(obj.getAsJsonArray("map_position"), obj.getAsJsonArray("map_offset")));
			if(obj.has("end_position")) {
				JsonArray array = obj.getAsJsonArray("end_position");
				JsonObject end = new JsonObject();
				end.addProperty("flying", obj.has("end_flying"));
				end.addProperty("x", array.get(0).getAsInt());
				end.addProperty("y", array.get(1).getAsInt());
				newEntry.add("end", end);
			}
			JsonArray images = new JsonArray();
			if(types.contains("location")) {
				JsonObject imageEntry = new JsonObject();
				imageEntry.addProperty("type", "location");
				imageEntry.addProperty("id", UUID.randomUUID().toString());
				imageEntry.addProperty("url", url+"/location.jpg");
				images.add(imageEntry);
			}
			if(types.contains("title")) {
				JsonObject imageEntry = new JsonObject();
				imageEntry.addProperty("type", "title");
				imageEntry.addProperty("id", UUID.randomUUID().toString());
				imageEntry.addProperty("url", url+"/title.jpg");
				images.add(imageEntry);
			}
			newEntry.add("images", images);
			result.add(newEntry);
		}
		JsonObject resultObj = new JsonObject();
		resultObj.add("entries", result);
		return resultObj;
	}
	
	private static JsonObject convert(JsonArray pos, JsonArray offset) {
		int x = pos.get(0).getAsInt();
		int y = pos.get(1).getAsInt();
		if(offset != null && offset.size() == 2) {
			x += offset.get(0).getAsInt();
			y += offset.get(1).getAsInt();
		}
		JsonObject obj = new JsonObject();
		obj.addProperty("x", x);
		obj.addProperty("y", y);
		return obj;
	}
	
	private static String applyHTML(String input, String type) {
		return "https://mkw.techtangents.net/marker/"+type+"/"+input.replace("#", "").replace("?", "").replace("\"", "");
	}
	
	private static Set<String> parseArray(JsonArray array) {
		Set<String> entry = new HashSet<>();
		for(JsonElement element : array) {
			entry.add(element.toString().replace("\"", ""));
		}
		return entry;
	}
	
	private static Path getOrigin() {
		try {
			Path data = Path.of(Registry.class.getProtectionDomain().getCodeSource().getLocation().toURI()); 
			if(!Files.isDirectory(data)) return data.getParent();
		}
		catch(URISyntaxException e) { e.printStackTrace(); }
		return Path.of("");
	}
	
	public static class Task implements Callable<DownloadedImage> {
		TaggedImage image;
		
		public Task(TaggedImage image) {
			this.image = image;
		}
		
		@Override
		public Testing.DownloadedImage call() throws Exception {
			try {
				ByteArrayOutputStream stream = new ByteArrayOutputStream();
				WritableByteChannel output = Channels.newChannel(stream);
				ReadableByteChannel input = Channels.newChannel(new URI(image.url().replace(" ", "%20")).toURL().openStream());
				ByteBuffer buffer = ByteBuffer.allocate(8192);
				while(input.read(buffer) > 0) {
					output.write(buffer.flip());
					buffer.flip();
				}
				input.close();
				output.close();
				BufferedImage image = ImageIO.read(new ByteArrayInputStream(stream.toByteArray()));
				stream.reset();
				ImageIO.write(image, "jpg", stream);
				
				return new DownloadedImage(this.image.fileId(), stream.toByteArray());
			}
			catch(Exception e) {
				e.printStackTrace();
			}
			return new DownloadedImage(null, null);
		}
		
	}
	
	public record DownloadedImage(UUID id, byte[] data) {
		
	}
	//https://mkw.techtangents.net/marker/pswitch/Collect%20blue%20coins%20along%20a%20scenic%20back%20route!/300_location.jpg
}
