// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package plan9font implements font faces for the Plan 9 font and subfont file
// formats. These formats are described at
// https://9p.io/magic/man2html/6/font
package plan9font // import "golang.org/x/image/font/plan9font"

import (
	"bytes"
	"errors"
	"fmt"
	"image"
	"image/color"
	"log"
	"strconv"
	"strings"

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

// fontchar describes one character glyph in a subfont.
//
// For more detail, look for "struct Fontchar" in
// https://9p.io/magic/man2html/2/cachechars
type fontchar struct {
	x      uint32 // X position in the image holding the glyphs.
	top    uint8  // First non-zero scan line.
	bottom uint8  // Last non-zero scan line.
	left   int8   // Offset of baseline.
	width  uint8  // Width of baseline.
}

func parseFontchars(p []byte) []fontchar {
	fc := make([]fontchar, len(p)/6)
	for i := range fc {
		fc[i] = fontchar{
			x:      uint32(p[0]) | uint32(p[1])<<8,
			top:    uint8(p[2]),
			bottom: uint8(p[3]),
			left:   int8(p[4]),
			width:  uint8(p[5]),
		}
		p = p[6:]
	}
	return fc
}

// subface implements font.Face for a Plan 9 subfont.
type subface struct {
	firstRune rune         // First rune in the subfont.
	n         int          // Number of characters in the subfont.
	height    int          // Inter-line spacing.
	ascent    int          // Height above the baseline.
	fontchars []fontchar   // Character descriptions.
	img       *image.Alpha // Image holding the glyphs.
}

func (f *subface) Close() error                   { return nil }
func (f *subface) Kern(r0, r1 rune) fixed.Int26_6 { return 0 }

func (f *subface) Metrics() font.Metrics {
	// Approximate XHeight with the ascent of lowercase 'x'.
	xbounds, _, _ := f.GlyphBounds('x')
	// The same applies to CapHeight, using the uppercase 'H'.
	hbounds, _, _ := f.GlyphBounds('H')
	return font.Metrics{
		Height:     fixed.I(f.height),
		Ascent:     fixed.I(f.ascent),
		Descent:    fixed.I(f.height - f.ascent),
		XHeight:    -xbounds.Min.Y,
		CapHeight:  -hbounds.Min.Y,
		CaretSlope: image.Point{X: 0, Y: 1},
	}
}

func (f *subface) Glyph(dot fixed.Point26_6, r rune) (
	dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) {

	r -= f.firstRune
	if r < 0 || f.n <= int(r) {
		return image.Rectangle{}, nil, image.Point{}, 0, false
	}
	i := &f.fontchars[r+0]
	j := &f.fontchars[r+1]

	minX := int(dot.X+32)>>6 + int(i.left)
	minY := int(dot.Y+32)>>6 + int(i.top) - f.ascent
	dr = image.Rectangle{
		Min: image.Point{
			X: minX,
			Y: minY,
		},
		Max: image.Point{
			X: minX + int(j.x-i.x),
			Y: minY + int(i.bottom) - int(i.top),
		},
	}
	return dr, f.img, image.Point{int(i.x), int(i.top)}, fixed.Int26_6(i.width) << 6, true
}

func (f *subface) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
	r -= f.firstRune
	if r < 0 || f.n <= int(r) {
		return fixed.Rectangle26_6{}, 0, false
	}
	i := &f.fontchars[r+0]
	j := &f.fontchars[r+1]

	bounds = fixed.R(
		int(i.left),
		int(i.top)-f.ascent,
		int(i.left)+int(j.x-i.x),
		int(i.bottom)-f.ascent,
	)
	return bounds, fixed.Int26_6(i.width) << 6, true
}

func (f *subface) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
	r -= f.firstRune
	if r < 0 || f.n <= int(r) {
		return 0, false
	}
	return fixed.Int26_6(f.fontchars[r].width) << 6, true
}

// runeRange maps a single rune range [lo, hi] to a lazily loaded subface. Both
// ends of the range are inclusive.
type runeRange struct {
	lo, hi      rune
	offset      rune // subfont index that the lo rune maps to.
	relFilename string
	subface     *subface
	bad         bool
}

// face implements font.Face for a Plan 9 font.
//
// It maps multiple rune ranges to *subface values. Rune ranges may overlap;
// the first match wins.
type face struct {
	height     int
	ascent     int
	readFile   func(relFilename string) ([]byte, error)
	runeRanges []runeRange
}

func (f *face) Close() error                   { return nil }
func (f *face) Kern(r0, r1 rune) fixed.Int26_6 { return 0 }

func (f *face) Metrics() font.Metrics {
	xbounds, _, _ := f.GlyphBounds('x')
	hbounds, _, _ := f.GlyphBounds('H')
	return font.Metrics{
		Height:     fixed.I(f.height),
		Ascent:     fixed.I(f.ascent),
		Descent:    fixed.I(f.height - f.ascent),
		XHeight:    -xbounds.Min.Y,
		CapHeight:  -hbounds.Min.Y,
		CaretSlope: image.Point{X: 0, Y: 1},
	}
}

func (f *face) Glyph(dot fixed.Point26_6, r rune) (
	dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) {

	if s, rr := f.subface(r); s != nil {
		return s.Glyph(dot, rr)
	}
	return image.Rectangle{}, nil, image.Point{}, 0, false
}

func (f *face) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
	if s, rr := f.subface(r); s != nil {
		return s.GlyphBounds(rr)
	}
	return fixed.Rectangle26_6{}, 0, false
}

func (f *face) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
	if s, rr := f.subface(r); s != nil {
		return s.GlyphAdvance(rr)
	}
	return 0, false
}

// For subfont files, if reading the given file name fails, we try appending
// ".n" where n is the log2 of the grayscale depth in bits (so at most 3) and
// then work down to 0. This was done in Plan 9 when antialiased fonts were
// introduced so that the 1-bit displays could keep using the 1-bit forms but
// higher depth displays could use the antialiased forms.
var subfontSuffixes = [...]string{
	"",
	".3",
	".2",
	".1",
	".0",
}

func (f *face) readSubfontFile(name string) ([]byte, error) {
	var firstErr error
	for _, suffix := range subfontSuffixes {
		if b, err := f.readFile(name + suffix); err == nil {
			return b, nil
		} else if firstErr == nil {
			firstErr = err
		}
	}
	return nil, firstErr
}

func (f *face) subface(r rune) (*subface, rune) {
	// Fall back on U+FFFD if we can't find r.
	for _, rr := range [2]rune{r, '\ufffd'} {
		// We have to do linear, not binary search. plan9port's
		// lucsans/unicode.8.font says:
		//	0x2591  0x2593  ../luc/Altshades.7.0
		//	0x2500  0x25ee  ../luc/FormBlock.7.0
		// and the rune ranges overlap.
		for i := range f.runeRanges {
			x := &f.runeRanges[i]
			if rr < x.lo || x.hi < rr || x.bad {
				continue
			}
			if x.subface == nil {
				data, err := f.readSubfontFile(x.relFilename)
				if err != nil {
					log.Printf("plan9font: couldn't read subfont %q: %v", x.relFilename, err)
					x.bad = true
					continue
				}
				sub, err := ParseSubfont(data, x.lo-x.offset)
				if err != nil {
					log.Printf("plan9font: couldn't parse subfont %q: %v", x.relFilename, err)
					x.bad = true
					continue
				}
				x.subface = sub.(*subface)
			}
			return x.subface, rr
		}
	}
	return nil, 0
}

// ParseFont parses a Plan 9 font file. data is the contents of that font file,
// which gives relative filenames for subfont files. readFile returns the
// contents of those subfont files. It is similar to io/ioutil's ReadFile
// function, except that it takes a relative filename instead of an absolute
// one.
func ParseFont(data []byte, readFile func(relFilename string) ([]byte, error)) (font.Face, error) {
	f := &face{
		readFile: readFile,
	}
	// TODO: don't use strconv, to avoid the conversions from []byte to string?
	for first := true; len(data) > 0; first = false {
		i := bytes.IndexByte(data, '\n')
		if i < 0 {
			return nil, errors.New("plan9font: invalid font: no final newline")
		}
		row := string(data[:i])
		data = data[i+1:]
		if first {
			height, s, ok := nextInt32(row)
			if !ok {
				return nil, fmt.Errorf("plan9font: invalid font: invalid header %q", row)
			}
			ascent, s, ok := nextInt32(s)
			if !ok {
				return nil, fmt.Errorf("plan9font: invalid font: invalid header %q", row)
			}
			if height < 0 || 0xffff < height || ascent < 0 || 0xffff < ascent {
				return nil, fmt.Errorf("plan9font: invalid font: invalid header %q", row)
			}
			f.height, f.ascent = int(height), int(ascent)
			continue
		}
		lo, s, ok := nextInt32(row)
		if !ok {
			return nil, fmt.Errorf("plan9font: invalid font: invalid row %q", row)
		}
		hi, s, ok := nextInt32(s)
		if !ok {
			return nil, fmt.Errorf("plan9font: invalid font: invalid row %q", row)
		}
		offset, s, _ := nextInt32(s)

		f.runeRanges = append(f.runeRanges, runeRange{
			lo:          lo,
			hi:          hi,
			offset:      offset,
			relFilename: s,
		})
	}
	return f, nil
}

func nextInt32(s string) (ret int32, remaining string, ok bool) {
	i := 0
	for ; i < len(s) && s[i] <= ' '; i++ {
	}
	j := i
	for ; j < len(s) && s[j] > ' '; j++ {
	}
	n, err := strconv.ParseInt(s[i:j], 0, 32)
	if err != nil {
		return 0, s, false
	}
	for ; j < len(s) && s[j] <= ' '; j++ {
	}
	return int32(n), s[j:], true
}

// ParseSubfont parses a Plan 9 subfont file.
//
// firstRune is the first rune in the subfont file. For example, the
// Phonetic.6.0 subfont, containing glyphs in the range U+0250 to U+02E9, would
// set firstRune to '\u0250'.
func ParseSubfont(data []byte, firstRune rune) (font.Face, error) {
	data, m, err := parseImage(data)
	if err != nil {
		return nil, err
	}
	if len(data) < 3*12 {
		return nil, errors.New("plan9font: invalid subfont: header too short")
	}
	n := atoi(data[0*12:])
	height := atoi(data[1*12:])
	ascent := atoi(data[2*12:])
	data = data[3*12:]
	if n < 0 || height < 0 || ascent < 0 {
		return nil, errors.New("plan9font: invalid subfont: dimension too large")
	} else if len(data) != 6*(n+1) {
		return nil, errors.New("plan9font: invalid subfont: data length mismatch")
	}

	// Convert from plan9Image to image.Alpha, as the standard library's
	// image/draw package works best when glyph masks are of that type.
	img := image.NewAlpha(m.Bounds())
	for y := img.Rect.Min.Y; y < img.Rect.Max.Y; y++ {
		i := img.PixOffset(img.Rect.Min.X, y)
		for x := img.Rect.Min.X; x < img.Rect.Max.X; x++ {
			img.Pix[i] = m.at(x, y)
			i++
		}
	}

	return &subface{
		firstRune: firstRune,
		n:         n,
		height:    height,
		ascent:    ascent,
		fontchars: parseFontchars(data),
		img:       img,
	}, nil
}

// plan9Image implements that subset of the Plan 9 image feature set that is
// used by this font file format.
//
// Some features, such as the repl bit and a clip rectangle, are omitted for
// simplicity.
type plan9Image struct {
	depth int             // Depth of the pixels in bits.
	width int             // Width in bytes of a single scan line.
	rect  image.Rectangle // Extent of the image.
	pix   []byte          // Pixel bits.
}

func (m *plan9Image) byteoffset(x, y int) int {
	x -= m.rect.Min.X
	y -= m.rect.Min.Y
	a := y * m.width
	if m.depth < 8 {
		// We need to always round down, but Go rounds toward zero.
		np := 8 / m.depth
		if x < 0 {
			return a + (x-np+1)/np
		}
		return a + x/np
	}
	return a + x*(m.depth/8)
}

func (m *plan9Image) Bounds() image.Rectangle { return m.rect }
func (m *plan9Image) ColorModel() color.Model { return color.AlphaModel }

func (m *plan9Image) At(x, y int) color.Color {
	if (image.Point{x, y}).In(m.rect) {
		return color.Alpha{m.at(x, y)}
	}
	return color.Alpha{0x00}
}

func (m *plan9Image) at(x, y int) uint8 {
	b := m.pix[m.byteoffset(x, y)]
	switch m.depth {
	case 1:
		// CGrey, 1.
		mask := uint8(1 << uint8(7-x&7))
		if (b & mask) != 0 {
			return 0xff
		}
		return 0
	case 2:
		// CGrey, 2.
		shift := uint(x&3) << 1
		// Place pixel at top of word.
		y := b << shift
		y &= 0xc0
		// Replicate throughout.
		y |= y >> 2
		y |= y >> 4
		return y
	}
	return 0
}

var compressed = []byte("compressed\n")

func parseImage(data []byte) (remainingData []byte, m *plan9Image, retErr error) {
	if !bytes.HasPrefix(data, compressed) {
		return nil, nil, errors.New("plan9font: unsupported uncompressed format")
	}
	data = data[len(compressed):]

	const hdrSize = 5 * 12
	if len(data) < hdrSize {
		return nil, nil, errors.New("plan9font: invalid image: header too short")
	}
	hdr, data := data[:hdrSize], data[hdrSize:]

	// Distinguish new channel descriptor from old ldepth. Channel descriptors
	// have letters as well as numbers, while ldepths are a single digit
	// formatted as %-11d.
	new := false
	for m := 0; m < 10; m++ {
		if hdr[m] != ' ' {
			new = true
			break
		}
	}
	if hdr[11] != ' ' {
		return nil, nil, errors.New("plan9font: invalid image: bad header")
	}
	if !new {
		return nil, nil, errors.New("plan9font: unsupported ldepth format")
	}

	depth := 0
	switch s := strings.TrimSpace(string(hdr[:1*12])); s {
	default:
		return nil, nil, fmt.Errorf("plan9font: unsupported pixel format %q", s)
	case "k1":
		depth = 1
	case "k2":
		depth = 2
	}
	r := ator(hdr[1*12:])
	if r.Min.X < 0 || r.Max.X < 0 || r.Min.Y < 0 || r.Max.Y < 0 ||
		r.Min.X > r.Max.X || r.Min.Y > r.Max.Y {
		return nil, nil, errors.New("plan9font: invalid image: bad rectangle")
	}

	width := bytesPerLine(r, depth)
	// These bounds are somewhat arbitrary, but multiplying them together won't
	// overflow an int32.
	if (width > 0xffff) || (r.Dy() > 0x7fff) {
		return nil, nil, errors.New("plan9font: unsupported dimensions")
	}
	m = &plan9Image{
		depth: depth,
		width: width,
		rect:  r,
		pix:   make([]byte, width*r.Dy()),
	}

	miny := r.Min.Y
	for miny != r.Max.Y {
		if len(data) < 2*12 {
			return nil, nil, errors.New("plan9font: invalid image: data band too short")
		}
		maxy := atoi(data[0*12:])
		nb := atoi(data[1*12:])
		data = data[2*12:]
		if maxy < 0 || nb < 0 {
			return nil, nil, errors.New("plan9font: invalid image: dimension too large")
		} else if len(data) < nb {
			return nil, nil, errors.New("plan9font: invalid image: data band length mismatch")
		}
		buf := data[:nb]
		data = data[nb:]

		if maxy <= miny || r.Max.Y < maxy {
			return nil, nil, fmt.Errorf("plan9font: bad maxy %d", maxy)
		}
		// An old-format image would flip the bits here, but we don't support
		// the old format.
		rr := r
		rr.Min.Y = miny
		rr.Max.Y = maxy
		if err := decompress(m, rr, buf); err != nil {
			return nil, nil, err
		}
		miny = maxy
	}
	return data, m, nil
}

// Compressed data are sequences of byte codes. If the first byte b has the
// 0x80 bit set, the next (b^0x80)+1 bytes are data. Otherwise, these two bytes
// specify a previous string to repeat.
const (
	compShortestMatch = 3    // shortest match possible.
	compWindowSize    = 1024 // window size.
)

var (
	errDecompressBufferTooSmall = errors.New("plan9font: decompress: buffer too small")
	errDecompressPhaseError     = errors.New("plan9font: decompress: phase error")
)

func decompress(m *plan9Image, r image.Rectangle, data []byte) error {
	if !r.In(m.rect) {
		return errors.New("plan9font: decompress: bad rectangle")
	}
	bpl := bytesPerLine(r, m.depth)
	mem := make([]byte, compWindowSize)
	memi := 0
	omemi := -1
	y := r.Min.Y
	linei := m.byteoffset(r.Min.X, y)
	eline := linei + bpl
	datai := 0
	for {
		if linei == eline {
			y++
			if y == r.Max.Y {
				break
			}
			linei = m.byteoffset(r.Min.X, y)
			eline = linei + bpl
		}
		if datai == len(data) {
			return errDecompressBufferTooSmall
		}
		c := data[datai]
		datai++
		if c >= 128 {
			for cnt := c - 128 + 1; cnt != 0; cnt-- {
				if datai == len(data) {
					return errDecompressBufferTooSmall
				}
				if linei == eline {
					return errDecompressPhaseError
				}
				m.pix[linei] = data[datai]
				linei++
				mem[memi] = data[datai]
				memi++
				datai++
				if memi == len(mem) {
					memi = 0
				}
			}
		} else {
			if datai == len(data) {
				return errDecompressBufferTooSmall
			}
			offs := int(data[datai]) + ((int(c) & 3) << 8) + 1
			datai++
			if memi < offs {
				omemi = memi + (compWindowSize - offs)
			} else {
				omemi = memi - offs
			}
			for cnt := (c >> 2) + compShortestMatch; cnt != 0; cnt-- {
				if linei == eline {
					return errDecompressPhaseError
				}
				m.pix[linei] = mem[omemi]
				linei++
				mem[memi] = mem[omemi]
				memi++
				omemi++
				if omemi == len(mem) {
					omemi = 0
				}
				if memi == len(mem) {
					memi = 0
				}
			}
		}
	}
	return nil
}

func ator(b []byte) image.Rectangle {
	return image.Rectangle{atop(b), atop(b[2*12:])}
}

func atop(b []byte) image.Point {
	return image.Pt(atoi(b), atoi(b[12:]))
}

func atoi(b []byte) int {
	i := 0
	for ; i < len(b) && b[i] == ' '; i++ {
	}
	n := 0
	for ; i < len(b) && '0' <= b[i] && b[i] <= '9'; i++ {
		n = n*10 + int(b[i]) - '0'
		if n > 999999 {
			return -1
		}
	}
	return n
}

func bytesPerLine(r image.Rectangle, depth int) int {
	if depth <= 0 || 32 < depth {
		panic("invalid depth")
	}
	var l int
	if r.Min.X >= 0 {
		l = (r.Max.X*depth + 7) / 8
		l -= (r.Min.X * depth) / 8
	} else {
		// Make positive before divide.
		t := (-r.Min.X*depth + 7) / 8
		l = t + (r.Max.X*depth+7)/8
	}
	return l
}
