package speiger.src.coreengine.platform.graphics.opengl.shader;

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;

import org.lwjgl.system.MemoryUtil;

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.collections.objects.maps.impl.hash.Object2ObjectOpenHashMap;
import speiger.src.coreengine.assets.api.ID;

public class ShaderCache {
	public static final ShaderCache INSTANCE = new ShaderCache();
	Map<ID, String> hashCache;
	Path cache;
	
	public void init(Path cache) {
		this.cache = cache;
		if(Files.notExists(cache)) {
			try { Files.createDirectories(cache); }
			catch(IOException e) { 
				e.printStackTrace(); 
				this.cache = null;
			}
		}
		try(BufferedReader reader = Files.newBufferedReader(cache.resolve("cache.json"))) {
			Map<ID, String> knownCache = new Object2ObjectOpenHashMap<>();
			for(JsonElement element : JsonParser.parseReader(reader).getAsJsonObject().getAsJsonArray("shaders")) {
				JsonObject shader = element.getAsJsonObject();
				knownCache.put(ID.of(shader.get("id").getAsString()), shader.get("hash").getAsString());
			}
			hashCache = knownCache;
		}
		catch(Exception e) {
			e.printStackTrace();
			hashCache = new Object2ObjectOpenHashMap<>();
		}
	}
	
	private void save() {
		JsonArray array = new JsonArray();
		hashCache.forEach((K, V) -> {
			JsonObject obj = new JsonObject();
			obj.addProperty("id", K.toString());
			obj.addProperty("hash", V);
			array.add(obj);
		});
		try(JsonWriter writer = new JsonWriter(Files.newBufferedWriter(cache.resolve("cache.json")))) {
			JsonObject obj = new JsonObject();
			obj.add("shaders", array);
			writer.setIndent("\t");
			Streams.write(obj, writer);
		}
		catch(Exception e) { e.printStackTrace(); }
	}
	
	public ByteBuffer get(ID shaderId, Supplier<String> fileHash) {
		if(cache == null) return null;
		String known = hashCache.get(shaderId);
		if(known == null || !Objects.equals(known, fileHash.get())) return null;
		Path path = toFile(shaderId);
		if(Files.notExists(path)) return null;
		try {
			byte[] data = Files.readAllBytes(path);
			return MemoryUtil.memAlloc(data.length).put(data).flip();
		}
		catch(Exception e) { e.printStackTrace(); }
		return null;
	}
	
	public void store(ID shaderId, String fileHash, byte[] shader) {
		Objects.requireNonNull(shaderId);
		Objects.requireNonNull(fileHash);
		Objects.requireNonNull(shader);
		if(cache == null) return;
		hashCache.put(shaderId, fileHash);
		try {
			Path file = toFile(shaderId);
			if(Files.notExists(file.getParent())) Files.createDirectories(file.getParent());
			Files.write(file, shader);
		}
		catch(Exception e) {
			e.printStackTrace();
		}
		save();
	}
	
	public void delete(ID shaderId) {
		if(cache == null) return;
		if(hashCache.remove(shaderId) == null) return;
		try { Files.deleteIfExists(toFile(shaderId)); }
		catch(Exception e) { e.printStackTrace(); }
		save();
	}
	
	private Path toFile(ID id) {
		return cache.resolve(id.domain()).resolve(id.path()+".bin");
	}
}
