package glyphs

import (
	"fmt"
	"math"
	"unicode"

	"github.com/bloeys/gglm/gglm"
	"github.com/bloeys/nmage/assets"
	"github.com/bloeys/nmage/buffers"
	"github.com/bloeys/nmage/materials"
	"github.com/bloeys/nmage/meshes"
	"github.com/bloeys/nterm/assert"
	"github.com/bloeys/nterm/consts"
	"github.com/go-gl/gl/v4.1-core/gl"
	"github.com/golang/freetype/truetype"
)

const (
	DefaultGlyphsPerBatch = 4 * 1024

	floatsPerGlyph = 13
	invalidRune    = unicode.ReplacementChar
)

var (
	RuneInfos map[rune]RuneInfo
)

type GlyphRendOpt uint64

const (
	GlyphRendOpt_None    GlyphRendOpt = 0
	GlyphRendOpt_BgColor GlyphRendOpt = 1 << (iota - 1)
	GlyphRendOpt_Underline
	GlyphRendOpt_COUNT GlyphRendOpt = iota
)

type GlyphRendOptValues struct {
	BgColor *gglm.Vec4
}

type GlyphRend struct {
	Atlas    *FontAtlas
	AtlasTex *assets.Texture

	GlyphMesh           *meshes.Mesh
	GlyphFgInstancedBuf buffers.Buffer
	GlyphBgInstancedBuf buffers.Buffer
	GlyphMat            *materials.Material
	TextRunsBuf         []TextRun

	//Luckily slices still work with go-opengl, so for now we will use our slice as an array (no appending)
	GlyphFgCount uint32
	GlyphFgVBO   []float32

	GlyphBgCount uint32
	GlyphBgVBO   []float32

	ScreenWidth  int32
	ScreenHeight int32

	SpacesPerTab uint

	Opts      GlyphRendOpt
	OptValues GlyphRendOptValues
}

func (gr *GlyphRend) SetOpts(opts ...GlyphRendOpt) {

	for _, v := range opts {
		assert.T(v <= (1<<(GlyphRendOpt_COUNT-2)), "Invalid opts of value %d", opts)

		if v == GlyphRendOpt_None {
			gr.Opts = GlyphRendOpt_None
		} else {
			gr.Opts |= v
		}
	}

	gl.ProgramUniform1ui(gr.GlyphMat.ShaderProg.ID, gr.GlyphMat.GetUnifLoc("opts1"), uint32(gr.Opts))
}

func (gr *GlyphRend) HasOpt(opt GlyphRendOpt) bool {
	return gr.Opts&opt != 0
}

// DrawTextOpenGLAbs prepares text that will be drawn on the next GlyphRend.Draw call.
// screenPos is in the range [0,1], where (0,0) is the bottom left.
// Color is RGBA in the range [0,1].
func (gr *GlyphRend) DrawTextOpenGL01String(text string, screenPos *gglm.Vec3, color *gglm.Vec4) gglm.Vec3 {
	screenPos.Set(screenPos.X()*float32(gr.ScreenWidth), screenPos.Y()*float32(gr.ScreenHeight), screenPos.Z())
	return gr.DrawTextOpenGLAbsString(text, screenPos, color)
}

// DrawTextOpenGLAbsString prepares text that will be drawn on the next GlyphRend.Draw call.
// screenPos is in the range ([0,ScreenWidth],[0,ScreenHeight]) where (0,0) is bottom left.
// Color is RGBA in the range [0,1].
func (gr *GlyphRend) DrawTextOpenGLAbsString(text string, screenPos *gglm.Vec3, color *gglm.Vec4) gglm.Vec3 {
	return gr.DrawTextOpenGLAbs([]rune(text), screenPos, color)
}

func (gr *GlyphRend) DrawTextOpenGL01(text []rune, screenPos *gglm.Vec3, color *gglm.Vec4) gglm.Vec3 {
	screenPos.Set(screenPos.X()*float32(gr.ScreenWidth), screenPos.Y()*float32(gr.ScreenHeight), screenPos.Z())
	return gr.DrawTextOpenGLAbs(text, screenPos, color)
}

// DrawTextOpenGLAbsString prepares text that will be drawn on the next GlyphRend.Draw call.
// screenPos is in the range ([0,ScreenWidth],[0,ScreenHeight]) where (0,0) is bottom left.
// Color is RGBA in the range [0,1].
func (gr *GlyphRend) DrawTextOpenGLAbs(text []rune, startPos *gglm.Vec3, color *gglm.Vec4) gglm.Vec3 {

	runs := gr.TextRunsBuf[:]
	gr.GetTextRuns(text, &runs)
	if runs == nil {
		return *startPos
	}

	drawPos := startPos.Clone()
	lineHeightF32 := float32(gr.Atlas.LineHeight)
	fgBufIndex, bgBufIndex := gr.getFgAndBgBufIndices()
	for runIndex := 0; runIndex < len(runs); runIndex++ {

		run := &runs[runIndex]
		prevRune := invalidRune

		screenWidthF32 := float32(gr.ScreenWidth)
		if run.IsLtr {

			for i := 0; i < len(run.Runes); i++ {

				if run.Runes[i] == '\n' {
					drawPos.SetXYZ(startPos.X(), drawPos.Y()-lineHeightF32, startPos.Z())
				}

				gr.drawRune(run, i, prevRune, drawPos, color, lineHeightF32, &fgBufIndex, &bgBufIndex)
				prevRune = run.Runes[i]

				//Wrap
				if drawPos.X()+gr.Atlas.SpaceAdvance >= screenWidthF32 {

					drawPos.SetXYZ(startPos.X(), drawPos.Y()-lineHeightF32, startPos.Z())
					// startPos.SetY(startPos.Y() - lineHeightF32)
					// *drawPos = *startPos.Clone()
				}
			}

		} else {

			for i := len(run.Runes) - 1; i >= 0; i-- {

				if run.Runes[i] == '\n' {
					drawPos.SetXYZ(startPos.X(), drawPos.Y()-lineHeightF32, startPos.Z())
				}

				gr.drawRune(run, i, prevRune, drawPos, color, lineHeightF32, &fgBufIndex, &bgBufIndex)
				prevRune = run.Runes[i]

				//Wrap
				if drawPos.X()+gr.Atlas.SpaceAdvance >= screenWidthF32 {
					drawPos.SetXYZ(startPos.X(), drawPos.Y()-lineHeightF32, startPos.Z())
					// startPos.SetY(startPos.Y() - lineHeightF32)
					// *drawPos = *startPos.Clone()
				}
			}

		}

		if consts.Mode_Debug && PrintPositions {
			println("")
		}
	}

	return *drawPos
}

func (gr *GlyphRend) DrawTextOpenGLAbsRect(text []rune, rectTopLeft *gglm.Vec3, rectBotRight *gglm.Vec2, color *gglm.Vec4) gglm.Vec3 {

	runs := gr.TextRunsBuf[:]
	gr.GetTextRuns(text, &runs)
	if runs == nil {
		return *rectTopLeft
	}

	drawPos := rectTopLeft.Clone()
	lineHeightF32 := float32(gr.Atlas.LineHeight)
	fgBufIndex, bgBufIndex := gr.getFgAndBgBufIndices()
	for runIndex := 0; runIndex < len(runs); runIndex++ {

		run := &runs[runIndex]
		prevRune := invalidRune

		if run.IsLtr {

			for i := 0; i < len(run.Runes); i++ {

				if run.Runes[i] == '\n' {
					drawPos.SetXYZ(rectTopLeft.X(), drawPos.Y()-lineHeightF32, rectTopLeft.Z())
				}

				gr.drawRune(run, i, prevRune, drawPos, color, lineHeightF32, &fgBufIndex, &bgBufIndex)
				prevRune = run.Runes[i]

				//Wrap
				if drawPos.X()+gr.Atlas.SpaceAdvance >= rectBotRight.X() {

					drawPos.SetXYZ(rectTopLeft.X(), drawPos.Y()-lineHeightF32, rectTopLeft.Z())
					// rectTopLeft.SetY(rectTopLeft.Y() - lineHeightF32)
					// *drawPos = *rectTopLeft.Clone()
				}
			}

		} else {

			for i := len(run.Runes) - 1; i >= 0; i-- {

				if run.Runes[i] == '\n' {
					drawPos.SetXYZ(rectTopLeft.X(), drawPos.Y()-lineHeightF32, rectTopLeft.Z())
				}

				gr.drawRune(run, i, prevRune, drawPos, color, lineHeightF32, &fgBufIndex, &bgBufIndex)
				prevRune = run.Runes[i]

				//Wrap
				if drawPos.X()+gr.Atlas.SpaceAdvance >= rectBotRight.X() {
					drawPos.SetXYZ(rectTopLeft.X(), drawPos.Y()-lineHeightF32, rectTopLeft.Z())
					// rectTopLeft.SetY(rectTopLeft.Y() - lineHeightF32)
					// *drawPos = *rectTopLeft.Clone()
				}
			}

		}

		if consts.Mode_Debug && PrintPositions {
			println("")
		}
	}

	return *drawPos
}

func (gr *GlyphRend) DrawTextOpenGLAbsRectWithStartPos(text []rune, startPos, rectTopLeft *gglm.Vec3, rectBotRight *gglm.Vec2, color *gglm.Vec4) gglm.Vec3 {

	runs := gr.TextRunsBuf[:]
	gr.GetTextRuns(text, &runs)
	if runs == nil {
		return *startPos
	}

	drawPos := startPos.Clone()
	lineHeightF32 := float32(gr.Atlas.LineHeight)
	fgBufIndex, bgBufIndex := gr.getFgAndBgBufIndices()
	for runIndex := 0; runIndex < len(runs); runIndex++ {

		run := &runs[runIndex]
		prevRune := invalidRune

		if run.IsLtr {

			for i := 0; i < len(run.Runes); i++ {

				if run.Runes[i] == '\n' {
					drawPos.SetXYZ(rectTopLeft.X(), drawPos.Y()-lineHeightF32, rectTopLeft.Z())
				}

				gr.drawRune(run, i, prevRune, drawPos, color, lineHeightF32, &fgBufIndex, &bgBufIndex)
				prevRune = run.Runes[i]

				//Wrap
				if drawPos.X()+gr.Atlas.SpaceAdvance >= rectBotRight.X() {

					drawPos.SetXYZ(rectTopLeft.X(), drawPos.Y()-lineHeightF32, rectTopLeft.Z())
					// rectTopLeft.SetY(rectTopLeft.Y() - lineHeightF32)
					// *drawPos = *rectTopLeft.Clone()
				}
			}

		} else {

			for i := len(run.Runes) - 1; i >= 0; i-- {

				if run.Runes[i] == '\n' {
					drawPos.SetXYZ(rectTopLeft.X(), drawPos.Y()-lineHeightF32, rectTopLeft.Z())
				}

				gr.drawRune(run, i, prevRune, drawPos, color, lineHeightF32, &fgBufIndex, &bgBufIndex)
				prevRune = run.Runes[i]

				//Wrap
				if drawPos.X()+gr.Atlas.SpaceAdvance >= rectBotRight.X() {
					drawPos.SetXYZ(rectTopLeft.X(), drawPos.Y()-lineHeightF32, rectTopLeft.Z())
					// rectTopLeft.SetY(rectTopLeft.Y() - lineHeightF32)
					// *drawPos = *rectTopLeft.Clone()
				}
			}

		}

		if consts.Mode_Debug && PrintPositions {
			println("")
		}
	}

	return *drawPos
}

func (gr *GlyphRend) getFgAndBgBufIndices() (fgBufIndex, bgBufIndex uint32) {
	return gr.GlyphFgCount * floatsPerGlyph, gr.GlyphBgCount * floatsPerGlyph
}

// @Debug
var PrintPositions bool

func (gr *GlyphRend) GridSize() (w, h int64) {
	w = int64(gr.ScreenWidth) / int64(gr.Atlas.SpaceAdvance)
	h = int64(gr.ScreenHeight) / int64(gr.Atlas.LineHeight)
	return w, h
}

func (gr *GlyphRend) ScreenPosToGridPos(x, y float32) (gridX, gridY float32) {
	gridX = floorF32(x / gr.Atlas.SpaceAdvance)
	gridY = floorF32(y / gr.Atlas.LineHeight)
	return gridX, gridY
}

func (gr *GlyphRend) drawRune(run *TextRun, i int, prevRune rune, pos *gglm.Vec3, color *gglm.Vec4, lineHeightF32 float32, glyphFgBufIndex, glyphBgBufIndex *uint32) {

	r := run.Runes[i]
	if r == '\t' {
		pos.AddX(gr.Atlas.SpaceAdvance * float32(gr.SpacesPerTab))
		return
	}

	var g FontAtlasGlyph
	if run.IsLtr {
		if i < len(run.Runes)-1 {
			//start or middle of sentence
			g = GlyphFromRunes(gr.Atlas.Glyphs, r, prevRune, run.Runes[i+1])
		} else {
			//Last character
			g = GlyphFromRunes(gr.Atlas.Glyphs, r, prevRune, invalidRune)
		}
	} else {
		if i > 0 {
			//start or middle of sentence
			g = GlyphFromRunes(gr.Atlas.Glyphs, r, run.Runes[i-1], prevRune)
		} else {
			//Last character
			g = GlyphFromRunes(gr.Atlas.Glyphs, r, invalidRune, prevRune)
		}
	}

	//We must adjust char positioning according to: https://developer.apple.com/library/archive/documentation/TextFonts/Conceptual/CocoaTextArchitecture/Art/glyph_metrics_2x.png
	drawPos := *pos
	//The flooring to an integer pixel must happen AFTER the (potentially) fractional adjustments have been made.
	//This is what the truetype face.Rasterizer does and seems to give good results. Do NOT floor bearing/descent first.
	drawPos.SetX(floorF32(drawPos.X() + g.BearingX))
	drawPos.SetY(floorF32(drawPos.Y() - g.Descent))

	if consts.Mode_Debug && PrintPositions {
		oldXY := gglm.NewVec2(pos.X(), pos.Y())
		newXY := gglm.NewVec2(drawPos.X(), drawPos.Y())
		fmt.Printf("char=%s; PosBefore=%s, PosAfter=%s; Bearing/Decent=(%f, %f)\n", string(r), oldXY.String(), newXY.String(), g.BearingX, g.Descent)
	}

	//Add the glyph information to the vbo
	if gr.HasOpt(GlyphRendOpt_BgColor) {
		// UV
		gr.GlyphBgVBO[*glyphBgBufIndex+0] = -1
		gr.GlyphBgVBO[*glyphBgBufIndex+1] = -1
		*glyphBgBufIndex += 2

		//UVSize
		gr.GlyphBgVBO[*glyphBgBufIndex+0] = 0
		gr.GlyphBgVBO[*glyphBgBufIndex+1] = 0
		*glyphBgBufIndex += 2

		//Color
		gr.GlyphBgVBO[*glyphBgBufIndex+0] = gr.OptValues.BgColor.R()
		gr.GlyphBgVBO[*glyphBgBufIndex+1] = gr.OptValues.BgColor.G()
		gr.GlyphBgVBO[*glyphBgBufIndex+2] = gr.OptValues.BgColor.B()
		gr.GlyphBgVBO[*glyphBgBufIndex+3] = gr.OptValues.BgColor.A()
		*glyphBgBufIndex += 4

		//Model Pos
		gr.GlyphBgVBO[*glyphBgBufIndex+0] = pos.X()
		gr.GlyphBgVBO[*glyphBgBufIndex+1] = pos.Y()
		gr.GlyphBgVBO[*glyphBgBufIndex+2] = pos.Z()
		*glyphBgBufIndex += 3

		//Model Scale
		gr.GlyphBgVBO[*glyphBgBufIndex+0] = gr.Atlas.SpaceAdvance
		gr.GlyphBgVBO[*glyphBgBufIndex+1] = lineHeightF32
		*glyphBgBufIndex += 2

		gr.GlyphBgCount++
		if gr.GlyphBgCount == DefaultGlyphsPerBatch {
			gr.Draw()
			*glyphBgBufIndex = 0
		}
	}

	//UV
	gr.GlyphFgVBO[*glyphFgBufIndex+0] = g.U
	gr.GlyphFgVBO[*glyphFgBufIndex+1] = g.V
	*glyphFgBufIndex += 2

	//UVSize
	gr.GlyphFgVBO[*glyphFgBufIndex+0] = g.SizeU
	gr.GlyphFgVBO[*glyphFgBufIndex+1] = g.SizeV
	*glyphFgBufIndex += 2

	//Color
	gr.GlyphFgVBO[*glyphFgBufIndex+0] = color.R()
	gr.GlyphFgVBO[*glyphFgBufIndex+1] = color.G()
	gr.GlyphFgVBO[*glyphFgBufIndex+2] = color.B()
	gr.GlyphFgVBO[*glyphFgBufIndex+3] = color.A()
	*glyphFgBufIndex += 4

	//Model Pos
	gr.GlyphFgVBO[*glyphFgBufIndex+0] = drawPos.X()
	gr.GlyphFgVBO[*glyphFgBufIndex+1] = drawPos.Y()
	gr.GlyphFgVBO[*glyphFgBufIndex+2] = drawPos.Z()
	*glyphFgBufIndex += 3

	//Model Scale
	gr.GlyphFgVBO[*glyphFgBufIndex+0] = g.SizeU
	gr.GlyphFgVBO[*glyphFgBufIndex+1] = g.SizeV
	*glyphFgBufIndex += 2

	pos.AddX(g.Advance)

	//If we fill the buffer we issue a draw call
	gr.GlyphFgCount++
	if gr.GlyphFgCount == DefaultGlyphsPerBatch {
		gr.Draw()
		*glyphFgBufIndex = 0
	}
}

// func roundF32(x float32) float32 {
// 	return float32(math.Round(float64(x)))
// }

// func ceilF32(x float32) float32 {
// 	return float32(math.Ceil(float64(x)))
// }

func floorF32(x float32) float32 {
	return float32(math.Floor(float64(x)))
}

type TextRun struct {
	Runes []rune
	IsLtr bool
}

func (gr *GlyphRend) GetTextRuns(rs []rune, textRunsBuf *[]TextRun) {

	if len(rs) == 0 {
		return
	}

	runs := textRunsBuf
	currRunScript := RuneInfos[rs[0]].ScriptTable

	//TODO: We need to detect neutral characters through BiDi category, not being in common
	//TODO: Diacritics go into things like 'Category_Mn' and don't necessairly follow the parent script (e.g. Arabic diacritics are NOT in unicode.Arabic).
	//They should be part of the same run but right now we split them into their own run.
	runStartIndex := 0
	for i := 1; i < len(rs); i++ {

		r := rs[i]
		ri := RuneInfos[r]
		//A run is a set of characters using the same script (and other metrics) minus leading/trailing neutral characters
		if ri.ScriptTable == currRunScript || ri.ScriptTable == unicode.Common {
			continue
		}

		//We reached a new run so count trailing neutrals to be removed from this run
		newRunRunes := rs[runStartIndex:i]
		trailingCommonsCount := 0
		for j := len(newRunRunes) - 1; j >= 0; j-- {
			if !unicode.Is(unicode.Common, newRunRunes[j]) {
				break
			}
			trailingCommonsCount++
		}

		//If we have a run without trailing neutrals or had a run of just neutrals (e.g. starting sentence with spaces)
		//then the full run is added, otherwise we slice the run to put neturals in a separate run
		if trailingCommonsCount == 0 || len(newRunRunes) == trailingCommonsCount {
			*runs = append(*runs, TextRun{Runes: newRunRunes})
		} else {
			*runs = append(*runs,
				TextRun{Runes: newRunRunes[:len(newRunRunes)-trailingCommonsCount]}, TextRun{Runes: newRunRunes[len(newRunRunes)-trailingCommonsCount:]})
		}

		//The removed neutrals are included as the start of the new run
		runStartIndex = i
		currRunScript = ri.ScriptTable
	}

	*runs = append(*runs, TextRun{Runes: rs[runStartIndex:]})

	//Detect directionality of each run
	for i := 0; i < len(*runs); i++ {

		run := &(*runs)[i]
		bidiCat := BidiCategory_L
		for _, r := range run.Runes {
			if !unicode.Is(unicode.Common, r) {
				bidiCat = RuneInfos[r].BidiCat
				break
			}
		}
		run.IsLtr = !(bidiCat == BidiCategory_R || bidiCat == BidiCategory_AL || bidiCat == BidiCategory_RLE || bidiCat == BidiCategory_RLO || bidiCat == BidiCategory_RLI || bidiCat == BidiCategory_RLM)
	}
}

// GlyphFromRunes does shaping where it selects the proper rune based (e.g. end Alef) on the surrounding runes
func GlyphFromRunes(glyphTable map[rune]FontAtlasGlyph, curr, prev, next rune) FontAtlasGlyph {

	//PERF: Map access times are absolute garbage to the point that ~85%+ of the runtime of this func
	//is spent reading from maps :). Using nSet or fMap or similar would be a lot better.
	type PosCtx int
	const (
		PosCtx_start PosCtx = iota
		PosCtx_mid
		PosCtx_end
		PosCtx_isolated
	)

	prevIsValid := prev != invalidRune
	nextIsValid := next != invalidRune

	//Isolated case
	if !prevIsValid && !nextIsValid {
		return glyphTable[curr]
	}

	ri := RuneInfos[curr]
	if ri.JoinType == JoiningType_None || ri.JoinType == JoiningType_Transparent {
		return glyphTable[curr]
	}

	prevJoinType := RuneInfos[prev].JoinType
	joinWithRight := prevIsValid &&
		(prevJoinType == JoiningType_Dual || prevJoinType == JoiningType_Left || prevJoinType == JoiningType_Causing) &&
		(ri.JoinType == JoiningType_Dual || ri.JoinType == JoiningType_Right)

	nextJoinType := RuneInfos[next].JoinType
	joinWithLeft := nextIsValid &&
		(nextJoinType == JoiningType_Dual || nextJoinType == JoiningType_Right || nextJoinType == JoiningType_Causing) &&
		(ri.JoinType == JoiningType_Dual || ri.JoinType == JoiningType_Left)

	var ctx PosCtx
	if joinWithRight && joinWithLeft {
		ctx = PosCtx_mid
	} else if joinWithLeft {
		ctx = PosCtx_start
	} else if joinWithRight {
		ctx = PosCtx_end
	} else {
		ctx = PosCtx_isolated
	}

	//This is only needed for Arabic (I think)
	switch ctx {
	case PosCtx_start:

		for i := 0; i < len(ri.EquivalentRunes); i++ {

			otherRune := ri.EquivalentRunes[i]
			otherDecompTag := RuneInfos[otherRune].DecompTag
			if otherDecompTag == DecompTag_initial {
				curr = otherRune
				break
			}
		}

	case PosCtx_mid:

		for i := 0; i < len(ri.EquivalentRunes); i++ {

			otherRune := ri.EquivalentRunes[i]
			otherDecompTag := RuneInfos[otherRune].DecompTag
			if otherDecompTag == DecompTag_medial {
				curr = otherRune
				break
			}
		}

	case PosCtx_end:

		for i := 0; i < len(ri.EquivalentRunes); i++ {

			otherRune := ri.EquivalentRunes[i]
			otherDecompTag := RuneInfos[otherRune].DecompTag
			if otherDecompTag == DecompTag_final {
				curr = otherRune
				break
			}
		}
	}

	return glyphTable[curr]
}

func (gr *GlyphRend) Draw() {

	if gr.GlyphFgCount == 0 && gr.GlyphBgCount == 0 {
		return
	}

	// Set common GPU settings for both Fg and Bg
	gr.GlyphMat.Bind()
	gl.Disable(gl.DEPTH_TEST) //We need to disable depth testing so that nearby characters don't occlude each other

	// We must draw Bg BEFORE foreground because they write to the same pixels.
	// Drawing everything in one instance buffer/batch doesn't work because the order in which instances are drawn in a batch is not guaranteed,
	// which means a background might be drawn on top of a foreground glyph.
	if gr.GlyphBgCount > 0 {

		gl.BindVertexArray(gr.GlyphBgInstancedBuf.VAOID)
		gl.BindBuffer(gl.ARRAY_BUFFER, gr.GlyphBgInstancedBuf.BufID)
		gl.BufferSubData(gl.ARRAY_BUFFER, 0, int(gr.GlyphBgCount*floatsPerGlyph)*4, gl.Ptr(&gr.GlyphBgVBO[:gr.GlyphBgCount*floatsPerGlyph][0]))

		gl.DrawElementsInstanced(gl.TRIANGLES, gr.GlyphBgInstancedBuf.IndexBufCount, gl.UNSIGNED_INT, gl.PtrOffset(0), int32(gr.GlyphBgCount))
		gr.GlyphBgCount = 0
	}

	if gr.GlyphFgCount > 0 {
		gl.BindVertexArray(gr.GlyphFgInstancedBuf.VAOID)
		gl.BindBuffer(gl.ARRAY_BUFFER, gr.GlyphFgInstancedBuf.BufID)
		gl.BufferSubData(gl.ARRAY_BUFFER, 0, int(gr.GlyphFgCount*floatsPerGlyph)*4, gl.Ptr(&gr.GlyphFgVBO[:gr.GlyphFgCount*floatsPerGlyph][0]))

		gl.DrawElementsInstanced(gl.TRIANGLES, gr.GlyphFgInstancedBuf.IndexBufCount, gl.UNSIGNED_INT, gl.PtrOffset(0), int32(gr.GlyphFgCount))
		gr.GlyphFgCount = 0
	}

	gl.Enable(gl.DEPTH_TEST)
}

// SetFace updates the underlying font atlas used by the glyph renderer.
// The current atlas is unchanged if there is an error
func (gr *GlyphRend) SetFace(fontOptions *truetype.Options) error {

	face := truetype.NewFace(gr.Atlas.Font, fontOptions)
	newAtlas, err := NewFontAtlasFromFont(gr.Atlas.Font, face, uint(fontOptions.Size))
	if err != nil {
		return err
	}

	gr.Atlas = newAtlas
	gr.updateFontAtlasTexture()
	return nil
}

func (gr *GlyphRend) SetFontFromFile(fontFile string, fontOptions *truetype.Options) error {

	atlas, err := NewFontAtlasFromFile(fontFile, fontOptions)
	if err != nil {
		return err
	}

	gr.Atlas = atlas
	gr.updateFontAtlasTexture()
	return nil
}

// updateFontAtlasTexture uploads the texture representing the font atlas to the GPU
// and updates the GlyphRend.AtlasTex field.
//
// Any old textures are deleted
func (gr *GlyphRend) updateFontAtlasTexture() error {

	//Clean old texture and load new texture
	if gr.AtlasTex != nil {
		gl.DeleteTextures(1, &gr.AtlasTex.TexID)
		gr.AtlasTex = nil
	}

	atlasTex, err := assets.LoadTextureInMemImg(gr.Atlas.Img, nil)
	if err != nil {
		return err
	}
	gr.AtlasTex = &atlasTex

	gl.BindTexture(gl.TEXTURE_2D, atlasTex.TexID)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
	gl.BindTexture(gl.TEXTURE_2D, 0)

	//Update material
	gr.GlyphMat.DiffuseTex = gr.AtlasTex.TexID
	// gr.GlyphMat.SetUnifFloat32("spaceAdv", gr.Atlas.SpaceAdvance)
	// gr.GlyphMat.SetUnifFloat32("lineHeight", gr.Atlas.LineHeight)

	return nil
}

func (gr *GlyphRend) SetScreenSize(screenWidth, screenHeight int32) {

	gr.ScreenWidth = screenWidth
	gr.ScreenHeight = screenHeight

	//The projection matrix fits the screen size. This is needed so we can size and position characters correctly.
	projMtx := gglm.Ortho(0, float32(screenWidth), float32(screenHeight), 0, 0.1, 20)
	viewMtx := gglm.LookAt(gglm.NewVec3(0, 0, -10), gglm.NewVec3(0, 0, 0), gglm.NewVec3(0, 1, 0))
	projViewMtx := projMtx.Mul(viewMtx)

	gr.GlyphMat.SetUnifMat4("projViewMat", &projViewMtx.Mat4)
}

func NewGlyphRend(fontFile string, fontOptions *truetype.Options, screenWidth, screenHeight int32) (*GlyphRend, error) {

	var err error
	if RuneInfos == nil {
		RuneInfos, err = ParseUnicodeData("./unicode-data-13.txt", "./arabic-shaping-13.txt")
		if err != nil {
			return nil, err
		}
	}

	gr := &GlyphRend{
		GlyphFgCount: 0,
		GlyphFgVBO:   make([]float32, floatsPerGlyph*DefaultGlyphsPerBatch),

		GlyphBgCount: 0,
		GlyphBgVBO:   make([]float32, floatsPerGlyph*DefaultGlyphsPerBatch),
		TextRunsBuf:  make([]TextRun, 0, 20),
		SpacesPerTab: 4,

		Opts: GlyphRendOpt_None,
		OptValues: GlyphRendOptValues{
			BgColor: gglm.NewVec4(0, 0, 0, 0),
		},
	}

	//Create glyph mesh
	gr.GlyphMesh = &meshes.Mesh{
		Name: "glypQuad",

		//VertPos only. Instanced attributes are stored separately
		Buf: buffers.NewBuffer(
			buffers.Element{ElementType: buffers.DataTypeVec3},
		),
	}

	//The quad must be anchored at the bottom-left, not it's center (i.e. bottom-left vertex must be at 0,0)
	gr.GlyphMesh.Buf.SetData([]float32{
		0, 0, 0,
		1, 0, 0,
		0, 1, 0,
		1, 1, 0,
	})

	gr.GlyphMesh.Buf.SetIndexBufData([]uint32{
		0, 1, 2,
		1, 3, 2,
	})

	//Setup material
	gr.GlyphMat = materials.NewMaterial("glyphMat", "./res/shaders/glyph.glsl")

	//With the material ready we can generate the atlas
	gr.Atlas, err = NewFontAtlasFromFile(fontFile, fontOptions)
	if err != nil {
		return nil, err
	}

	err = gr.updateFontAtlasTexture()
	if err != nil {
		return nil, err
	}

	// Multiple VBOs under one VAO, one VBO for vertex data (vertex attrib 0), and one VBO for forground data (vertex attrib 1+)
	gr.GlyphFgInstancedBuf = buffers.NewBuffer()

	// Background data uses another VAO because the bg buffer has the same type (ARRAY_BUFFER) as fg data, and two
	// buffers of the same type can't go under VAO+attrib array locations
	gr.GlyphBgInstancedBuf = buffers.NewBuffer()

	// Create instanced buffers and set their vertex attributes.
	glyphMeshVertPosLayoutEle := gr.GlyphMesh.Buf.GetLayout()[0]
	setupGlyphInstanceBuf := func(instanceBuf *buffers.Buffer, vbo *[]float32) {

		instanceBuf.SetLayout(
			buffers.Element{ElementType: buffers.DataTypeVec2}, //UV0
			buffers.Element{ElementType: buffers.DataTypeVec2}, //UVSize
			buffers.Element{ElementType: buffers.DataTypeVec4}, //Color
			buffers.Element{ElementType: buffers.DataTypeVec3}, //ModelPos
			buffers.Element{ElementType: buffers.DataTypeVec2}, //ModelScale
		)

		// Use the index buffer based on the mesh data
		instanceBuf.IndexBufID = gr.GlyphMesh.Buf.IndexBufID
		instanceBuf.SetIndexBufData([]uint32{
			0, 1, 2,
			1, 3, 2,
		})

		// Set index buf data unbinds at the end so we re-bind here
		instanceBuf.Bind()

		// Set vertex attribute zero to use the buffer of the glyph mesh, which contains onlt vertex positions.
		// Since all instances have the same vertex positions we only send this data in the first vertex and all instances refer
		// to the position information of the first vertex. This is done by setting an attrib divisor of zero for this attribute
		gl.BindBuffer(gl.ARRAY_BUFFER, gr.GlyphMesh.Buf.BufID)
		gl.EnableVertexAttribArray(0)
		gl.VertexAttribPointer(0, glyphMeshVertPosLayoutEle.ElementType.CompCount(), glyphMeshVertPosLayoutEle.ElementType.GLType(), false, gr.GlyphMesh.Buf.Stride, gl.PtrOffset(glyphMeshVertPosLayoutEle.Offset))
		gl.VertexAttribDivisor(0, 0)

		// Set the rest of the vertex attributes to use the instance buffer
		gl.BindBuffer(gl.ARRAY_BUFFER, instanceBuf.BufID)
		layout := instanceBuf.GetLayout()

		uvEle := layout[0]
		gl.EnableVertexAttribArray(1)
		gl.VertexAttribPointer(1, uvEle.ElementType.CompCount(), uvEle.ElementType.GLType(), false, instanceBuf.Stride, gl.PtrOffset(uvEle.Offset))
		gl.VertexAttribDivisor(1, 1)

		uvSize := layout[1]
		gl.EnableVertexAttribArray(2)
		gl.VertexAttribPointer(2, uvSize.ElementType.CompCount(), uvSize.ElementType.GLType(), false, instanceBuf.Stride, gl.PtrOffset(uvSize.Offset))
		gl.VertexAttribDivisor(2, 1)

		colorEle := layout[2]
		gl.EnableVertexAttribArray(3)
		gl.VertexAttribPointer(3, colorEle.ElementType.CompCount(), colorEle.ElementType.GLType(), false, instanceBuf.Stride, gl.PtrOffset(colorEle.Offset))
		gl.VertexAttribDivisor(3, 1)

		posEle := layout[3]
		gl.EnableVertexAttribArray(4)
		gl.VertexAttribPointer(4, posEle.ElementType.CompCount(), posEle.ElementType.GLType(), false, instanceBuf.Stride, gl.PtrOffset(posEle.Offset))
		gl.VertexAttribDivisor(4, 1)

		scaleEle := layout[4]
		gl.EnableVertexAttribArray(5)
		gl.VertexAttribPointer(5, scaleEle.ElementType.CompCount(), scaleEle.ElementType.GLType(), false, instanceBuf.Stride, gl.PtrOffset(scaleEle.Offset))
		gl.VertexAttribDivisor(5, 1)

		//Fill buffer with zeros and set to dynamic so in the actual draw calls we use bufferSubData which makes things a lot faster
		gl.BufferData(gl.ARRAY_BUFFER, len(*vbo)*4, gl.Ptr(&(*vbo)[0]), buffers.BufUsage_Dynamic.ToGL())

		//Reset mesh layout because the instancedBuf setLayout over-wrote vertex attribute 0
		gr.GlyphMesh.Buf.SetLayout(buffers.Element{ElementType: buffers.DataTypeVec3})
	}

	setupGlyphInstanceBuf(&gr.GlyphFgInstancedBuf, &gr.GlyphFgVBO)
	setupGlyphInstanceBuf(&gr.GlyphBgInstancedBuf, &gr.GlyphBgVBO)

	gr.SetScreenSize(screenWidth, screenHeight)

	return gr, nil
}
