package speiger.src.coreengine.rendering.guiOld.renderer;

import java.util.List;
import java.util.Locale;

import org.lwjgl.opengl.GL11;

import speiger.src.collections.floats.lists.FloatArrayList;
import speiger.src.collections.floats.lists.FloatList;
import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.collections.objects.utils.ObjectLists;
import speiger.src.coreengine.rendering.guiOld.components.TextComponent;
import speiger.src.coreengine.rendering.guiOld.helper.Align;
import speiger.src.coreengine.rendering.guiOld.renderer.buffer.DelayedRenderBuffer;
import speiger.src.coreengine.rendering.guiOld.renderer.buffer.RenderBuffer;
import speiger.src.coreengine.rendering.guiOld.renderer.buffer.TranslatedVertexBuilder;
import speiger.src.coreengine.rendering.guiOld.renderer.lexer.Line;
import speiger.src.coreengine.rendering.guiOld.renderer.lexer.TextContext;
import speiger.src.coreengine.rendering.guiOld.renderer.lexer.TextContext.WordContext;
import speiger.src.coreengine.rendering.guiOld.renderer.lexer.TextLexer;
import speiger.src.coreengine.rendering.guiOld.renderer.provider.IFontProvider;
import speiger.src.coreengine.rendering.models.DrawCall;
import speiger.src.coreengine.rendering.tesselationOld.IVertexBuilder;
import speiger.src.coreengine.rendering.tesselationOld.Tesselator;
import speiger.src.coreengine.rendering.tesselationOld.VertexType;
import speiger.src.coreengine.rendering.texturesOld.base.ITexture;
import speiger.src.coreengine.utils.helpers.TextUtil;

public class FontRenderer implements IFontRenderer
{
	public static final String INVALID_SEARCH = new String(" §<");
	public static final int EMPTY = (char)0;
	public static final int SPACE = ' ';
	public static final int TAB = '\t';
	public static final int LINE_SEPERATOR = '\n';
	Tesselator bufferBuilder = new Tesselator(655340);
	
	IFontProvider provider;
	final TextLexer lexer = new TextLexer(this);
	final DelayedRenderBuffer lineBuffer = new DelayedRenderBuffer();
	
	public void setProvider(IFontProvider provider)
	{
		this.provider = provider;
	}
	
	@Override
	public CharInstance getInstance(int codepoint, boolean isBold)
	{
		return provider.getCharacter(codepoint, isBold);
	}
	
	@Override
	public float height()
	{
		return provider.height();
	}
	
	@Override
	public float baseLine()
	{
		return provider.baseLine();
	}
	
	@Override
	public ITexture getTexture()
	{
		return provider.getTexture();
	}
	
	public IFontProvider getProvider()
	{
		return provider;
	}
	
	public void destory()
	{
		if(provider != null)
		{
			provider.destroy();
			provider = null;
		}
	}
	
	public List<DrawCall> renderText(String text, float x, float y, float z)
	{
		if(text.isEmpty()) return ObjectLists.empty();
		List<DrawCall> drawCalls = new ObjectArrayList<>();
		TextContext context = new TextContext(1F, 0);
		List<Line> lines = lexer.evaluateLines(text, context, Float.MAX_VALUE);
		if(lines.isEmpty()) return ObjectLists.empty();
		WordContext effects = context.getEffect();
		int textColor = effects.color;
		float yOffset = 0F;
		IVertexBuilder builder = new TranslatedVertexBuilder(bufferBuilder, x, y, z, 1F);
		bufferBuilder.begin(GL11.GL_TRIANGLES, VertexType.IN_WORLD_UI);
		for(int i = 0,m=lines.size();i<m;i++)
		{
			float xOffset = 9F;
			for(CharInstance letter : lines.get(i).letterIterator())
			{
				xOffset += renderChar(letter, xOffset, yOffset, context.getScale(), effects.italic, effects.flipped, textColor, builder, true);
			}
			yOffset += height() * context.getScale();
		}
		bufferBuilder.finishData();
		if(bufferBuilder.getVertexCount() > 0)
		{
			drawCalls.add(bufferBuilder.getDrawCall(getTexture().getTextureId()));
		}
		bufferBuilder.setOffset(0F, 0F, 0F);
		return drawCalls;
	}
	
	@Override
	public void updateText(TextComponent component)
	{
		RenderBuffer buffer = component.getBuffer();
		buffer.clear();
		if(component.getText().isEmpty())
		{
			return;
		}
		TextContext context = new TextContext(component);
		float boxWidth = component.getBox().getWidth();
		float boxHeight = component.getBox().getHeight();
		List<Line> lines = lexer.evaluateLines(component.getText(), context, component.isWidthLimited() ? boxWidth : Float.MAX_VALUE);
		if(lines.isEmpty())
		{
			return;
		}
		bufferBuilder.begin(GL11.GL_TRIANGLES, VertexType.UI);
		int maxLanes = component.isHeightLimited() ? Math.min((int)(boxHeight / (height() * context.getScale())), lines.size()) : lines.size();
		float maxHeight = maxLanes * height() * context.getScale();
		float maxWidth = 0F;
		float yOffset = component.getVertical().align(boxHeight, maxHeight);
		float startX = component.getHorizontal().align(boxWidth, lines.get(0).getWidth());
		component.getMetadata().setStart(startX, yOffset);
		WordContext effects = context.getEffect();
		while(effects.isNext(0))
		{
			effects = effects.next();
		}
		int textColor = effects.color;
		Float strikeThrough = effects.strike_through ? startX : null;
		Float underline = effects.underline ? startX : -1F;
		if(component.getBackgroundColor() != null)
		{
			Tesselator tes = buffer.start(GL11.GL_TRIANGLES, VertexType.UI).setOffset(0F, 0F, -0.001F);
			addBackground(tes, lines, maxLanes, yOffset, component.getHorizontal(), boxWidth, component.getBackgroundColor(), false);
			tes.setOffset(0F, 0F, 0F);
			buffer.finishShape(0);
		}
		for(int i = 0, index = 0;i < maxLanes;i++)
		{
			float xOffset = component.getHorizontal().align(boxWidth, lines.get(i).getWidth());
			maxWidth = Math.max(maxWidth, lines.get(i).getWidth());
			strikeThrough = effects.strike_through ? xOffset : null;
			underline = effects.underline ? xOffset : null;
			for(CharInstance letter : lines.get(i).letterIterator())
			{
				xOffset += renderChar(letter, xOffset, yOffset, context.getScale(), effects.italic, effects.flipped, textColor, bufferBuilder, false);
				if(effects.isNext(++index))
				{
					WordContext next = effects.next();
					Float newStrike = getValue(effects.strike_through, next.strike_through, xOffset, strikeThrough);
					if(newStrike == null && strikeThrough != null)
					{
						addStrikeThrough(strikeThrough, xOffset - strikeThrough, yOffset, textColor, lineBuffer);
					}
					strikeThrough = newStrike;
					Float newLine = getValue(effects.underline, next.underline, xOffset, strikeThrough);
					if(newLine == null && underline != null)
					{
						addUnderline(underline, xOffset - underline, yOffset, textColor, lineBuffer, false);
					}
					textColor = next.color;
					underline = newLine;
					effects = next;
				}
			}
			if(strikeThrough != null)
			{
				addStrikeThrough(strikeThrough, xOffset - strikeThrough, yOffset, textColor, lineBuffer);
			}
			if(underline != null)
			{
				addUnderline(underline, xOffset - underline, yOffset, textColor, lineBuffer, false);
			}
			yOffset += height() * context.getScale();
			component.getMetadata().addLine(lines.get(i));
		}
		maxWidth /= 2;
		buffer.finishShape(getTexture().getTextureId(), bufferBuilder);
		if(lineBuffer.hasData())
		{
			Tesselator tes = buffer.start(GL11.GL_TRIANGLES, VertexType.UI).offset(0F, 0F, 0.001F);
			lineBuffer.merge(tes);
			tes.setOffset(0F, 0F, 0F);
			buffer.finishShape(0);
		}
	}
	
	protected float renderChar(CharInstance instance, float x, float y, float scale, float italic, boolean flipped, int color, IVertexBuilder buffer, boolean flipPos)
	{
		switch(instance.getCharacter())
		{
			case TAB:
				return provider.getTabWidth() * scale;
			case SPACE:
				return provider.getSpaceWidth() * scale;
		}
		if(instance.getXAdvance() <= 0F)
		{
			return 0F;
		}
		if(flipPos) flipped = !flipped;
		float minX = x;
		float minY = y;
		float maxX = x + (instance.getWidth() * scale);
		float maxY = flipPos ? y - (instance.getHeight() * scale) : y + (instance.getHeight() * scale);
		float minV = flipped ? instance.getMaxV() : instance.getMinV();
		float maxV = flipped ? instance.getMinV() : instance.getMaxV();
		buffer.pos(minX - italic, maxY, 0F).tex(instance.getMinU(), maxV).color4f(color).endVertex();
		buffer.pos(minX + italic, minY, 0F).tex(instance.getMinU(), minV).color4f(color).endVertex();
		buffer.pos(maxX - italic, maxY, 0F).tex(instance.getMaxU(), maxV).color4f(color).endVertex();
		buffer.pos(maxX - italic, maxY, 0F).tex(instance.getMaxU(), maxV).color4f(color).endVertex();
		buffer.pos(minX + italic, minY, 0F).tex(instance.getMinU(), minV).color4f(color).endVertex();
		buffer.pos(maxX + italic, minY, 0F).tex(instance.getMaxU(), minV).color4f(color).endVertex();
		return instance.getXAdvance() * scale;
	}
	
	protected void addBackground(IVertexBuilder tes, List<Line> lines, int maxLines, float yPos, Align align, float width, int color, boolean flipPos)
	{
		for(int i = 0;i < maxLines;i++)
		{
			float lineWidth = lines.get(i).getWidth();
			float xOffset = align.align(width, lineWidth);
			float maxY = flipPos ? yPos - height() : yPos + height();
			tes.pos(xOffset, maxY, 0.0F).tex(0F, 0F).color4f(color).endVertex();
			tes.pos(xOffset, yPos, 0.0F).tex(0F, 0F).color4f(color).endVertex();
			tes.pos(xOffset + lineWidth, maxY, 0.0F).tex(0F, 0F).color4f(color).endVertex();
			tes.pos(xOffset + lineWidth, maxY, 0.0F).tex(0F, 0F).color4f(color).endVertex();
			tes.pos(xOffset, yPos, 0.0F).tex(0F, 0F).color4f(color).endVertex();
			tes.pos(xOffset + lineWidth, yPos, 0.0F).tex(0F, 0F).color4f(color).endVertex();
			yPos = maxY;
		}
	}
	
	protected void addUnderline(float xStart, float width, float yStart, int color, IVertexBuilder buffer, boolean flipPos)
	{
		float minY = yStart + baseLine() + 0.5F;
		float maxY = yStart + baseLine() + 1.5F;
		if(flipPos)
		{
			minY = yStart - baseLine() - 0.5F;
			maxY = yStart - baseLine() - 1.5F;
		}
		buffer.pos(xStart, maxY, 0F).tex(0F, 0F).color4f(color).endVertex();
		buffer.pos(xStart, minY, 0F).tex(0F, 0F).color4f(color).endVertex();
		buffer.pos(xStart + width, maxY, 0F).tex(0F, 0F).color4f(color).endVertex();
		buffer.pos(xStart + width, maxY, 0F).tex(0F, 0F).color4f(color).endVertex();
		buffer.pos(xStart, minY, 0F).tex(0F, 0F).color4f(color).endVertex();
		buffer.pos(xStart + width, minY, 0F).tex(0F, 0F).color4f(color).endVertex();
	}
	
	protected void addStrikeThrough(float xStart, float width, float yStart, int color, IVertexBuilder buffer)
	{
		float minY = yStart + height() / 2.0F;
		float maxY = yStart + height() / 2.0F + 1.4F;
		buffer.pos(xStart, maxY, 0.0F).tex(0F, 0F).color4f(color).endVertex();
		buffer.pos(xStart, minY, 0.0F).tex(0F, 0F).color4f(color).endVertex();
		buffer.pos(xStart + width, maxY, 0.0F).tex(0F, 0F).color4f(color).endVertex();
		buffer.pos(xStart + width, maxY, 0.0F).tex(0F, 0F).color4f(color).endVertex();
		buffer.pos(xStart, minY, 0.0F).tex(0F, 0F).color4f(color).endVertex();
		buffer.pos(xStart + width, minY, 0.0F).tex(0F, 0F).color4f(color).endVertex();
	}
	
	protected Float getValue(boolean oldValue, boolean newValue, Float position, Float oldPosition)
	{
		if((position == null && oldPosition == null) || (position != null && oldPosition != null)) return oldPosition;
		return newValue ? position : null;
	}
	
	@Override
	public String trimToWidth(String text, float limit, boolean reverse)
	{
		StringBuilder builder = new StringBuilder();
		float width = 0F;
		int start = reverse ? text.length() - 1 : 0;
		int direction = reverse ? -1 : 1;
		for(int i = start;i >= 0 && i < text.length() && width < limit;i += direction)
		{
			char letter = text.charAt(i);
			width += width(letter);
			if(width > limit) break;
			if(reverse) builder.insert(0, letter);
			else builder.append(letter);
		}
		return builder.toString();
	}
	
	@Override
	public float width(int codepoint, boolean bold)
	{
		switch(codepoint)
		{
			case SPACE:
				return provider.getSpaceWidth();
			case TAB:
				return provider.getTabWidth();
			default:
				CharInstance instance = getInstance(codepoint, bold);
				return instance == null ? 0F : instance.getXAdvance();
		}
	}
	
	@Override
	public float width(String text, int flags)
	{
		float result = 0.0F;
		float current = 0.0F;
		boolean bold = (flags & BOLD) != 0;
		for(int i = 0;i < text.length();i++)
		{
			char character = text.charAt(i);
			if(LINE_SEPERATOR == character)
			{
				result = Math.max(result, current += provider.getSpaceWidth());
				current = 0.0F;
				continue;
			}
			if(character == '§' && (flags & SPECIAL) != 0 && i + 1 < text.length() && text.charAt(i + 1) == '<')
			{
				String search = TextUtil.searchUntil(text, i + 2, '>', INVALID_SEARCH);
				if(search.length() > 0)
				{
					for(String entry : search.toLowerCase(Locale.ROOT).split(","))
					{
						bold = TextUtil.findFlag(entry, "bold", bold);
					}
					i += search.length() + 2;
					continue;
				}
			}
			current += width(character, bold);
		}
		return Math.max(result, current);
	}
	
	@Override
	public float[] widths(String text, int flags)
	{
		FloatList results = new FloatArrayList();
		results.add(0F);
		float current = 0F;
		int chars = 0;
		boolean bold = (flags & BOLD) != 0;
		float max = 0F;
		for(int i = 0;i < text.length();i++)
		{
			char character = text.charAt(i);
			if(LINE_SEPERATOR == character)
			{
				results.add(current);
				max = Math.max(max, current);
				current = 0F;
				chars = 0;
				continue;
			}
			chars++;
			if(character == '§' && (flags & SPECIAL) != 0 && i + 1 < text.length() && text.charAt(i + 1) == '<')
			{
				String result = TextUtil.searchUntil(text, i + 2, '>', INVALID_SEARCH);
				if(result.length() > 0)
				{
					for(String entry : result.toLowerCase(Locale.ROOT).split(","))
					{
						bold = TextUtil.findFlag(entry, "bold", bold);
					}
					i += result.length() + 2;
					continue;
				}
			}
			current += width(character, bold);
		}
		if(chars++ > 0)
		{
			results.add(current);
			max = Math.max(max, current);
		}
		results.set(0, max);
		return results.toFloatArray();
	}
	
	@Override
	public float height(String text, int flags)
	{
		return widths(text, flags).length - 1 * height();
	}
	
	@Override
	public boolean isCharValid(int codepoint)
	{
		return provider.isCharacterValid(codepoint);
	}
	
	@Override
	public String[] split(String text, float maxWidth, int flags)
	{
		TextContext context = new TextContext(1F, ((flags & IFontRenderer.SPECIAL) != 0 ? 16 : 0) | ((flags & IFontRenderer.BOLD) != 0 ? 1 : 0));
		List<Line> lines = lexer.evaluateLines(text, context, maxWidth);
		String[] array = new String[lines.size()];
		if(context.allowsSpecial())
		{
			int index = 0;
			StringBuilder builder = new StringBuilder();
			WordContext wordContext = context.getEffect();
			while(wordContext.isNext(0))
			{
				builder.append(wordContext.getString(false));
				wordContext = wordContext.next();
			}
			for(int i = 0,m=lines.size();i<m;i++)
			{
				if(i != 0) builder.append(wordContext.getString(true));
				for(CharInstance letter : lines.get(i).letterIterator())
				{
					builder.append(letter.getCharacter());
					if(wordContext.isNext(++index))
					{
						wordContext = wordContext.next();
						builder.append(wordContext.getString(false));
					}
				}
				array[i] = builder.toString();
				builder.setLength(0);
			}
		}
		else
		{
			for(int i = 0,m=lines.size();i<m;i++) array[i] = lines.get(i).getText();
		}
		return array;
	}
}
