package speiger.src.coreengine;

import java.util.List;
import java.util.function.BiConsumer;

import org.lwjgl.glfw.GLFW;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL43;
import org.lwjgl.system.Configuration;
import org.lwjgl.util.freetype.FreeType;

import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.coreengine.assets.AssetLocation;
import speiger.src.coreengine.assets.AssetManager;
import speiger.src.coreengine.assets.base.IAssetPackage;
import speiger.src.coreengine.assets.base.IAssetProvider;
import speiger.src.coreengine.math.vector.matrix.Matrix4f;
import speiger.src.coreengine.rendering.gui.font.Font;
import speiger.src.coreengine.rendering.gui.font.FontManager;
import speiger.src.coreengine.rendering.gui.font.glyth.Glyth;
import speiger.src.coreengine.rendering.gui.font.glyth.IGlythSheetInfo;
import speiger.src.coreengine.rendering.gui.font.glyth.MissingGlyth;
import speiger.src.coreengine.rendering.gui.font.glyth.UnbakedGlyth.GlythBaker;
import speiger.src.coreengine.rendering.input.devices.FileDrop;
import speiger.src.coreengine.rendering.input.devices.Joystick;
import speiger.src.coreengine.rendering.input.devices.Keyboard;
import speiger.src.coreengine.rendering.input.devices.Mouse;
import speiger.src.coreengine.rendering.input.events.KeyEvent;
import speiger.src.coreengine.rendering.input.events.MouseEvent;
import speiger.src.coreengine.rendering.input.window.Window;
import speiger.src.coreengine.rendering.input.window.WindowManager;
import speiger.src.coreengine.rendering.models.buffers.BufferAttribute;
import speiger.src.coreengine.rendering.models.buffers.VertexArray;
import speiger.src.coreengine.rendering.shader.Shader;
import speiger.src.coreengine.rendering.shader.SimpleShader;
import speiger.src.coreengine.rendering.shader.uniform.base.TextureUniform;
import speiger.src.coreengine.rendering.shader.uniform.vec.Matrix4fUniform;
import speiger.src.coreengine.rendering.tesselation.buffer.IVertexBuilder;
import speiger.src.coreengine.rendering.tesselation.buffer.VertexBuilder;
import speiger.src.coreengine.rendering.tesselation.format.VertexTypes;
import speiger.src.coreengine.rendering.textures.custom.Drawable;
import speiger.src.coreengine.rendering.textures.custom.DynamicTexture;
import speiger.src.coreengine.rendering.utils.GLStateTracker;
import speiger.src.coreengine.rendering.utils.values.GLDataType;
import speiger.src.coreengine.rendering.utils.values.GLMode;
import speiger.src.coreengine.rendering.utils.values.textures.GLTextureFormat;
import speiger.src.coreengine.utils.eventbus.EventBus;
import speiger.src.coreengine.utils.helpers.IOUtils;

public class NewInputTest {
	
	EventBus bus = new EventBus();
	WindowManager manager = new WindowManager();
	AssetManager assets = new AssetManager(List.of(IAssetPackage.of(IOUtils.getBaseLocation())));
	FontManager fonts = new FontManager();
	private Shader<TestShader> shaderTest = Shader.create(TestShader::new);
	private Shader<GuiShader> guiShader = Shader.create(GuiShader::new);
	
	
	public static void main(String[] args) {
		new NewInputTest().run();
	}
	
	private void applyWindowSize(Window window) {
		int scale = 0;
		guiShader.get().proView.set(new Matrix4f().ortho(0, 0, window.width() >> scale, window.height() >> scale, 1000, -1000));		
	}
	
	public void run() {
		Configuration.HARFBUZZ_LIBRARY_NAME.set(FreeType.getLibrary());
		GLFW.glfwInit();
		manager.initialize(null);
		Mouse.INSTANCE.init(bus);
		Keyboard.INSTANCE.init(bus);
		Joystick.INSTANCE.init(manager, bus);
		FileDrop.INSTANCE.init(bus);
		manager.addDevices(Mouse.INSTANCE, Keyboard.INSTANCE, Joystick.INSTANCE, FileDrop.INSTANCE);
		Window window = manager.builder().title("Testing Engine").width(800).height(600).antialis(0).build();
		Window secondWindow = manager.builder().title("Second Window Engine").width(800).height(600).antialis(0).build();
		Thread.ofPlatform().start(() -> drawSecondScreen(secondWindow));
		window.setupContext();
		shaderTest.register();
		guiShader.register();
		assets.addListener(GLStateTracker.instance().shaders);
		assets.addListener(GLStateTracker.TEXTURE_TRACKER);
		assets.addListener(fonts);
		assets.reload();
		
		applyWindowSize(window);
		System.out.println("Testing: "+GL.getCapabilities().OpenGL41);
		System.out.println("Testing: "+Integer.divideUnsigned(-1, 255));

		GL11.glEnable(GL43.GL_MULTISAMPLE);
		int size = 512;
		int half = size >> 1;
		int base = size >> 3;
				
		DynamicTexture texture = new DynamicTexture(size, size, DynamicTexture.DEFAULT_PARAMETERS);
		texture.fill(0, 0, size, size, -1);
		texture.fill(0, 0, half, half, 255, 0, 0, 255);
		texture.fill(half, 0, half, half, 0, 255, 0, 255);
		texture.fill(half, half, half, half, 255, 0, 0, 255);
		texture.fill(0, half, half, half, 0, 0, 255, 255);
		texture.process(true);
		window.visible(true);

		bus.register(MouseEvent.Click.class, T -> {
			System.out.println("Testing: Button="+T.button()+", Click="+T.press()+", X="+T.x()+", Y="+T.y()+", WindowId="+T.windowId()+", WindowName="+manager.getWindow(T.windowId()).title());
		});
		bus.register(KeyEvent.Key.class, T -> {
			if(T.key() == GLFW.GLFW_KEY_T) {
				T.cancel();
//				texture.bind();
				Drawable drawable = new Drawable(GLTextureFormat.RGBA, half, half);
				drawable.fill(0, 0, half, half, 255, 255, 0, 255);
				drawable.upload(texture.id(), base * 3, base * 3, base, base, base * 2, base * 2);
				drawable.close();
			}
			else if(T.key() == GLFW.GLFW_KEY_U) {
				MissingGlyth glyth = new MissingGlyth(18.5F, 4F);
				glyth.bake(new GlythBaker() {
					@Override
					public Glyth bake(IGlythSheetInfo info) {
						int width = info.width();
						int height = info.height();
						info.upload(texture.id(), half - (width >> 1), half - (height >> 1));
						System.out.println("Test3: "+width+", "+height);
						return null;
					}
				});
			}
			else if(T.key() == GLFW.GLFW_KEY_O && T.press()) {
				window.borderless(!window.isBorderless());
			}
		});
		
		VertexBuilder builder = new VertexBuilder(255);
		builder.start(GLMode.TRIANGLES, VertexTypes.TESTING);
		builder.pos(-0.5F, -0.5F, 0).tex(0F, 1F).rgba(-1).endVertex();
		builder.pos(0.5F, -0.5F, 0).tex(1F, 1F).rgba(-1).endVertex();
		builder.pos(0.5F, 0.5F, 0).tex(1F, 0F).rgba(-1).endVertex();
		builder.pos(0.5F, 0.5F, 0).tex(1F, 0F).rgba(-1).endVertex();
		builder.pos(-0.5F, 0.5F, 0).tex(0F, 0F).rgba(-1).endVertex();
		builder.pos(-0.5F, -0.5F, 0).tex(0F, 1F).rgba(-1).endVertex();
		
//		Font font = fonts.createFont(1F);
		TestModel model = new TestModel(builder.getBytes());
		builder.close();
//		TestModel[] guiModel = new TestModel[1];
//		List<GLDraw> draws = new ObjectArrayList<>();
//		TexturedBuffer buffer = new TexturedBuffer((K, V) -> {
//			draws.addAll(K);
//			guiModel[0] = new TestModel(V);
//			System.out.println("Testing: "+V.length+" bytes, "+K.size());
//		});
		
//		String s = "The Quick brown fox Jumps over the Lazy dog";
//		float x = 50;
//		float y = 50;
//		float scale = 1F;
//		y /= scale;
//		TextStyle style = TextStyle.DEFAULT.size(12).bold(false);
//		font.drawText(style, "Testing My Theory", x, y, -1, buffer, scale, true);
//		float offset = font.drawText(style, "The Quick ", 50, y, -1, buffer, scale, false);
//		offset += font.drawText(style, "Brown ", 50+offset, y, -1, buffer, scale, false);
//		offset += font.drawText(style, "§<c=0x24FF00FF>F§<c=r>ox ", 50+offset, y, -1, buffer, scale, false);
//		offset += font.drawText(style, "Jumps ", 50+offset, y, -1, buffer, scale, false);
//		offset += font.drawText(style, "over the Lazy dog", 50+offset, y, -1, buffer, scale, true);
//		font.drawText(s, 50, 50, -1, buffer, true);
//		GLStateTracker tracker = GLStateTracker.instance();
		GL11.glClearColor(0.2F, 0.55F, 0.66F, 1F);
		while(!window.shouldClose()) {
			GLFW.glfwPollEvents();
			if(window.changed()) {
				window.updateViewport();
				applyWindowSize(window);
			}
			
			window.beginFrame();
			GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
			texture.bind();
			shaderTest.bind();
			model.bindArray();
			GLStateTracker.drawArrays(GLMode.TRIANGLES.glValue(), 6);
			model.unbindArray();
//			guiModel[0].bindArray();
//			guiShader.bind();
//			tracker.blend.enable();
//			tracker.blend.setFunction(GLBlendFactor.SRC_ALPHA, GLBlendFactor.ONE_MINUS_SRC_ALPHA);
//			for(GLDraw draw : draws) {
//				tracker.textures.bind(draw.texture());
//				GLStateTracker.drawArrays(GLMode.TRIANGLES.glValue(), draw.startVertex(), draw.vertexCount());
//			}
//			GLStateTracker.instance().blend.disable();
//			guiModel[0].unbindArray();

			window.handleInput();
			window.finishFrame();
			try { Thread.sleep(100); }
			catch(InterruptedException e) { e.printStackTrace(); }
		}
		window.destroy();
		manager.destroy();
	}
	
	public void drawSecondScreen(Window window) {
		window.setupContext();
		assets.addListener(GLStateTracker.instance().shaders);
		Shader<TestShader> secondShader = Shader.createAndRegister(TestShader::new);

		guiShader.register();
		window.visible(true);
		GL11.glClearColor(0.4F, 0.55F, 0.36F, 1F);
		int size = 512;
		int half = size >> 1;
//		int base = size >> 3;
		
		DynamicTexture texture = new DynamicTexture(size, size, DynamicTexture.DEFAULT_PARAMETERS);
		texture.fill(0, 0, size, size, -1);
		texture.fill(0, 0, half, half, 255, 0, 0, 255);
		texture.fill(half, 0, half, half, 0, 255, 0, 255);
		texture.fill(half, half, half, half, 255, 0, 0, 255);
		texture.fill(0, half, half, half, 0, 0, 255, 255);
		texture.process(true);
		
		VertexBuilder builder = new VertexBuilder(255);
		builder.start(GLMode.TRIANGLES, VertexTypes.TESTING);
		builder.pos(-0.5F, -0.5F, 0).tex(0F, 1F).rgba(-1).endVertex();
		builder.pos(0.5F, -0.5F, 0).tex(1F, 1F).rgba(-1).endVertex();
		builder.pos(0.5F, 0.5F, 0).tex(1F, 0F).rgba(-1).endVertex();
		builder.pos(0.5F, 0.5F, 0).tex(1F, 0F).rgba(-1).endVertex();
		builder.pos(-0.5F, 0.5F, 0).tex(0F, 0F).rgba(-1).endVertex();
		builder.pos(-0.5F, -0.5F, 0).tex(0F, 1F).rgba(-1).endVertex();
		
		TestModel model = new TestModel(builder.getBytes());
		builder.close();
		while(!window.shouldClose()) {
			GLFW.glfwPollEvents();
			if(window.changed()) {
				window.updateViewport();
				applyWindowSize(window);
			}
			window.beginFrame();
			GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
			texture.bind();
			secondShader.bind();
			model.bindArray();
			GLStateTracker.drawArrays(GLMode.TRIANGLES.glValue(), 6);
			model.unbindArray();
			
			window.handleInput();
			window.finishFrame();
			try { Thread.sleep(100); }
			catch(InterruptedException e) { e.printStackTrace(); }
		}
		window.destroy();
	}
	
	public static class TexturedBuffer implements Font.TexturedBuffer {
		int previousId = -1;
		int lastVertex = 0;
		List<GLDraw> draws = new ObjectArrayList<>();
		VertexBuilder builder;
		BiConsumer<List<GLDraw>, byte[]> callbacks;
		
		public TexturedBuffer(BiConsumer<List<GLDraw>, byte[]> callbacks) {
			this.callbacks = callbacks;
		}

		@Override
		public IVertexBuilder builderForTexture(int textureId) {
			if(builder == null) {
				builder = new VertexBuilder(100000);
				builder.start(GLMode.TRIANGLES, VertexTypes.TESTING);
				previousId = textureId;
			}
			else if(previousId != textureId) {
				int count = builder.size() - lastVertex;
				draws.add(new GLDraw(previousId, lastVertex, count));
				lastVertex+=count;
				previousId = textureId;
			}
			return builder;
		}

		@Override
		public void end() {
			int count = builder.size() - lastVertex;
			draws.add(new GLDraw(previousId, lastVertex, count));
			callbacks.accept(draws, builder.getBytes());
			builder.close();
		}
		
	}
	
	public static record GLDraw(int texture, int startVertex, int vertexCount) {
		
	}
	
	public static class GuiShader extends SimpleShader {
		public TextureUniform texture = uniforms.addTexture("texture", 0);
		public Matrix4fUniform proView = uniforms.addMat("proViewMatrix", new Matrix4f());
		
		public GuiShader(IAssetProvider provider) {
			super(provider, "gui_shader", AssetLocation.of("shader/testGui/vertex.vs"), AssetLocation.of("shader/testGui/fragment.fs"), "in_position", "in_tex", "in_color");
		}
		
	}
	
	public static class TestShader extends SimpleShader {
		public TextureUniform texture = uniforms.addTexture("texture", 0);
		public TestShader(IAssetProvider provider) {
			super(provider, "testing_shader", AssetLocation.of("shader/testing/vertex.vs"), AssetLocation.of("shader/testing/fragment.fs"), "in_position", "in_tex", "in_color");
		}
	}
	
	public static class TestModel extends VertexArray {
		public TestModel(byte[] data) {
			bind();
			createBuffer(new BufferAttribute(0, 3), new BufferAttribute(1, 2), new BufferAttribute(2, 4, GLDataType.UNSIGNED_BYTE, true)).set(data).unbind();
			unbind();
		}
	}
}
