package com.danitheskunk.skunkworks;

import com.danitheskunk.skunkworks.backends.gl.Window;
import com.danitheskunk.skunkworks.gfx.GraphicsBackend;
import com.danitheskunk.skunkworks.gfx.Image;
import org.lwjgl.BufferUtils;

import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.*;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;

import static com.danitheskunk.skunkworks.gfx.GraphicsBackend.OPENGL;

public class Engine {
	private final GraphicsBackend graphicsBackend;
	private final Map<WatchKey, Callable<Boolean>> watchCallbacks;
	private final Map<WatchKey, Path> watchPaths;
	private final WatchService watchService;

	//Constructors
	public Engine() {
		this(OPENGL);
	}

	public Engine(GraphicsBackend graphicsBackend) {
		this.graphicsBackend = graphicsBackend;
		try {
			watchService = FileSystems.getDefault().newWatchService();
		} catch(IOException e) {
			throw new RuntimeException(e);
		}
		watchCallbacks = new HashMap<>();
		watchPaths = new HashMap<>();
	}

	//Methods
	public ByteBuffer loadBytes(String path) {
		ByteBuffer buf;
		byte[] bytes;
		try(FileInputStream fis = new FileInputStream(path)) {
			bytes = fis.readAllBytes();
		} catch(IOException e) {
			throw new RuntimeException(e);
		}
		buf = BufferUtils.createByteBuffer(bytes.length);
		buf.put(bytes);
		buf.flip();
		return buf;
	}

	public Data loadData(String path) {
		return new Data(loadBytes(path));
	}

	public Data loadDataWatched(String path) {
		var data = loadData(path);
		watchFile(path, () -> {
			data.reload(loadBytes(path));
			return true;
		});
		return data;
	}

	public Image loadImage(String path) {
		return new Image(loadBytes(path));
	}

	public IWindow openWindow(Vec2i size, String title) {
		return switch(graphicsBackend) {
			case OPENGL -> new Window(size, title, this);
		};
	}

	public void tick() {
		WatchKey key;
		while((key = watchService.poll()) != null) {
			for(var event : key.pollEvents()) {
				@SuppressWarnings("unchecked") var path =
					((WatchEvent<Path>) event).context();
				if(path.equals(watchPaths.get(key))) {
					try {
						if(watchCallbacks.get(key).call()) {
							key.reset();
						}
					} catch(Exception e) {
						throw new RuntimeException(e);
					}
				}
			}
		}
	}

	//requires engine.tick() to be called regularly
	public void watchFile(String path, Callable<Boolean> callback) {
		var p = Paths.get(path);
		var dir = p.getParent();
		try {
			var key = dir.register(watchService,
				StandardWatchEventKinds.ENTRY_MODIFY
			);
			watchCallbacks.put(key, callback);
			watchPaths.put(key, p.getFileName());
		} catch(IOException e) {
			throw new RuntimeException(e);
		}
	}
}
