// Copyright 2016 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 text

// TODO: do we care about "\n" vs "\r" vs "\r\n"? We only recognize "\n" for
// now.

import (
	"bytes"
	"errors"
	"io"
	"strings"
	"unicode/utf8"

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

// Caret is a location in a Frame's text, and is the mechanism for adding and
// removing bytes of text. Conceptually, a Caret and a Frame's text is like an
// int c and a []byte t such that the text before and after that Caret is t[:c]
// and t[c:]. That byte-count location remains unchanged even when a Frame is
// re-sized and laid out into a new tree of Paragraphs, Lines and Boxes.
//
// A Frame can have multiple open Carets. For example, the beginning and end of
// a text selection can be represented by two Carets. Multiple Carets for the
// one Frame are not safe to use concurrently, but it is valid to interleave
// such operations sequentially. For example, if two Carets c0 and c1 for the
// one Frame are positioned at the 10th and 20th byte, and 4 bytes are written
// to c0, inserting what becomes the equivalent of text[10:14], then c0's
// position is updated to be 14 but c1's position is also updated to be 24.
type Caret struct {
	f *Frame

	// caretsIndex is the index of this Caret in the f.carets slice.
	caretsIndex int

	// seqNum is the Frame f's sequence number for which this Caret's cached p,
	// l, b and k fields are valid. If f has been modified since then, those
	// fields will have to be re-calculated based on the pos field (which is
	// always valid).
	//
	// TODO: when re-calculating p, l, b and k, be more efficient than a linear
	// scan from the start or end?
	seqNum uint64

	// p, l and b cache the index of the Caret's Paragraph, Line and Box. None
	// of these values can be zero.
	p, l, b int32

	// k caches the Caret's position in the text, in Frame.text order. It is
	// valid to index the Frame.text slice with k, analogous to the Box.i and
	// Box.j fields. For a Caret c, letting bb := c.f.boxes[c.b], an invariant
	// is that bb.i <= c.k && c.k <= bb.j if the cache is valid (i.e. the
	// Caret's seqNum equals the Frame's seqNum).
	k int32

	// pos is the Caret's position in the text, in layout order. It is the "c"
	// as in "t[:c]" in the doc comment for type Caret above. It is not valid
	// to index the Frame.text slice with pos, since the Frame.text slice does
	// not necessarily hold the textual content in layout order.
	pos int32

	tmp [utf8.UTFMax]byte
}

// Close closes the Caret.
func (c *Caret) Close() error {
	i, j := c.caretsIndex, len(c.f.carets)-1

	// Swap c with the last element of c.f.carets.
	if i != j {
		other := c.f.carets[j]
		other.caretsIndex = i
		c.f.carets[i] = other
	}

	c.f.carets[j] = nil
	c.f.carets = c.f.carets[:j]
	*c = Caret{}
	return nil
}

type leanResult int

const (
	// leanOK means that the lean changed the Caret's Box.
	leanOK leanResult = iota
	// leanFailedEOF means that the lean did not change the Caret's Box,
	// because the Caret was already at the end / beginning of the Frame (when
	// leaning forwards / backwards).
	leanFailedEOF
	// leanFailedNotEOB means that the lean did not change the Caret's Box,
	// because the Caret was not placed at the end / beginning of the Box (when
	// leaning forwards / backwards).
	leanFailedNotEOB
)

// leanForwards moves the Caret from the right end of one Box to the left end
// of the next Box, crossing Lines and Paragraphs to find that next Box. It
// returns whether the Caret moved to a different Box.
//
// Diagramatically, suppose we have two adjacent boxes (shown by square
// brackets below), with the Caret (an integer location called Caret.pos in the
// Frame's text) in the middle of the "foo2bar3" word:
//
//	[foo0 foo1 foo2]^[bar3 bar4 bar5]
//
// leanForwards moves Caret.k from fooBox.j to barBox.i, also updating the
// Caret's p, l and b. Caret.pos remains unchanged.
func (c *Caret) leanForwards() leanResult {
	if c.k != c.f.boxes[c.b].j {
		return leanFailedNotEOB
	}
	if nextB := c.f.boxes[c.b].next; nextB != 0 {
		c.b = nextB
		c.k = c.f.boxes[c.b].i
		return leanOK
	}
	if nextL := c.f.lines[c.l].next; nextL != 0 {
		c.l = nextL
		c.b = c.f.lines[c.l].firstB
		c.k = c.f.boxes[c.b].i
		return leanOK
	}
	if nextP := c.f.paragraphs[c.p].next; nextP != 0 {
		c.p = nextP
		c.l = c.f.paragraphs[c.p].firstL
		c.b = c.f.lines[c.l].firstB
		c.k = c.f.boxes[c.b].i
		return leanOK
	}
	return leanFailedEOF
}

// leanBackwards is like leanForwards but in the other direction.
func (c *Caret) leanBackwards() leanResult {
	if c.k != c.f.boxes[c.b].i {
		return leanFailedNotEOB
	}
	if prevB := c.f.boxes[c.b].prev; prevB != 0 {
		c.b = prevB
		c.k = c.f.boxes[c.b].j
		return leanOK
	}
	if prevL := c.f.lines[c.l].prev; prevL != 0 {
		c.l = prevL
		c.b = c.f.lines[c.l].lastBox(c.f)
		c.k = c.f.boxes[c.b].j
		return leanOK
	}
	if prevP := c.f.paragraphs[c.p].prev; prevP != 0 {
		c.p = prevP
		c.l = c.f.paragraphs[c.p].lastLine(c.f)
		c.b = c.f.lines[c.l].lastBox(c.f)
		c.k = c.f.boxes[c.b].j
		return leanOK
	}
	return leanFailedEOF
}

func (c *Caret) seekStart() {
	c.p = c.f.firstP
	c.l = c.f.paragraphs[c.p].firstL
	c.b = c.f.lines[c.l].firstB
	c.k = c.f.boxes[c.b].i
	c.pos = 0
}

func (c *Caret) seekEnd() {
	c.p = c.f.lastParagraph()
	c.l = c.f.paragraphs[c.p].lastLine(c.f)
	c.b = c.f.lines[c.l].lastBox(c.f)
	c.k = c.f.boxes[c.b].j
	c.pos = int32(c.f.len)
}

// calculatePLBK ensures that the Caret's cached p, l, b and k fields are
// valid.
func (c *Caret) calculatePLBK() {
	if c.seqNum != c.f.seqNum {
		c.seek(c.pos)
	}
}

// Seek satisfies the io.Seeker interface.
func (c *Caret) Seek(offset int64, whence int) (int64, error) {
	switch whence {
	case SeekSet:
		// No-op.
	case SeekCur:
		offset += int64(c.pos)
	case SeekEnd:
		offset += int64(c.f.len)
	default:
		return 0, errors.New("text: invalid seek whence")
	}
	if offset < 0 {
		return 0, errors.New("text: negative seek position")
	}
	if offset > int64(c.f.len) {
		offset = int64(c.f.len)
	}
	c.seek(int32(offset))
	return offset, nil
}

func (c *Caret) seek(off int32) {
	delta := off - c.pos
	// If the new offset is closer to the start or the end than to the current
	// c.pos, or if c's cached {p,l,b,k} values are invalid, move to the start
	// or end first. In case of a tie, we prefer to seek forwards (i.e. set
	// delta > 0).
	if (delta < 0 && -delta >= off) || (c.seqNum != c.f.seqNum) {
		c.seekStart()
		delta = off - c.pos
	}
	if delta > 0 && delta > int32(c.f.len)-off {
		c.seekEnd()
		delta = off - c.pos
	}

	if delta != 0 {
		// Seek forwards.
		for delta > 0 {
			if n := c.f.boxes[c.b].j - c.k; n > 0 {
				if n > delta {
					n = delta
				}
				c.pos += n
				c.k += n
				delta -= n
			} else if c.leanForwards() != leanOK {
				panic("text: invalid state")
			}
		}

		// Seek backwards.
		for delta < 0 {
			if n := c.f.boxes[c.b].i - c.k; n < 0 {
				if n < delta {
					n = delta
				}
				c.pos += n
				c.k += n
				delta -= n
			} else if c.leanBackwards() != leanOK {
				panic("text: invalid state")
			}
		}

		// A Caret can't be placed at the end of a Paragraph, unless it is the
		// final Paragraph. A simple way to enforce this is to lean forwards.
		c.leanForwards()
	}

	c.seqNum = c.f.seqNum
}

// Read satisfies the io.Reader interface by copying those bytes after the
// Caret and incrementing the Caret.
func (c *Caret) Read(buf []byte) (n int, err error) {
	c.calculatePLBK()
	for len(buf) > 0 {
		if j := c.f.boxes[c.b].j; c.k < j {
			nn := copy(buf, c.f.text[c.k:j])
			buf = buf[nn:]
			n += nn
			c.pos += int32(nn)
			c.k += int32(nn)
		}
		// A Caret can't be placed at the end of a Paragraph, unless it is the
		// final Paragraph. A simple way to enforce this is to lean forwards.
		if c.leanForwards() == leanFailedEOF {
			break
		}
	}
	if int(c.pos) == c.f.len {
		err = io.EOF
	}
	return n, err
}

// ReadByte returns the next byte after the Caret and increments the Caret.
func (c *Caret) ReadByte() (x byte, err error) {
	c.calculatePLBK()
	for {
		if j := c.f.boxes[c.b].j; c.k < j {
			x = c.f.text[c.k]
			c.pos++
			c.k++
			// A Caret can't be placed at the end of a Paragraph, unless it is
			// the final Paragraph. A simple way to enforce this is to lean
			// forwards.
			c.leanForwards()
			return x, nil
		}
		if c.leanForwards() == leanFailedEOF {
			return 0, io.EOF
		}
	}
}

// ReadRune returns the next rune after the Caret and increments the Caret.
func (c *Caret) ReadRune() (r rune, size int, err error) {
	c.calculatePLBK()
	for {
		if c.k < c.f.boxes[c.b].j {
			r, size, c.b, c.k = c.f.readRune(c.b, c.k)
			c.pos += int32(size)
			// A Caret can't be placed at the end of a Paragraph, unless it is
			// the final Paragraph. A simple way to enforce this is to lean
			// forwards.
			c.leanForwards()
			return r, size, nil
		}
		if c.leanForwards() == leanFailedEOF {
			return 0, 0, io.EOF
		}
	}
}

// WriteByte inserts x into the Frame's text at the Caret and increments the
// Caret.
func (c *Caret) WriteByte(x byte) error {
	c.tmp[0] = x
	return c.write(c.tmp[:1], "")
}

// WriteRune inserts r into the Frame's text at the Caret and increments the
// Caret.
func (c *Caret) WriteRune(r rune) (size int, err error) {
	size = utf8.EncodeRune(c.tmp[:], r)
	if err = c.write(c.tmp[:size], ""); err != nil {
		return 0, err
	}
	return size, nil
}

// WriteString inserts s into the Frame's text at the Caret and increments the
// Caret.
func (c *Caret) WriteString(s string) (n int, err error) {
	for len(s) > 0 {
		i := 1 + strings.IndexByte(s, '\n')
		if i == 0 {
			i = len(s)
		}
		if err = c.write(nil, s[:i]); err != nil {
			break
		}
		n += i
		s = s[i:]
	}
	return n, err
}

// Write inserts s into the Frame's text at the Caret and increments the Caret.
func (c *Caret) Write(s []byte) (n int, err error) {
	for len(s) > 0 {
		i := 1 + bytes.IndexByte(s, '\n')
		if i == 0 {
			i = len(s)
		}
		if err = c.write(s[:i], ""); err != nil {
			break
		}
		n += i
		s = s[i:]
	}
	return n, err
}

// write inserts a []byte or string into the Frame's text at the Caret.
//
// Exactly one of s0 and s1 must be non-empty. That non-empty argument must
// contain at most one '\n' and if it does contain one, it must be the final
// byte.
func (c *Caret) write(s0 []byte, s1 string) error {
	if m := maxLen - len(c.f.text); len(s0) > m || len(s1) > m {
		return errors.New("text: insufficient space for writing")
	}

	// Ensure that the Caret is at the end of its Box, and that Box's text is
	// at the end of the Frame's buffer.
	c.calculatePLBK()
	for {
		bb, n := &c.f.boxes[c.b], int32(len(c.f.text))
		if c.k == bb.j && c.k == n {
			break
		}

		// If the Box's text is empty, move its empty i:j range to the
		// equivalent empty range at the end of c.f.text.
		if bb.i == bb.j {
			bb.i = n
			bb.j = n
			for _, cc := range c.f.carets {
				if cc.b == c.b {
					cc.k = n
				}
			}
			continue
		}

		// Make the Caret be at the end of its Box.
		if c.k != bb.j {
			c.splitBox(true)
			continue
		}

		// Make a new empty Box and move the Caret to it.
		c.splitBox(true)
		c.leanForwards()
	}

	c.f.invalidateCaches()
	c.f.paragraphs[c.p].invalidateCaches()
	c.f.lines[c.l].invalidateCaches()

	length, nl := len(s0), false
	if length > 0 {
		nl = s0[length-1] == '\n'
		c.f.text = append(c.f.text, s0...)
	} else {
		length = len(s1)
		nl = s1[length-1] == '\n'
		c.f.text = append(c.f.text, s1...)
	}
	c.f.len += length
	c.f.boxes[c.b].j += int32(length)
	c.k += int32(length)
	for _, cc := range c.f.carets {
		if cc.pos > c.pos {
			cc.pos += int32(length)
		}
	}
	c.pos += int32(length)
	oldL := c.l

	if nl {
		breakParagraph(c.f, c.p, c.l, c.b)
		c.p = c.f.paragraphs[c.p].next
		c.l = c.f.paragraphs[c.p].firstL
		c.b = c.f.lines[c.l].firstB
		c.k = c.f.boxes[c.b].i
	}

	// TODO: re-layout the new c.p paragraph, if we saw '\n'.
	layout(c.f, oldL)

	c.f.seqNum++
	return nil
}

// breakParagraph breaks the Paragraph p into two Paragraphs, just after Box b
// in Line l in Paragraph p. b's text must end with a '\n'. The new Paragraph
// is inserted after p.
func breakParagraph(f *Frame, p, l, b int32) {
	// Assert that the Box b's text ends with a '\n'.
	if j := f.boxes[b].j; j == 0 || f.text[j-1] != '\n' {
		panic("text: invalid state")
	}

	// Make a new, empty Paragraph after this Paragraph p.
	newP, _ := f.newParagraph()
	nextP := f.paragraphs[p].next
	if nextP != 0 {
		f.paragraphs[nextP].prev = newP
	}
	f.paragraphs[newP].next = nextP
	f.paragraphs[newP].prev = p
	f.paragraphs[p].next = newP

	// Any Lines in this Paragraph after the break point's Line l move to the
	// newP Paragraph.
	if nextL := f.lines[l].next; nextL != 0 {
		f.lines[l].next = 0
		f.lines[nextL].prev = 0
		f.paragraphs[newP].firstL = nextL
	}

	// Any Boxes in this Line after the break point's Box b move to a new Line
	// at the start of the newP Paragraph.
	if nextB := f.boxes[b].next; nextB != 0 {
		f.boxes[b].next = 0
		f.boxes[nextB].prev = 0
		newL, _ := f.newLine()
		f.lines[newL].firstB = nextB
		if newPFirstL := f.paragraphs[newP].firstL; newPFirstL != 0 {
			f.lines[newL].next = newPFirstL
			f.lines[newPFirstL].prev = newL
		}
		f.paragraphs[newP].firstL = newL
	}

	// Make the newP Paragraph's first Line and first Box explicit, since
	// Carets require an explicit p, l and b.
	{
		pp := &f.paragraphs[newP]
		if pp.firstL == 0 {
			pp.firstL, _ = f.newLine()
		}
		ll := &f.lines[pp.firstL]
		if ll.firstB == 0 {
			ll.firstB, _ = f.newBox()
		}
	}

	// TODO: re-layout the newP paragraph.
}

// breakLine breaks the Line l at text index k in Box b. The b-and-k index must
// not be at the start or end of the Line. Text to the right of b-and-k in the
// Line l will be moved to the start of the next Line in the Paragraph, with
// that next Line being created if it didn't already exist.
func breakLine(f *Frame, l, b, k int32) {
	// Split this Box into two if necessary, so that k equals a Box's j end.
	bb := &f.boxes[b]
	if k != bb.j {
		if k == bb.i {
			panic("TODO: degenerate split left, possibly adjusting the Line's firstB??")
		}
		newB, realloc := f.newBox()
		if realloc {
			bb = &f.boxes[b]
		}
		nextB := bb.next
		if nextB != 0 {
			f.boxes[nextB].prev = newB
		}
		f.boxes[newB].next = nextB
		f.boxes[newB].prev = b
		f.boxes[newB].i = k
		f.boxes[newB].j = bb.j
		bb.next = newB
		bb.j = k
	}

	// Assert that the break point isn't already at the start or end of the Line.
	if bb.next == 0 || (bb.prev == 0 && k == bb.i) {
		panic("text: invalid state")
	}

	// Insert a line after this one, if one doesn't already exist.
	ll := &f.lines[l]
	if ll.next == 0 {
		newL, realloc := f.newLine()
		if realloc {
			ll = &f.lines[l]
		}
		f.lines[newL].prev = l
		ll.next = newL
	}

	// Move the remaining boxes to the next line.
	nextB, nextL := bb.next, ll.next
	bb.next = 0
	f.boxes[nextB].prev = 0
	fb := f.lines[nextL].firstB
	f.lines[nextL].firstB = nextB

	// If the next Line already contained Boxes, append them to the end of the
	// nextB chain, and join the two newly linked Boxes if possible.
	if fb != 0 {
		lb := f.lines[nextL].lastBox(f)
		lbb := &f.boxes[lb]
		fbb := &f.boxes[fb]
		lbb.next = fb
		fbb.prev = lb
		f.joinBoxes(lb, fb, lbb, fbb)
	}
}

// layout inserts a soft return in the Line l if its text measures longer than
// f.maxWidth and a suitable line break point is found. This may spill text
// onto the next line, which will also be laid out, and so on recursively.
func layout(f *Frame, l int32) {
	if f.maxWidth <= 0 || f.face == nil {
		return
	}
	f.seqNum++

	for ; l != 0; l = f.lines[l].next {
		var (
			firstB     = f.lines[l].firstB
			reader     = f.lineReader(firstB, f.boxes[firstB].i)
			breakPoint bAndK
			prevR      rune
			prevRValid bool
			advance    fixed.Int26_6
		)
		for {
			r, _, err := reader.ReadRune()
			if err != nil || r == '\n' {
				return
			}
			if prevRValid {
				advance += f.face.Kern(prevR, r)
			}
			// TODO: match all whitespace, not just ' '?
			if r == ' ' {
				breakPoint = reader.bAndK()
			}
			a, _ := f.face.GlyphAdvance(r)
			advance += a
			if r != ' ' && advance > f.maxWidth && breakPoint.b != 0 {
				breakLine(f, l, breakPoint.b, breakPoint.k)
				break
			}
			prevR, prevRValid = r, true
		}
	}
}

// Delete deletes nBytes bytes in the specified direction from the Caret's
// location. It returns the number of bytes deleted, which can be fewer than
// that requested if it hits the beginning or end of the Frame.
func (c *Caret) Delete(dir Direction, nBytes int) (dBytes int) {
	if nBytes <= 0 {
		return 0
	}

	// Convert a backwards delete of n bytes from position p to a forwards
	// delete of n bytes from position p-n.
	//
	// In general, it's easier to delete forwards than backwards. For example,
	// when crossing paragraph boundaries, it's easier to find the first line
	// of the next paragraph than the last line of the previous paragraph.
	if dir == Backwards {
		newPos := int(c.pos) - nBytes
		if newPos < 0 {
			newPos = 0
			nBytes = int(c.pos)
			if nBytes == 0 {
				return 0
			}
		}
		c.seek(int32(newPos))
	}

	if int(c.pos) == c.f.len {
		return 0
	}

	c.calculatePLBK()
	c.leanForwards()
	if c.f.boxes[c.b].i != c.k && c.splitBox(false) {
		c.leanForwards()
	}
	for nBytes > 0 && int(c.pos) != c.f.len {
		bb := &c.f.boxes[c.b]
		n := bb.j - bb.i
		newLine := n != 0 && c.f.text[bb.j-1] == '\n'
		if int(n) > nBytes {
			n = int32(nBytes)
		}
		bb.i += n
		c.k += n
		dBytes += int(n)
		nBytes -= int(n)
		c.f.len -= int(n)

		if bb.i != bb.j {
			break
		}

		if newLine {
			c.joinNextParagraph()
		}
		c.leanForwards()
	}

	// The mergeIntoOneLine will shake out any empty Boxes.
	l := c.f.mergeIntoOneLine(c.p)
	layout(c.f, l)
	c.f.invalidateCaches()

	// Compact c.f.text if it's large enough and the fraction of deleted text
	// is above some threshold. The actual threshold value (25%) is arbitrary.
	// A lower value means more frequent compactions, so less memory on average
	// but more CPU. A higher value means the opposite.
	if len(c.f.text) > 4096 && len(c.f.text)/4 < c.f.deletedLen() {
		c.f.compactText()
	}

	c.f.seqNum++
	for _, cc := range c.f.carets {
		if cc == c {
			continue
		}
		switch relPos := cc.pos - c.pos; {
		case relPos <= 0:
			// No-op.
		case relPos <= int32(dBytes):
			cc.pos = c.pos
		default:
			cc.pos -= int32(dBytes)
		}
	}
	return dBytes
}

// DeleteRunes deletes nRunes runes in the specified direction from the Caret's
// location. It returns the number of runes and bytes deleted, which can be
// fewer than that requested if it hits the beginning or end of the Frame.
func (c *Caret) DeleteRunes(dir Direction, nRunes int) (dRunes, dBytes int) {
	// Save the current Caret position, move the Caret by nRunes runes to
	// calculate how many bytes to delete, restore that saved Caret position,
	// then delete that many bytes.
	c.calculatePLBK()
	savedC := *c
	if dir == Forwards {
		for dRunes < nRunes {
			var size int
			_, size, c.b, c.k = c.f.readRune(c.b, c.k)
			if size != 0 {
				dRunes++
				dBytes += size
			} else if c.leanForwards() != leanOK {
				break
			}
		}
	} else {
		for dRunes < nRunes {
			var size int
			_, size, c.b, c.k = c.f.readLastRune(c.b, c.k)
			if size != 0 {
				dRunes++
				dBytes += size
			} else if c.leanBackwards() != leanOK {
				break
			}
		}
	}
	*c = savedC
	if dBytes != c.Delete(dir, dBytes) {
		panic("text: invalid state")
	}
	return dRunes, dBytes
}

// joinNextParagraph joins c's current and next Paragraph. That next Paragraph
// must exist, and c must be at the last Line of its current Paragraph.
func (c *Caret) joinNextParagraph() {
	pp0 := &c.f.paragraphs[c.p]
	ll0 := &c.f.lines[c.l]
	if pp0.next == 0 || ll0.next != 0 {
		panic("text: invalid state")
	}
	pp1 := &c.f.paragraphs[pp0.next]
	l1 := pp1.firstL

	ll0.next = l1
	c.f.lines[l1].prev = c.l

	toFree := pp0.next
	pp0.next = pp1.next
	if pp0.next != 0 {
		c.f.paragraphs[pp0.next].prev = c.p
	}

	c.f.freeParagraph(toFree)
}

// splitBox splits the Caret's Box into two, at the Caret's location. Unless
// force is set, it does nothing if the Caret is at either edge of its Box. It
// returns whether the Box was split. If so, the new Box is created after, not
// before, the Caret's current Box.
func (c *Caret) splitBox(force bool) bool {
	bb := &c.f.boxes[c.b]
	if !force && (c.k == bb.i || c.k == bb.j) {
		return false
	}
	newB, realloc := c.f.newBox()
	if realloc {
		bb = &c.f.boxes[c.b]
	}
	nextB := bb.next
	if nextB != 0 {
		c.f.boxes[nextB].prev = newB
	}
	c.f.boxes[newB] = Box{
		next: nextB,
		prev: c.b,
		i:    c.k,
		j:    bb.j,
	}
	bb.next = newB
	bb.j = c.k
	return true
}
