package typeset

import "fmt"
import "strconv"
import "golang.org/x/image/font"
import "golang.org/x/image/math/fixed"

type validationLevel int; const (
	validationLevelNone        validationLevel = iota
	validationLevelTokens
	validationLevelMeasurement
	validationLevelFlow
)

type tokenKind int; const (
	tokenKindWord      tokenKind = iota // contains everything that isn't:
	tokenKindSpace     // only unicode space runes, except \r or \n
	tokenKindTab       // only \t runes
	tokenKindLineBreak // either "\n", or "\r\n"
)

func (kind tokenKind) String () string {
	switch kind {
	case tokenKindWord:      return "Word"
	case tokenKindSpace:     return "Space"
	case tokenKindTab:       return "Tab"
	case tokenKindLineBreak: return "LineBreak"
	}
	return fmt.Sprintf("typeset.tokenKind(%d)", kind)
}

type token struct {
	kind     tokenKind
	width    fixed.Int26_6
	position fixed.Point26_6
	runes    []runeLayout
}

func (tok token) String () string {
	str := ""
	for _, runl := range tok.runes {
		str += string(runl.run)
	}
	return fmt.Sprintf (
		"%v:%v{%v,%v-%v}",
		tok.kind, strconv.Quote(str),
		tok.position.X, tok.position.Y, tok.width)
}

// TODO: perhaps rename this to just "glyph"
type runeLayout struct {
	x   fixed.Int26_6
	run rune
}

func (run runeLayout) String () string {
	return fmt.Sprintf("%s-{%v}", strconv.Quote(string([]rune { run.run })), run.x)
}

// RuneIter is an iterator that iterates over positioned runes.
type RuneIter func (yield func(fixed.Point26_6, rune) bool)

// Align specifies a text alignment method.
type Align int; const (
	//                              X     |     Y
	AlignStart  Align = iota // left      | top
	AlignMiddle              // center    | center
	AlignEnd                 // right     | bottom
	AlignEven                // justified | (unsupported)
)

// TypeSetter manages text, and can perform layout operations on it. It
// automatically avoids performing redundant work. It has no constructor and its
// zero value can be used safely, but it must not be copied after first use.
type TypeSetter struct {
	text   string
	runes  []runeLayout
	tokens []token

	validationLevel validationLevel

	xAlign, yAlign Align
	face           font.Face
	size           fixed.Point26_6 // width, height
	wrap           bool

	minimumSize       fixed.Point26_6
	layoutBounds      fixed.Rectangle26_6
	layoutBoundsSpace fixed.Rectangle26_6
}

// AtPosition returns the index of the rune at the specified position. The
// returned index may be greater than the length of runes in the TypeSetter.
func (this *TypeSetter) AtPosition (position fixed.Point26_6) int {
	metrics := this.face.Metrics()
	lastValidIndex := 0
	index          := 0
	for _, tok := range this.tokens {
		pos := tok.position
		yValid :=
			position.Y >= pos.Y - metrics.Ascent &&
			position.Y <= pos.Y + metrics.Descent
		if !yValid { index += len(tok.runes); continue }
		
		for _, runl := range tok.runes {
			x := pos.X + runl.x
			xValid := position.X >= pos.X + runl.x
			if xValid {
				lastValidIndex = index
			} else if x > position.X {
				return lastValidIndex
			}

			index ++
		}
	}
	index ++
	return lastValidIndex
}

// Runes returns an iterator for all runes in the TypeSetter, and their positions.
func (this *TypeSetter) Runes () RuneIter {
	this.needFlow()
	return func (yield func (fixed.Point26_6, rune) bool) {
		for _, tok := range this.tokens {
			for _, runl := range tok.runes {
				pos := tok.position
				pos.X += runl.x
				if !yield(pos, runl.run) { return }
			}
		}
	}
}

// RunesWithNull returns an iterator for all runes in the TypeSetter, plus an
// additional null rune at the end. This is useful for calculating the positions
// of things.
func (this *TypeSetter) RunesWithNull () RuneIter {
	this.needFlow()
	return func (yield func (fixed.Point26_6, rune) bool) {
		var tok token
		for _, tok = range this.tokens {
			for _, runl := range tok.runes {
				pos := tok.position
				pos.X += runl.x
				if !yield(pos, runl.run) { return }
			}
		}

		pos := tok.position
		pos.X += tok.width
		yield(pos, 0)
	}
}

// Em returns the width of one emspace according to the typesetter's typeface,
// which is the width of the capital letter 'M'. 
func (this *TypeSetter) Em () fixed.Int26_6 {
	if this.face == nil { return 0 }
	width, _ := this.face.GlyphAdvance('M')
	return width
}

// Face returns the font face as set by SetFace.
func (this *TypeSetter) Face () font.Face {
	return this.face
}

// LayoutBounds returns the semantic bounding box of the text. The origin point
// (0, 0) of the rectangle corresponds to the origin of the first line's
// baseline.
func (this *TypeSetter) LayoutBounds () fixed.Rectangle26_6 {
	this.needFlow()
	return this.layoutBounds
}

// LayoutBoundsSpace is like LayoutBounds, but it also takes into account the
// trailing whitespace at the end of each line (if it exists).
func (this *TypeSetter) LayoutBoundsSpace () fixed.Rectangle26_6 {
	this.needFlow()
	return this.layoutBoundsSpace
}

// MinimumSize returns the minimum width and height needed to display text. If
// wrapping is enabled, this method will return { X: Em(), Y: 0 }.
func (this *TypeSetter) MinimumSize () fixed.Point26_6 {
	if this.wrap { return fixed.Point26_6{ X: this.Em(), Y: 0 } }
	this.needFlow()
	return this.minimumSize
}

// PositionAt returns the position of the rune at the specified index.
func (this *TypeSetter) PositionAt (index int) fixed.Point26_6 {
	idx := 0
	var position fixed.Point26_6
	this.RunesWithNull()(func (pos fixed.Point26_6, run rune) bool {
		if index == idx {
			position = pos
			return false	
		}
		idx ++
		return true
	})
	return position
}

// ReccomendedHeightFor returns the reccomended max height if the text were to
// have its maximum width set to the given width. This does not actually move
// any text, it only simulates it.
func (this *TypeSetter) RecommendedHeight (width fixed.Int26_6) fixed.Int26_6 {
	this.needMeasurement()
	return recommendHeight(this.tokens, this.face, width)
}

// SetAlign sets the horizontal and vertical alignment of the text.
func (this *TypeSetter) SetAlign (x, y Align) {
	if this.xAlign == x && this.yAlign == y { return }
	this.xAlign = x
	this.yAlign = y
	this.invalidate(validationLevelFlow)
}

// SetFace sets the font face the text will be laid out according to.
func (this *TypeSetter) SetFace (face font.Face) {
	if this.face == face { return }
	this.face = face
	this.invalidate(validationLevelMeasurement)
}

// SetSize sets the width and height of the TypeSetter.
func (this *TypeSetter) SetSize (size fixed.Point26_6) {
	if this.size == size { return }
	this.size = size
	this.invalidate(validationLevelFlow)
}

// SetText sets the text of the TypeSetter.
func (this *TypeSetter) SetText (text string) {
	if this.text == text { return }
	this.text = text
	this.invalidate(validationLevelTokens)
}

// SetWrap sets whether the text will wrap to the width specified by SetSize.
func (this *TypeSetter) SetWrap (wrap bool) {
	if this.wrap == wrap { return }
	this.wrap = wrap
	this.invalidate(validationLevelFlow)
}

func (this *TypeSetter) needTokens () {
	if this.valid(validationLevelTokens) { return }
	this.runes, this.tokens = parseString(this.text)
	this.validate(validationLevelTokens)
}

func (this *TypeSetter) needMeasurement () {
	if this.valid(validationLevelMeasurement) { return }
	this.needTokens()
	measure(this.tokens, this.face)
	this.validate(validationLevelMeasurement)
}

func (this *TypeSetter) needFlow () {
	if this.valid(validationLevelFlow) { return }
	this.needMeasurement()
	this.layoutBounds, this.layoutBoundsSpace, this.minimumSize = reflow (
		this.tokens,
		this.face, this.size,
		this.wrap, this.xAlign, this.yAlign)
	this.validate(validationLevelFlow)
}

func (this *TypeSetter) validate (level validationLevel) {
	this.validationLevel = level
}

func (this *TypeSetter) invalidate (level validationLevel) {
	if this.valid(level) {
		this.validationLevel = level - 1
	}
}

func (this *TypeSetter) valid (level validationLevel) bool {
	return this.validationLevel >= level
}