package main

import (
	"embed"
	"fmt"
	"image"
	"image/draw"

	"golang.org/x/image/font"
	"golang.org/x/image/font/opentype"
	"golang.org/x/image/math/fixed"
)

//go:embed font/AtkinsonHyperlegibleMono-Regular.ttf font/AtkinsonHyperlegibleMono-Bold.ttf
var fontFS embed.FS

type glyph struct {
	// Coverage values, cellW*cellH, origin at the cell's top left.
	cov  []uint8
	w, h int
}

type textFace struct {
	face   font.Face
	cellW  int
	cellH  int
	ascent int
	cache  map[rune]*glyph

	// The line as the eye reads it: top of a digit down to the baseline. The
	// cell is taller at both ends, holding accent space nothing here uses and
	// descenders that hang past the line without being read as part of it, so
	// laying out by the cell puts more air around text than around a box.
	capTop int
	lineH  int
}

// Digits carry no ascender or descender, so the first row one marks is the top
// of the line and the baseline is the bottom.
func (t *textFace) measureLine() error {
	g := t.glyph('0')
	for y := 0; y < g.h; y++ {
		for x := 0; x < g.w; x++ {
			if g.cov[y*g.w+x] != 0 {
				t.capTop, t.lineH = y, t.ascent-y
				return nil
			}
		}
	}
	return fmt.Errorf("font rasterised no ink for a digit")
}

func loadFace(bold bool, sizePx float64) (*textFace, error) {
	name := "font/AtkinsonHyperlegibleMono-Regular.ttf"
	if bold {
		name = "font/AtkinsonHyperlegibleMono-Bold.ttf"
	}
	raw, err := fontFS.ReadFile(name)
	if err != nil {
		return nil, err
	}
	parsed, err := opentype.Parse(raw)
	if err != nil {
		return nil, err
	}
	face, err := opentype.NewFace(parsed, &opentype.FaceOptions{
		Size: sizePx,
		DPI:  72,
	})
	if err != nil {
		return nil, err
	}

	m := face.Metrics()
	adv, ok := face.GlyphAdvance('0')
	if !ok {
		return nil, fmt.Errorf("font has no digit glyphs")
	}
	t := &textFace{
		face:   face,
		cellW:  adv.Ceil(),
		cellH:  (m.Ascent + m.Descent).Ceil(),
		ascent: m.Ascent.Ceil(),
		cache:  make(map[rune]*glyph),
	}
	if err := t.measureLine(); err != nil {
		return nil, err
	}
	return t, nil
}

// Rasterises once per rune and keeps the coverage mask, since the same few
// dozen runes are redrawn every frame.
func (t *textFace) glyph(r rune) *glyph {
	if g, ok := t.cache[r]; ok {
		return g
	}
	g := &glyph{w: t.cellW, h: t.cellH, cov: make([]uint8, t.cellW*t.cellH)}

	dr, mask, maskp, _, ok := t.face.Glyph(
		fixed.P(0, t.ascent), r)
	if ok {
		alpha := image.NewAlpha(image.Rect(0, 0, t.cellW, t.cellH))
		draw.DrawMask(alpha, dr, image.NewUniform(image.White.C), image.Point{},
			mask, maskp, draw.Src)
		for y := 0; y < t.cellH; y++ {
			for x := 0; x < t.cellW; x++ {
				g.cov[y*t.cellW+x] = alpha.AlphaAt(x, y).A
			}
		}
	}
	t.cache[r] = g
	return g
}

func (t *textFace) draw(fb *framebuffer, x, y int, s string, c rgb) int {
	for _, r := range s {
		if r == ' ' {
			x += t.cellW
			continue
		}
		g := t.glyph(r)
		for gy := 0; gy < g.h; gy++ {
			row := gy * g.w
			for gx := 0; gx < g.w; gx++ {
				if cov := g.cov[row+gx]; cov != 0 {
					fb.blend(x+gx, y+gy, c, cov)
				}
			}
		}
		x += t.cellW
	}
	return x
}
