package core

import (
	
	

	
	
	
)

// Selection contains all regions of an input line that are currently selected/marked
// with either a begin and/or end position. The main selection is the visual one, used
// with the default cursor mark and position, and contains a list of additional surround
// selections used to change/select multiple parts of the line at once.
type Selection struct {
	Type       string // Can be a normal one, surrounding (pairs), (cursor) matchers, etc.
	active     bool   // The selection is running.
	visual     bool   // The selection is highlighted.
	visualLine bool   // The selection should span entire lines.
	bpos       int    // Beginning index position
	epos       int    // End index position (can be +1 in visual mode, to encompass cursor pos)
	kpos       int    // Keyword regexp matchers cycling counter.
	kmpos      int    // Keyword regexp matcher subgroups counter.

	// Display
	fg        string      // Foreground color of the highlighted selection.
	bg        string      // Background color.
	surrounds []Selection // Surrounds are usually pairs of characters matching each other (quotes/brackets, etc.)

	// Core
	line   *Line
	cursor *Cursor
}

// NewSelection is a required constructor to use for initializing
// a selection, as some numeric values must be negative by default.
func ( *Line,  *Cursor) *Selection {
	return &Selection{
		bpos:   -1,
		epos:   -1,
		line:   ,
		cursor: ,
	}
}

// Mark starts a pending selection at the specified position in the line.
// If the position is out of the line bounds, no selection is started.
// If this function is called on a surround selection, nothing happens.
func ( *Selection) ( int) {
	if  < 0 ||  > .line.Len() {
		return
	}

	.MarkRange(, -1)
}

// MarkRange starts a selection as a range in the input line. If either of
// begin/end pos are negative, it is replaced with the current cursor position.
// Any out of range positive value is replaced by the length of the line.
func ( *Selection) (,  int) {
	, ,  := .checkRange(, )
	if ! {
		return
	}

	.Type = "visual"
	.active = true
	.bpos = 
	.epos = 
	.bg = color.BgBlue
}

// MarkSurround creates two distinct selections each containing one rune.
// The first area starts at bpos, and the second one at epos. If either bpos
// is negative or epos is > line.Len()-1, no selection is created.
func ( *Selection) (,  int) {
	if  < 0 ||  > .line.Len()-1 {
		return
	}

	.active = true

	for ,  := range []int{, } {
		.surrounds = append(.surrounds, Selection{
			Type:   "surround",
			active: true,
			visual: true,
			bpos:   ,
			epos:   ,
			bg:     color.BgRed,
			line:   .line,
			cursor: .cursor,
		})
	}
}

// Active return true if the selection is active.
// When created, all selections are marked active,
// so that visual modes in Vim can work properly.
func ( *Selection) () bool {
	return .active
}

// Visual sets the selection as a visual one (highlighted),
// which is commonly seen in Vim edition mode.
// If line is true, the visual is extended to entire lines.
func ( *Selection) ( bool) {
	.visual = true
	.visualLine = 
}

// IsVisual indicates whether the selection should be highlighted.
func ( *Selection) () bool {
	return .visual
}

// Pos returns the begin and end positions of the selection.
// If any of these is not set, it is set to the cursor position.
// This is generally the case with "pending" visual selections.
func ( *Selection) () (,  int) {
	if .line.Len() == 0 || !.active {
		return -1, -1
	}

	, ,  := .checkRange(.bpos, .epos)
	if ! {
		return , 
	}

	// Use currently set values, or update if one is pending.
	.bpos, .epos = , 

	if  == -1 {
		,  = .selectToCursor()
	}

	if .visual {
		++
	}

	// Always check that neither the initial values nor the ones
	// that we might have updated are wrong. It's very rare that
	// the adjusted values would be invalid as a result of this
	// call (unfixable values), but better being too safe than not.
	, ,  = .checkRange(, )
	if ! {
		return -1, -1
	}

	return , 
}

// Cursor returns what should be the cursor position if the active
// selection is to be deleted, but also works for yank operations.
func ( *Selection) () int {
	,  := .Pos()
	if  == -1 &&  == -1 {
		return .cursor.Pos()
	}

	 := 

	if !.visual || !.visualLine {
		return 
	}

	var  int
	 := .cursor.Pos()

	// Get the indent of the cursor line.
	for  =  - 1;  >= 0; -- {
		if (*.line)[] == '\n' {
			break
		}
	}

	 =  -  - 1

	// If the selection includes the last line,
	// the cursor will move up the above line.
	var ,  int

	if  < .line.Len() {
		 =  + 1
		 = 
	} else {
		for  =  - 2;  >= 0; -- {
			if (*.line)[] == '\n' {
				break
			}
		}

		if  < -1 {
			 = -1
		}

		++
		 = 
	}

	// Now calculate the cursor position, the indent
	// must be less than the line characters.
	for  = ;  < .line.Len(); ++ {
		if (*.line)[] == '\n' {
			break
		}

		if + <=  {
			break
		}
	}

	// That cursor position might be bigger than the line itself:
	// it should be controlled when the line is redisplayed.
	 =  +  - 

	return 
}

// Len returns the length of the current selection. If any
// of begin/end pos is not set, the cursor position is used.
func ( *Selection) () int {
	if .line.Len() == 0 || (.bpos == .epos) {
		return 0
	}

	,  := .Pos()
	 := (*.line)[:]

	return len()
}

// Text returns the current selection as a string, but does not reset it.
func ( *Selection) () string {
	if .line.Len() == 0 {
		return ""
	}

	,  := .Pos()
	if  == -1 ||  == -1 {
		return ""
	}

	return string((*.line)[:])
}

// Pop returns the contents of the current selection as a string,
// as well as its begin and end position in the line, and the cursor
// position as given by the Cursor() method. Then, the selection is reset.
func ( *Selection) () ( string, , ,  int) {
	if .line.Len() == 0 {
		return "", -1, -1, 0
	}

	defer .Reset()

	,  = .Pos()
	if  == -1 ||  == -1 {
		return "", -1, -1, 0
	}

	 = .Cursor()
	 = string((*.line)[:])

	return , , , 
}

// InsertAt insert the contents of the selection into the line, between the
// begin and end position, effectively deleting everything in between those.
//
// If either or these positions is equal to -1, the selection content
// is inserted at the other position. If both are negative, nothing is done.
// This is equivalent to selection.Pop(), and line.InsertAt() combined.
//
// After insertion, the selection is reset.
func ( *Selection) (,  int) {
	, ,  := .checkRange(, )
	if ! {
		return
	}

	// Get and reset the selection.
	defer .Reset()

	 := .Text()

	switch {
	case  == -1,  == :
		.line.Insert(, []rune()...)
	default:
		.line.InsertBetween(, , []rune()...)
	}
}

// Surround surrounds the selection with a begin and end character,
// effectively inserting those characters into the current input line.
// If epos is greater than the line length, the line length is used.
// After insertion, the selection is reset.
func ( *Selection) (,  rune) {
	if .line.Len() == 0 || .Len() == 0 {
		return
	}

	defer .Reset()

	var  []rune
	 = append(, )
	 = append(, []rune(.Text())...)
	 = append(, )

	// The begin and end positions of the selection
	// is where we insert our new buffer.
	,  := .Pos()
	if  == -1 ||  == -1 {
		return
	}

	.line.InsertBetween(, , ...)
}

// SelectAWord selects a word around the current cursor position,
// selecting leading or trailing spaces depending on where the cursor
// is: if on a blank space, in a word, or at the end of the line.
func ( *Selection) () (,  int) {
	if .line.Len() == 0 {
		return
	}

	 = .cursor.Pos()
	 := 

	,  := .spacesAroundWord()

	,  = .line.SelectWord()
	.cursor.Set()
	 = .cursor.Pos()

	 :=  < .line.Len()-1 && isSpace((*.line)[+1])

	// And only select spaces after it if the word selected is not preceded
	// by spaces as well, or if we started the selection within this word.
	, _ = .adjustWordSelection(, , , )

	if !.Active() ||  <  {
		.Mark()
	}

	return , 
}

// SelectABlankWord selects a bigword around the current cursor position,
// selecting leading or trailing spaces depending on where the cursor is:
// if on a blank space, in a word, or at the end of the line.
func ( *Selection) () (,  int) {
	if .line.Len() == 0 {
		return
	}

	 = .cursor.Pos()
	,  := .spacesAroundWord()

	// If we are out of a word or in the middle of one, find its beginning.
	if ! && ! {
		.cursor.Inc()
		.cursor.Move(.line.Backward(.line.TokenizeSpace, .cursor.Pos()))
		 = .cursor.Pos()
	} else {
		.cursor.ToFirstNonSpace(true)
	}

	// Then go to the end of the blank word
	.cursor.Move(.line.ForwardEnd(.line.TokenizeSpace, .cursor.Pos()))
	 := .cursor.Pos() < .line.Len()-1 && isSpace((*.line)[.cursor.Pos()+1])

	// And only select spaces after it if the word selected is not preceded
	// by spaces as well, or if we started the selection within this word.
	, _ = .adjustWordSelection(, , , )

	if !.Active() ||  < .cursor.Pos() {
		.Mark()
	}

	return , .cursor.Pos()
}

// SelectAShellWord selects a shell word around the cursor position,
// selecting leading or trailing spaces depending on where the cursor
// is: if on a blank space, in a word, or at the end of the line.
func ( *Selection) () (,  int) {
	if .line.Len() == 0 {
		return , 
	}

	.cursor.CheckCommand()
	.cursor.ToFirstNonSpace(true)

	,  := .line.SurroundQuotes(true, .cursor.Pos())
	,  := .line.SurroundQuotes(false, .cursor.Pos())

	,  := strutil.AdjustSurroundQuotes(, , , )

	// If none of the quotes matched, use blank word
	if  == -1 &&  == -1 {
		,  = .line.SelectBlankWord(.cursor.Pos())
	}

	.cursor.Set()

	// The quotes might be followed by non-blank characters,
	// in which case we must keep expanding our selection.
	for {
		 :=  > 0 && isSpace((*.line)[-1])
		if  {
			.cursor.Dec()
			.cursor.ToFirstNonSpace(false)
			.cursor.Inc()

			break
		} else if  == 0 {
			break
		}

		.cursor.Move(.line.Backward(.line.TokenizeSpace, .cursor.Pos()))
		 = .cursor.Pos()
	}

	 = .cursor.Pos()
	.cursor.Set()

	// Adjust if no spaces after.
	for {
		 :=  < .line.Len()-1 && isSpace((*.line)[+1])
		if  ||  == .line.Len()-1 {
			break
		}

		.cursor.Move(.line.ForwardEnd(.line.TokenizeSpace, ))
		 = .cursor.Pos()
	}

	// Else set the region inside those quotes
	if !.Active() ||  < .cursor.Pos() {
		.Mark()
	}

	return , 
}

// SelectKeyword attempts to find a pattern in the current blank word
// around the current cursor position, using various regular expressions.
// Repeatedly calling this function will cycle through all regex matches,
// or if a matcher captured multiple subgroups, through each of those groups.
//
// Those are, in the order in which they are tried:
// URI / URL / Domain|IPv4|IPv6 / URL path component / URL parameters.
//
// The returned positions are the beginning and end positions of the match
// on the line (absolute position, not relative to cursor), or if no matcher
// succeeds, the bpos and epos parameters are returned unchanged.
// If found is true, it means a match occurred, otherwise false is returned.
func ( *Selection) (,  int,  bool) (,  int,  bool) {
	if .line.Len() == 0 {
		return , , false
	}

	 := (*.line)[:]

	_, , ,  = .matchKeyword(, , )
	if ! {
		return , , false
	}

	// Always check the validity of the selection
	, ,  = .checkRange(, )
	if ! {
		return , , false
	}

	// Mark the selection at its beginning
	// and move the cursor to its end.
	.Mark()

	return , , true
}

// ReplaceWith replaces all characters of the line within the current
// selection range by applying to each rune the provided replacer function.
// After replacement, the selection is reset.
func ( *Selection) ( func( rune) rune) {
	if .line.Len() == 0 || .Len() == 0 {
		return
	}

	defer .Reset()

	,  := .Pos()
	if  == -1 ||  == -1 {
		return
	}

	for  := ;  < ; ++ {
		 := (*.line)[]
		 = ()
		(*.line)[] = 
	}
}

// Cut deletes the current selection from the line, updates the cursor position
// and returns the deleted content, which can then be passed to the shell registers.
// After deletion, the selection is reset.
func ( *Selection) () ( string) {
	if .line.Len() == 0 {
		return
	}

	defer .Reset()

	switch {
	case len(.surrounds) > 0:
		 := 0

		for ,  := range .surrounds {
			.line.CutRune(.bpos - )

			++
		}

	default:
		,  := .Pos()
		if  == -1 ||  == -1 {
			return
		}

		 = .Text()

		.line.Cut(, )
	}

	return
}

// Surrounds returns all surround-selected regions contained by the selection.
func ( *Selection) () []Selection {
	return .surrounds
}

// Highlights returns the highlighting sequences for the selection.
func ( *Selection) () (,  string) {
	return .fg, .bg
}

// HighlightMatchers adds highlighting to matching
// parens when the cursor is on one of them.
func ( *Selection) {
	 := .cursor.Pos()

	if .line.Len() == 0 ||  == .line.Len() {
		return
	}

	if strutil.IsBracket(.cursor.Char()) {
		var ,  int

		, ,  := .line.TokenizeBlock()

		switch {
		case len() == 0:
			return
		case  == 0 && len() > :
			 = len([])
		default:
			 =  * -1
		}

		 =  + 

		.surrounds = append(.surrounds, Selection{
			Type:   "matcher",
			active: true,
			visual: true,
			bpos:   ,
			epos:   ,
			bg:     color.Fmt("240"),
			line:   .line,
			cursor: .cursor,
		})
	}
}

// ResetMatchers is used by the display engine
// to reset matching parens highlighting regions.
func ( *Selection) {
	var  []Selection

	for ,  := range .surrounds {
		if .Type == "matcher" {
			continue
		}

		 = append(, )
	}

	.surrounds = 
}

// Reset makes the current selection inactive, resetting all of its values.
func ( *Selection) () {
	.Type = ""
	.active = false
	.visual = false
	.visualLine = false
	.bpos = -1
	.epos = -1
	.kpos = 0
	.fg = ""
	.bg = ""

	// Get rid of all surround regions but matcher ones.
	 := make([]Selection, 0)

	for ,  := range .surrounds {
		if .Type != "matcher" {
			continue
		}

		 = append(, )
	}

	.surrounds = 
}

func ( *Selection) (,  int) (int, int, bool) {
	// Return on some on unfixable cases.
	switch {
	case .line.Len() == 0:
		return -1, -1, false
	case  < 0 &&  < 0:
		return -1, -1, false
	case  > .line.Len() &&  > .line.Len():
		return -1, -1, false
	}

	// Adjust positive out-of-range values
	if  > .line.Len() {
		 = .line.Len()
	}

	if  > .line.Len() {
		 = .line.Len()
	}

	// Adjust negative values when pending.
	if  < 0 {
		,  = , -1
	} else if  < 0 {
		 = -1
	}

	// And reorder if inversed.
	if  >  &&  != -1 {
		,  = , 
	}

	return , , true
}

func ( *Selection) ( int) (int, int) {
	var  int

	// The cursor might be now before its original mark,
	// in which case we invert before doing any move.
	if .cursor.Pos() <  {
		,  = .cursor.Pos(), 
	} else {
		 = .cursor.Pos()
	}

	if .visualLine {
		// To beginning of line
		for --;  >= 0; -- {
			if (*.line)[] == '\n' {
				++
				break
			}
		}

		if  == -1 {
			 = 0
		}

		// To end of line
		for ;  < .line.Len(); ++ {
			if  == -1 {
				 = 0
			}

			if (*.line)[] == '\n' {
				break
			}
		}
	}

	// Check again in case the visual line inverted both.
	if  >  {
		,  = , 
	}

	return , 
}

func ( *Selection) ( int) (,  bool) {
	 = isSpace(.cursor.Char())
	 =  > 0 && isSpace((*.line)[-1])

	return
}

func isSpace( rune) bool {
	return unicode.IsSpace() &&  != inputrc.Newline
}

// adjustWordSelection adjust the beginning and end of a word (blank or not) selection, depending
// on whether it's surrounded by spaces, and if selection started from a whitespace or within word.
func ( *Selection) (, ,  bool,  int) (int, int) {
	var  int

	if  && ! {
		.cursor.Inc()
		.cursor.ToFirstNonSpace(true)
		.cursor.Dec()
	} else if ! {
		 = .cursor.Pos()
		.cursor.Set( - 1)
		.cursor.ToFirstNonSpace(false)
		.cursor.Inc()
		 = .cursor.Pos()
		.cursor.Set()
	}

	 = .cursor.Pos()

	return , 
}

func ( *Selection) ( []rune,  int,  bool) ( string,  bool, ,  int) {
	 := []string{
		"URI",
	}

	 := map[string]*regexp.Regexp{
		"URI": regexp.MustCompile(`https?:\/\/(?:www\.)?([-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b)*(\/[\/\d\w\.-]*)*(?:[\?])*(.+)*`),
	}

	// The matcher name and all the capturing subgroups it found.
	var  string
	var  []int

	// Always preload the current/last target matcher used.
	if .kpos > 0 && .kpos <= len() {
		 = [.kpos-1]
		 := []

		 = .runMatcher(, )
	}

	// Either increment/decrement the counter (switch the matcher altogether),
	// or cycle through the match groups for the current matcher.
	if , ,  := .cycleSubgroup(, , );  {
		return , true, , 
	}

	// We did not cycle through subgroups, so cycle the matchers.
	 := .kpos
	.kmpos = 1

	if  {
		.kpos++
	} else {
		.kpos--
	}

	if .kpos > len() {
		.kpos = 1
	}

	if .kpos < 1 {
		.kpos = len()
	}

	// Prepare the cycling functions, increments and counters.
	var (
		 = .kpos
		 func( int) bool
		 func( int) int
	)

	if  {
		 = func( int) bool { return  <= len() }
		 = func( int) int { return  + 1 }
	} else {
		 = func( int) bool { return  > 0 }
		 = func( int) int { return  - 1 }
	}

	// Try the different matchers until one succeeds, and select the first/last capturing group.
	for () {
		 = [-1]
		 := []

		 = .runMatcher(, )

		if len() <= 1 {
			 = ()
			continue
		}

		if ! &&  == 1 {
			.kmpos = len()

			return , true,  + [len()-2],  + [len()-1]
		}

		return , true,  + [0],  + [1]
	}

	return "", false, -1, -1
}

func ( *Selection) ( []int,  int,  bool) (,  int,  bool) {
	 := ( && .kmpos < len()-1) || (! && .kmpos > 1)

	if ! {
		return
	}

	if  {
		.kmpos++
	} else {
		.kmpos--
	}

	return  + [.kmpos-1],  + [.kmpos], true
}

func ( *Selection) ( []rune,  *regexp.Regexp) ( []int) {
	if  == nil {
		return
	}

	 := .FindAllStringSubmatchIndex(string(), -1)
	if len() == 0 {
		return
	}

	return [0]
}

// Tested URL / IP regexp matchers, not working as well as the current ones
//
// "URL": regexp.MustCompile(`([\w+]+\:\/\/)?([\w\d-]+\.)*[\w-]+[\.\:]\w+([\/\?\=\&\#.]?[\w-]+)*\/?`),

// "Domain|IP": regexp.MustCompile(ipv4_regex + `|` + ipv6_regex + `|` + domain_regex),
// (http(s?):\/\/)?(((www\.)?+[a-zA-Z0-9\.\-\_]+(\.[a-zA-Z]{2,3})+)|(\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b))(\/[a-zA-Z0-9\_\-\s\.\/\?\%\#\&\=]*)?
// "Domain|IP": regexp.MustCompile(`((www\.)?[a-zA-Z0-9\.\-\_]+(\.[a-zA-Z]{2,3})+)|(\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b)`),

// "URL path": ,
// "URL parameters": regexp.MustCompile(`[(\?|\&)]([^=]+)\=([^&#]+)`),

// ipv6Regex   = `^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))`
// ipv4Regex   = `^(((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4})`
// domainRegex = `^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]`