package speiger.src.coreengine.rendering.gui.font;

import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL45;

import speiger.src.coreengine.assets.base.IAssetProvider;
import speiger.src.coreengine.math.MathUtils;
import speiger.src.coreengine.rendering.gui.font.glyth.Glyth;
import speiger.src.coreengine.rendering.gui.font.glyth.IGlythSheetInfo;
import speiger.src.coreengine.rendering.textures.base.BaseTexture;
import speiger.src.coreengine.rendering.textures.base.TextureMetadata;
import speiger.src.coreengine.rendering.utils.GLFunctions;
import speiger.src.coreengine.rendering.utils.values.GLDataType;
import speiger.src.coreengine.rendering.utils.values.IGLValue.ITextureComponentFormat;
import speiger.src.coreengine.rendering.utils.values.textures.GLTextureFormat;
import speiger.src.coreengine.rendering.utils.values.textures.GLTextureParameter;
import speiger.src.coreengine.rendering.utils.values.textures.GLTextureValue;

public class FontTexture extends BaseTexture {
	private static final TextureMetadata COLOR = TextureMetadata.builder()
			.externalFormat(GLTextureFormat.RGBA).internalFormat(GLTextureFormat.RGBAI).dataFormat(GLDataType.UNSIGNED_BYTE).mipmapping(false)
			.arguement(GLTextureParameter.MIN_FILTER, GLTextureValue.LINEAR).arguement(GLTextureParameter.MAG_FILTER, GLTextureValue.LINEAR)
			.arguement(GLTextureParameter.WRAP_S, GLTextureValue.CLAMP_TO_EDGE).arguement(GLTextureParameter.WRAP_T, GLTextureValue.CLAMP_TO_EDGE).build();
	private static final TextureMetadata NO_COLOR = COLOR.copy().internalFormat(GLTextureFormat.LUMINANCE).externalFormat(GLTextureFormat.R).specialarguments(FontTexture::swizzle).build();
	
	boolean color;
	int bounds;
	Slot slot;
	
	public FontTexture(int bounds, boolean color) {
		this.bounds = bounds;
		this.color = color;
		slot = new Slot(0, 0, bounds, bounds);
		track();
	}
	
	public static ITextureComponentFormat formatByColor(boolean colored) {
		return colored ? GLTextureFormat.RGBA : GLTextureFormat.R;
	}
	
	public static TextureMetadata metadataByColor(boolean colored) {
		return colored ? COLOR : NO_COLOR;
	}
	
	private static void swizzle(int texture) {
		GL45.glTextureParameterIiv(texture, GLTextureParameter.SWIZZLE_RGBA.glValue(), new int[]{GL11.GL_RED, GL11.GL_RED, GL11.GL_RED, GL11.GL_RED});
	}
	
	@Override
	public void load(IAssetProvider provider) {
		createTexture();
		TextureMetadata metadata = metadataByColor(color);
		metadata.applyArguments(id());
		GLFunctions.prepare2DImage(id(), 1, metadata, bounds, bounds);
	}
	
	public Glyth build(IGlythSheetInfo info) {
		if(color != info.isColored()) return null;
		Slot result = slot.insert(info);
		if(result != null) {
			info.upload(id(), result.x, result.y);
			float minU = (float)(result.x) / (float)bounds;
			float maxU = (float)((result.x) + info.width()) / (float)bounds;
			float minV = (float)(result.y) / (float)bounds;
			float maxV = (float)((result.y) + info.height()) / (float)bounds;
			return new Glyth(id(), minU, minV, maxU, maxV, info.left(), info.right(), info.top(), info.bottom());
		}
		return null;
	}
	
	public boolean colored() { return color; }
	@Override
	public int width() { return bounds; }
	@Override
	public int height() { return bounds; }
	
	public static class Slot {
		final int x;
		final int y;
		final int width;
		final int height;
		Slot[] children = null;
		boolean occupied;
		
		public Slot(int x, int y, int width, int height) {
			this.x = x;
			this.y = y;
			this.width = width;
			this.height = height;
		}
		
		public Slot insert(IGlythSheetInfo info) {
			if(children == null) {
				if(occupied) return null;
				int iw = info.width();
				int ih = info.height();
				if(iw > width || ih > height) return null;
				if(iw == width && ih == height) {
					occupied = true;
					return this;
				}
				int dw = width - iw;
				int dh = height - ih;
				children = new Slot[2];
				if(dw > dh) {
					int offset = MathUtils.ceil(iw / 50F);
					children[0] = new Slot(x, y, iw, height);
					children[1] = new Slot(x + iw + offset, y, dw - offset, height);
				}
				else {
					int offset = MathUtils.ceil(ih / 50F);
					children[0] = new Slot(x, y, width, ih);
					children[1] = new Slot(x, y + ih + offset, width, dh - offset);
				}
				return children[0].insert(info);
			}
			for(int i = 0,m=children.length;i<m;i++) {
				Slot slot = children[i].insert(info);
				if(slot != null) return slot;
			}
			return null;
		}
				
		@Override
		public String toString() {
			return "Slot[x="+x+", y="+y+", w="+width+", h="+height+"]";
		}
	}
}
