package termutil

func (buffer *Buffer) ClearSelection() {
	buffer.selectionStart = nil
	buffer.selectionEnd = nil
}

func (buffer *Buffer) GetBoundedTextAtPosition(pos Position) (start Position, end Position, text string, textIndex int, found bool) {
	return buffer.FindWordAt(pos, func(r rune) bool {
		return r > 0 && r < 256
	})
}

// if the selection is invalid - e.g. lines are selected that no longer exist in the buffer
func (buffer *Buffer) fixSelection() bool {
	if buffer.selectionStart == nil || buffer.selectionEnd == nil {
		return false
	}

	if buffer.selectionStart.Line >= uint64(len(buffer.lines)) {
		buffer.selectionStart.Line = uint64(len(buffer.lines)) - 1
	}

	if buffer.selectionEnd.Line >= uint64(len(buffer.lines)) {
		buffer.selectionEnd.Line = uint64(len(buffer.lines)) - 1
	}

	if buffer.selectionStart.Col >= uint16(len(buffer.lines[buffer.selectionStart.Line].cells)) {
		buffer.selectionStart.Col = 0
		if buffer.selectionStart.Line < uint64(len(buffer.lines))-1 {
			buffer.selectionStart.Line++
		}
	}

	if buffer.selectionEnd.Col >= uint16(len(buffer.lines[buffer.selectionEnd.Line].cells)) {
		buffer.selectionEnd.Col = uint16(len(buffer.lines[buffer.selectionEnd.Line].cells)) - 1
	}

	return true
}

func (buffer *Buffer) ExtendSelectionToEntireLines() {
	if !buffer.fixSelection() {
		return
	}

	buffer.selectionStart.Col = 0
	buffer.selectionEnd.Col = uint16(len(buffer.lines[buffer.selectionEnd.Line].cells)) - 1
}

type RuneMatcher func(r rune) bool

func (buffer *Buffer) SelectWordAt(pos Position, runeMatcher RuneMatcher) {
	start, end, _, _, found := buffer.FindWordAt(pos, runeMatcher)
	if !found {
		return
	}
	buffer.setRawSelectionStart(start)
	buffer.setRawSelectionEnd(end)
}

// takes raw coords
func (buffer *Buffer) Highlight(start Position, end Position, annotation *Annotation) {
	buffer.highlightStart = &start
	buffer.highlightEnd = &end
	buffer.highlightAnnotation = annotation
}

func (buffer *Buffer) ClearHighlight() {
	buffer.highlightStart = nil
	buffer.highlightEnd = nil
}

// returns raw lines
func (buffer *Buffer) FindWordAt(pos Position, runeMatcher RuneMatcher) (start Position, end Position, text string, textIndex int, found bool) {
	line := buffer.convertViewLineToRawLine(uint16(pos.Line))
	col := pos.Col

	if line >= uint64(len(buffer.lines)) {
		return
	}
	if col >= uint16(len(buffer.lines[line].cells)) {
		return
	}

	if !runeMatcher(buffer.lines[line].cells[col].r.Rune) {
		return
	}

	found = true

	start = Position{
		Line: line,
		Col:  col,
	}
	end = Position{
		Line: line,
		Col:  col,
	}

	var startCol uint16
BACK:
	for y := int(line); y >= 0; y-- {
		if y == int(line) {
			startCol = col
		} else {
			if len(buffer.lines[y].cells) < int(buffer.viewWidth) {
				break
			}
			startCol = uint16(len(buffer.lines[y].cells) - 1)
		}
		for x := int(startCol); x >= 0; x-- {
			if runeMatcher(buffer.lines[y].cells[x].r.Rune) {
				start = Position{
					Line: uint64(y),
					Col:  uint16(x),
				}
				text = string(buffer.lines[y].cells[x].r.Rune) + text
			} else {
				break BACK
			}
		}

	}
	textIndex = len([]rune(text)) - 1
FORWARD:
	for y := uint64(line); y < uint64(len(buffer.lines)); y++ {
		if y == line {
			startCol = col + 1
		} else {
			startCol = 0
		}
		for x := int(startCol); x < len(buffer.lines[y].cells); x++ {
			if runeMatcher(buffer.lines[y].cells[x].r.Rune) {
				end = Position{
					Line: y,
					Col:  uint16(x),
				}
				text = text + string(buffer.lines[y].cells[x].r.Rune)
			} else {
				break FORWARD
			}
		}
		if len(buffer.lines[y].cells) < int(buffer.viewWidth) {
			break
		}
	}

	return
}

func (buffer *Buffer) SetSelectionStart(pos Position) {
	buffer.selectionStart = &Position{
		Col:  pos.Col,
		Line: buffer.convertViewLineToRawLine(uint16(pos.Line)),
	}
}

func (buffer *Buffer) setRawSelectionStart(pos Position) {
	buffer.selectionStart = &pos
}

func (buffer *Buffer) SetSelectionEnd(pos Position) {
	buffer.selectionEnd = &Position{
		Col:  pos.Col,
		Line: buffer.convertViewLineToRawLine(uint16(pos.Line)),
	}
}

func (buffer *Buffer) setRawSelectionEnd(pos Position) {
	buffer.selectionEnd = &pos
}

func (buffer *Buffer) GetSelection() (string, *Selection) {
	if !buffer.fixSelection() {
		return "", nil
	}

	start := *buffer.selectionStart
	end := *buffer.selectionEnd

	if end.Line < start.Line || (end.Line == start.Line && end.Col < start.Col) {
		swap := end
		end = start
		start = swap
	}

	var text string
	for y := start.Line; y <= end.Line; y++ {
		line := buffer.lines[y]
		startX := 0
		endX := len(line.cells) - 1
		if y == start.Line {
			startX = int(start.Col)
		}
		if y == end.Line {
			endX = int(end.Col)
		}
		if y > start.Line {
			text += "\n"
		}
		for x := startX; x <= endX; x++ {
			mr := line.cells[x].Rune()
			x += mr.Width - 1
			text += string(mr.Rune)
		}
	}

	viewSelection := Selection{
		Start: start,
		End:   end,
	}

	viewSelection.Start.Line = uint64(buffer.convertRawLineToViewLine(viewSelection.Start.Line))
	viewSelection.End.Line = uint64(buffer.convertRawLineToViewLine(viewSelection.End.Line))
	return text, &viewSelection
}

func (buffer *Buffer) InSelection(pos Position) bool {

	if !buffer.fixSelection() {
		return false
	}

	start := *buffer.selectionStart
	end := *buffer.selectionEnd

	if end.Line < start.Line || (end.Line == start.Line && end.Col < start.Col) {
		swap := end
		end = start
		start = swap
	}

	rY := buffer.convertViewLineToRawLine(uint16(pos.Line))
	if rY < start.Line {
		return false
	}
	if rY > end.Line {
		return false
	}
	if rY == start.Line {
		if pos.Col < start.Col {
			return false
		}
	}
	if rY == end.Line {
		if pos.Col > end.Col {
			return false
		}
	}

	return true
}

func (buffer *Buffer) GetHighlightAnnotation() *Annotation {
	return buffer.highlightAnnotation
}

// takes view coords
func (buffer *Buffer) IsHighlighted(pos Position) bool {

	if buffer.highlightStart == nil || buffer.highlightEnd == nil {
		return false
	}

	if buffer.highlightStart.Line >= uint64(len(buffer.lines)) {
		return false
	}

	if buffer.highlightEnd.Line >= uint64(len(buffer.lines)) {
		return false
	}

	if buffer.highlightStart.Col >= uint16(len(buffer.lines[buffer.highlightStart.Line].cells)) {
		return false
	}

	if buffer.highlightEnd.Col >= uint16(len(buffer.lines[buffer.highlightEnd.Line].cells)) {
		return false
	}

	start := *buffer.highlightStart
	end := *buffer.highlightEnd

	if end.Line < start.Line || (end.Line == start.Line && end.Col < start.Col) {
		swap := end
		end = start
		start = swap
	}

	rY := buffer.convertViewLineToRawLine(uint16(pos.Line))
	if rY < start.Line {
		return false
	}
	if rY > end.Line {
		return false
	}
	if rY == start.Line {
		if pos.Col < start.Col {
			return false
		}
	}
	if rY == end.Line {
		if pos.Col > end.Col {
			return false
		}
	}

	return true
}
