package frame

import (
	"fmt"

	"9fans.net/go/draw"
)

func (f *Frame) drawtext(pt draw.Point, text, back *draw.Image) {
	for nb := 0; nb < len(f.box); nb++ {
		b := &f.box[nb]
		f.cklinewrap(&pt, b)
		if f.NoRedraw == 0 && b.nrune >= 0 {
			f.B.BytesBg(pt, text, draw.Point{}, f.Font, b.bytes, back, draw.Point{})
		}
		pt.X += b.wid
	}
}

// Drawsel repaints a section of the frame,
// delimited by character positions p0 and p1, either with
// plain background or entirely highlighted, according to the
// flag highlighted, managing the tick appropriately.
//
// The point pt0 is the geometrical location of p0 on the screen;
// like all of the selection-helper routines' Point arguments,
// it must be a value generated by PointOf.
func (f *Frame) Drawsel(pt0 draw.Point, p0, p1 int, highlighted bool) {
	if f.Ticked {
		f.Tick(f.PointOf(f.P0), false)
	}
	if p0 == p1 {
		f.Tick(pt0, highlighted)
		return
	}

	back, text := f.Cols[BACK], f.Cols[TEXT]
	if highlighted {
		back, text = f.Cols[HIGH], f.Cols[HTEXT]
	}
	f.Drawsel0(pt0, p0, p1, back, text)
}

// Drawsel0 is a lower-level Drawsel, taking as arguments a background color,
// back, and text color, text. It assumes that the tick is
// being handled (removed beforehand, replaced afterwards, as
// required) by its caller.
func (f *Frame) Drawsel0(pt draw.Point, p0, p1 int, back, text *draw.Image) draw.Point {
	if p0 > p1 {
		panic(fmt.Sprintf("libframe: frDrawsel0 p0=%d > p1=%d", p0, p1))
	}

	p := 0
	trim := 0
	nb := 0
	for ; nb < len(f.box) && p < p1; nb++ {
		b := &f.box[nb]
		nr := b.nrune
		if nr < 0 {
			nr = 1
		}
		if p+nr <= p0 {
			p += nr
			continue
		}
		if p >= p0 {
			qt := pt
			f.cklinewrap(&pt, b)
			// fill in the end of a wrapped line
			if pt.Y > qt.Y {
				f.B.Draw(draw.Rect(qt.X, qt.Y, f.R.Max.X, pt.Y), back, nil, qt)
			}
		}
		ptr := b.bytes
		if p < p0 { // beginning of region: advance into box
			ptr = ptr[runeindex(ptr, p0-p):]
			nr -= p0 - p
			p = p0
		}
		trim = 0
		if p+nr > p1 { // end of region: trim box
			nr -= (p + nr) - p1
			trim = 1
		}
		var w int
		if b.nrune < 0 || nr == b.nrune {
			w = b.wid
		} else {
			ptr = ptr[:runeindex(ptr, nr)]
			w = f.Font.BytesWidth(ptr)
		}
		x := pt.X + w
		if x > f.R.Max.X {
			x = f.R.Max.X
		}
		f.B.Draw(draw.Rect(pt.X, pt.Y, x, pt.Y+f.Font.Height), back, nil, pt)
		if b.nrune >= 0 {
			f.B.BytesBg(pt, text, draw.Point{}, f.Font, ptr, back, draw.Point{})
		}
		pt.X += w
		p += nr
	}

	// if this is end of last plain text box on wrapped line, fill to end of line
	if p1 > p0 && 0 < nb && nb < len(f.box) && f.box[nb-1].nrune > 0 && trim == 0 {
		qt := pt
		b := &f.box[nb]
		f.cklinewrap(&pt, b)
		if pt.Y > qt.Y {
			f.B.Draw(draw.Rect(qt.X, qt.Y, f.R.Max.X, pt.Y), back, nil, qt)
		}
	}
	return pt
}

func (f *Frame) Redraw() {
	if f.P0 == f.P1 {
		ticked := f.Ticked
		if ticked {
			f.Tick(f.PointOf(f.P0), false)
		}
		f.Drawsel0(f.PointOf(0), 0, f.NumChars, f.Cols[BACK], f.Cols[TEXT])
		if ticked {
			f.Tick(f.PointOf(f.P0), true)
		}
		return
	}

	pt := f.PointOf(0)
	pt = f.Drawsel0(pt, 0, f.P0, f.Cols[BACK], f.Cols[TEXT])
	pt = f.Drawsel0(pt, f.P0, f.P1, f.Cols[HIGH], f.Cols[HTEXT])
	pt = f.Drawsel0(pt, f.P1, f.NumChars, f.Cols[BACK], f.Cols[TEXT])
	_ = pt
}

// Tick draws (if ticked is true) or
// removes (if ticked is false) the tick at the screen position
// indicated by pt.
func (f *Frame) Tick(pt draw.Point, ticked bool) {
	if f.tickscale != f.Display.ScaleSize(1) {
		if f.Ticked {
			f.drawTick(pt, false)
		}
		f.InitTick()
	}
	f.drawTick(pt, ticked)
}

func (f *Frame) drawTick(pt draw.Point, ticked bool) {
	if f.Ticked == ticked || f.tick == nil && !pt.In(f.R) {
		return
	}
	pt.X -= f.tickscale // looks best just left of where requested
	r := draw.Rect(pt.X, pt.Y, pt.X+_FRTICKW*f.tickscale, pt.Y+f.Font.Height)
	// can go into left border but not right
	if r.Max.X > f.R.Max.X {
		r.Max.X = f.R.Max.X
	}
	if ticked {
		f.tickback.Draw(f.tickback.R, f.B, nil, pt)
		f.B.Draw(r, f.tick, nil, draw.ZP)
	} else {
		f.B.Draw(r, f.tickback, nil, draw.ZP)
	}
	f.Ticked = ticked
}

func (f *Frame) draw(pt draw.Point) draw.Point {
	for nb := 0; nb < len(f.box); nb++ {
		b := &f.box[nb]
		f.cklinewrap0(&pt, b)
		if pt.Y == f.R.Max.Y {
			f.NumChars -= f.strlen(nb)
			f.delbox(nb, len(f.box)-1)
			break
		}
		if b.nrune > 0 {
			n := f.canfit(pt, b)
			if n == 0 {
				break
			}
			if n != b.nrune {
				f.splitbox(nb, n)
				b = &f.box[nb]
			}
			pt.X += b.wid
		} else {
			if b.bc == '\n' {
				pt.X = f.R.Min.X
				pt.Y += f.Font.Height
			} else {
				pt.X += f.newwid(pt, b)
			}
		}
	}
	return pt
}

func (f *Frame) strlen(nb int) int {
	n := 0
	for ; nb < len(f.box); nb++ {
		b := &f.box[nb]
		n += b.NRUNE()
	}
	return n
}
