package speiger.src.coreengine.rendering.input.window;

import java.util.Iterator;
import java.util.List;
import java.util.Objects;

import org.lwjgl.glfw.GLFW;
import org.lwjgl.system.Callback;
import org.lwjgl.system.CallbackI;

import speiger.src.collections.longs.lists.LongArrayList;
import speiger.src.collections.longs.lists.LongList;
import speiger.src.collections.longs.maps.impl.concurrent.Long2ObjectConcurrentOpenHashMap;
import speiger.src.collections.longs.maps.interfaces.Long2ObjectMap;
import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.coreengine.math.bits.BitUtil;
import speiger.src.coreengine.platform.graphics.api.core.Graphics;
import speiger.src.coreengine.rendering.input.devices.InputDevice;
import speiger.src.coreengine.rendering.input.window.WindowCallback.SimpleReloadFunction;

public class WindowManager {
	Graphics graphcis;
	Long2ObjectMap<Monitor> monitors;
	Long2ObjectMap<Window> windows = new Long2ObjectConcurrentOpenHashMap<>();
	Window activeWindow;
	Window primaryWindow;
	List<WindowCallback> callbacks = new ObjectArrayList<>();
	Callback monitorTracker;
	List<InputDevice> devices = new ObjectArrayList<>();
	
	public void initialize(Graphics graphics) {
		this.graphcis = graphics;
		monitors = Monitor.createMonitors();
		addCallback(this::onMonitorChanged, GLFW::glfwSetMonitorCallback);
	}
	
	private void onMonitorChanged(long monitor, int event) {
		switch(event) {
			case GLFW.GLFW_CONNECTED -> monitors.put(monitor, new Monitor(monitor));
			case GLFW.GLFW_DISCONNECTED -> monitors.remove(monitor);
		}
	}
	
	public WindowBuilder builder() { return new WindowBuilder(this); }
	private Window create(WindowBuilder builder) { return new Window(builder, graphcis); }
	
	public void addDevices(InputDevice...devices) {
		for(InputDevice device : devices) addDevice(device);
	}
	public void addDevice(InputDevice device) {
		devices.add(device);
		windows.values().forEach(device::register);
	}
	public void removeDevice(InputDevice device) { devices.remove(device); }
	public void processDevices(long windowId) {
		for(int i = 0,m=devices.size();i<m;i++) {
			devices.get(i).processInput(windowId);
		}
	}
	
	@SuppressWarnings("unchecked")
	public <T extends CallbackI> WindowCallback addCallback(T listener, SimpleReloadFunction<T> function) {
		WindowCallback callback = new WindowCallback(listener, (SimpleReloadFunction<CallbackI>)function);
		callbacks.add(callback);
		callback.load(0L);
		return callback;
	}
	
	public void removeCallback(WindowCallback callback) {
		if(callbacks.remove(callback)) {
			callback.destroy();
		}
	}
	
	void addWindow(Window window) {
		windows.put(window.id(), window);
		for(int i = 0,m=devices.size();i<m;i++) {
			devices.get(i).register(window);
		}
	}
	
	void updateWindow(long oldId) {
		Window prev = windows.remove(oldId);
		if(prev == null) return;
		windows.put(prev.id(), prev);
	}
	
	void removeWindow(long id) {
		Window window = windows.remove(id);
		if(window == null) return;
		if(window == activeWindow) activeWindow = null;
		if(window == primaryWindow) {
			Iterator<Window> iter = windows.values().iterator();
			primaryWindow = iter.hasNext() ? iter.next() : null;
		}
	}
	
	void updateFocus(Window window, boolean focus) {
		if(focus) this.activeWindow = window;
		else if(activeWindow == window) activeWindow = null;
	}
	
	void updateInputs(long windowId) {
		for(int i = 0,m=devices.size();i<m;i++) {
			InputDevice device = devices.get(i);
			device.reset(windowId);
			device.processInput(windowId);
		}
	}
	
	public void destroy() {
		callbacks.forEach(WindowCallback::destroy);
		callbacks.clear();
	}
	
	public long getActiveWindow() {
		return activeWindow == null ? 0L : activeWindow.id();
	}
	
	public long getPrimaryWindow() {
		return primaryWindow == null ? 0 : primaryWindow.id();
	}
	
	public Window getWindow(long window) {
		return windows.get(window);
	}
	
	public Monitor getMonitor(long id) {
		return monitors.get(id);
	}
	
	public Monitor getPriamryMonitor() {
		return getMonitor(GLFW.glfwGetPrimaryMonitor());
	}
	
	public Monitor getMonitorForWindow(Window window) {
		long current = GLFW.glfwGetWindowMonitor(window.id());
		if(current != 0L) return getMonitor(current);
		int minX = window.x();
		int minY = window.y();
		int maxX = minX + window.screenWidth();
		int maxY = minY + window.screenHeight();
		int largest = 0;
		Monitor mon = null;
		for(Monitor monitor : monitors.values()) {
			int next = monitor.getOverlap(minX, minY, maxX, maxY);
			if(next > largest) {
				largest = next;
				mon = monitor;
			}
		}
		return mon;
	}
	
	public static class WindowBuilder {
		WindowManager manager;
		long monitor;
		LongList windowHints = new LongArrayList();
		VideoMode fullScreenTarget;
		String title = "";
		int width = 640;
		int height = 480;
		int antiAlis = 4;
		boolean vsync = true;
		boolean fpsCap;
		boolean fullScreen;
		boolean maximized;
		boolean resizable = true;
		boolean floating;
		boolean borderless;
		boolean center = true;
		
		private WindowBuilder(WindowManager manager) {
			this.manager = manager;
			monitor = GLFW.glfwGetPrimaryMonitor();
		}

		public WindowBuilder title(String title) {
			this.title = Objects.requireNonNull(title);
			return this;
		}
		
		public WindowBuilder width(int width) {
			this.width = Math.max(1, width);
			return this;
		}
		
		public WindowBuilder height(int height) {
			this.height = Math.max(1, height);
			return this;
		}
		
		public WindowBuilder antialis(int antiAlis) {
			this.antiAlis = Math.max(1, antiAlis);
			return this;
		}
		
		public WindowBuilder fullscreen(boolean fullScreen) {
			this.fullScreen = fullScreen;
			borderless &= !fullScreen;
			floating &= !fullScreen;
			maximized &= !fullScreen;
			center &= !fullScreen;
			return this;
		}
		
		public WindowBuilder maximized(boolean maximized) {
			this.maximized = maximized;
			this.fullScreen &= !maximized;
			center &= !maximized;
			return this;
		}
		
		public WindowBuilder borderless(boolean borderless) {
			this.borderless = borderless;
			fullScreen &= !borderless;
			return this;
		}
		
		public WindowBuilder floating(boolean floating) {
			this.floating = floating;
			fullScreen &= !floating;
			return this;
		}
		
		public WindowBuilder resizeable(boolean resizable) {
			this.resizable = resizable;
			return this;
		}
		
		public WindowBuilder centered(boolean center) {
			this.center = center;
			this.maximized &= !center;
			this.fullScreen &= !center;
			return this;
		}
		
		public WindowBuilder monitor(Monitor monitor) {
			if(monitor == null || monitor.defaultMode() == null) return this;
			this.monitor = monitor.id();
			return this;
		}
		
		public WindowBuilder fullScreenMode(VideoMode mode) {
			this.fullScreenTarget = mode;
			return this;
		}
		
		public WindowBuilder vsync(boolean value) {
			vsync = value;
			return this;
		}
		
		public WindowBuilder fpsCap(boolean cap) {
			fpsCap = cap;
			return this;
		}
		
		public WindowBuilder addCustomHint(int key, int value) {
			windowHints.add(BitUtil.toLong(key, value));
			return this;
		}
				
		public Window build() {
			return manager.create(this);
		}
		
	}
}
