package com.danitheskunk.skunkworks.gfx.font;

import com.danitheskunk.skunkworks.IWindow;
import com.danitheskunk.skunkworks.Vec2i;
import com.danitheskunk.skunkworks.gfx.ITexture;
import com.danitheskunk.skunkworks.gfx.Image;
import org.lwjgl.stb.STBTTFontinfo;

import java.nio.ByteBuffer;
import java.util.HashMap;

import static org.lwjgl.stb.STBTruetype.*;

public class FontTTF implements IFont {
	private final HashMap<Integer, Char> chars;
	private final STBTTFontinfo info;
	private final int lineHeight;
	private final float size;
	private final IWindow window;

	public FontTTF(ByteBuffer buffer, float size, IWindow window) {
		int[] ascent = {0};
		int[] descent = {0};
		int[] lineGap = {0};

		this.window = window;
		chars = new HashMap<>();
		info = STBTTFontinfo.create();

		if(!stbtt_InitFont(info, buffer)) {
			throw new IllegalStateException(
				"Failed to initialize font information.");
		}
		//todo: save these
		stbtt_GetFontVMetrics(info, ascent, descent, lineGap);
		lineHeight = lineGap[0];
		this.size = stbtt_ScaleForPixelHeight(info, size);

		//precache ascii characters
		for(int i = 32; i < 128; ++i) {
			cacheChar(i);
		}
	}

	void cacheChar(int ch) {
		if(chars.containsKey(ch)) return;

		int[] width = {0};
		int[] height = {0};
		int[] xoff = {0};
		int[] yoff = {0};
		int[] advanceWidth = {0};
		int[] leftSideBearing = {0};
		ITexture tex;

		var bufmono = stbtt_GetCodepointBitmap(info,
			size,
			size,
			ch,
			width,
			height,
			xoff,
			yoff
		);
		if(bufmono != null) {
			var buf = ByteBuffer.allocate(bufmono.remaining() * 4);
			while(bufmono.hasRemaining()) {
				var b = bufmono.get();
				buf.put((byte) 0xff);
				buf.put((byte) 0xff);
				buf.put((byte) 0xff);
				buf.put(b);
			}
			buf.flip();
			tex = window.loadTexture(new Image(buf,
				new Vec2i(width[0], height[0])
			));
		} else {
			tex = window.loadTexture(new Image(Vec2i.ZERO));
		}
		stbtt_GetCodepointHMetrics(info, ch, advanceWidth, leftSideBearing);
		var c = new Char(tex,
			(int) (advanceWidth[0] * size),
			new Vec2i(xoff[0], yoff[0])
		);
		chars.put(ch, c);
	}

	@Override
	public int getLineHeight(int ch) {
		cacheChar(ch);
		return lineHeight;
	}

	@Override
	public Vec2i getMonospaceSize() {
		throw new RuntimeException("Not a monospace font");
	}

	@Override
	public Vec2i getOffset(int ch) {
		cacheChar(ch);
		return chars.get(ch).off;
	}

	@Override
	public ITexture getTexture(int ch) {
		cacheChar(ch);
		return chars.get(ch).tex;
	}

	@Override
	public int getXAdvance(int ch) {
		cacheChar(ch);
		return chars.get(ch).advance;
	}

	@Override
	public boolean isCP437() {
		return false;
	}

	@Override
	public boolean isMonospace() {
		return false;
	}

	private static class Char {
		final int advance;
		final Vec2i off;
		final ITexture tex;

		Char(ITexture tex, int advance, Vec2i off) {
			this.tex = tex;
			this.advance = advance;
			this.off = off;
		}
	}
}
