package cview

import (
	
	
	
	
	

	
	
	
)

// TrueColorTags is a flag which controls whether color tags should render as
// the specific colors defined by tcell, or as the colors defined by the user's
// terminal configuration (the default).
var TrueColorTags = false

// ColorUnset represents an unset color. This is necessary because the zero
// value of color, ColorDefault, results in default terminal colors.
var ColorUnset = tcell.ColorSpecial | 108

// Horizontal alignment within a box.
const (
	AlignLeft = iota
	AlignCenter
	AlignRight
)

// VerticalAlignment represents vertical alignment.
type VerticalAlignment int

// Vertical alignment within a box.
const (
	AlignTop VerticalAlignment = iota
	AlignMiddle
	AlignBottom
)

// Common regular expressions.
var (
	colorPattern     = regexp.MustCompile(`\[([a-zA-Z]+|#[0-9a-zA-Z]{6}|\-)?(:([a-zA-Z]+|#[0-9a-zA-Z]{6}|\-)?(:([bdilrsu]+|\-)?)?)?\]`)
	regionPattern    = regexp.MustCompile(`\["([a-zA-Z0-9_,;: \-\.]*)"\]`)
	escapePattern    = regexp.MustCompile(`\[([a-zA-Z0-9_,;: \-\."#]+)\[(\[*)\]`)
	nonEscapePattern = regexp.MustCompile(`(\[[a-zA-Z0-9_,;: \-\."#]+\[*)\]`)
	boundaryPattern  = regexp.MustCompile(`(([,\.\-:;!\?&#+]|\n)[ \t\f\r]*|([ \t\f\r]+))`)
	spacePattern     = regexp.MustCompile(`\s+`)
)

// Positions of substrings in regular expressions.
const (
	colorForegroundPos = 1
	colorBackgroundPos = 3
	colorFlagPos       = 5
)

// Predefined InputField acceptance functions.
var (
	// InputFieldInteger accepts integers.
	InputFieldInteger func(text string, ch rune) bool

	// InputFieldFloat accepts floating-point numbers.
	InputFieldFloat func(text string, ch rune) bool

	// InputFieldMaxLength returns an input field accept handler which accepts
	// input strings up to a given length. Use it like this:
	//
	//   inputField.SetAcceptanceFunc(InputFieldMaxLength(10)) // Accept up to 10 characters.
	InputFieldMaxLength func(maxLength int) func(text string, ch rune) bool
)

// Transformation describes a widget state modification.
type Transformation int

// Widget transformations.
const (
	TransformFirstItem    Transformation = 1
	TransformLastItem     Transformation = 2
	TransformPreviousItem Transformation = 3
	TransformNextItem     Transformation = 4
	TransformPreviousPage Transformation = 5
	TransformNextPage     Transformation = 6
)

// Package initialization.
func init() {
	runewidth.EastAsianWidth = true
	runewidth.CreateLUT() // Create lookup table

	// Initialize the predefined input field handlers.
	InputFieldInteger = func( string,  rune) bool {
		if  == "-" {
			return true
		}
		,  := strconv.Atoi()
		return  == nil
	}
	InputFieldFloat = func( string,  rune) bool {
		if  == "-" ||  == "." ||  == "-." {
			return true
		}
		,  := strconv.ParseFloat(, 64)
		return  == nil
	}
	InputFieldMaxLength = func( int) func( string,  rune) bool {
		return func( string,  rune) bool {
			return len([]rune()) <= 
		}
	}
}

// StripTags returns the provided text without color and/or region tags.
func ( []byte,  bool,  bool) []byte {
	if ! && ! {
		 := make([]byte, len())
		copy(, )
		return 
	}

	var  []byte
	 := 
	if  {
		 = regionPattern.ReplaceAll(, nil)
		 = 
	}
	if  {
		 = colorPattern.ReplaceAllFunc(, func( []byte) []byte {
			if len() > 2 {
				return nil
			}
			return 
		})
	}

	return escapePattern.ReplaceAll(, []byte(`[$1$2]`))
}

// ColorHex returns the hexadecimal value of a color as a string, prefixed with #.
// If the color is invalid, a blank string is returned.
func ( tcell.Color) string {
	if !.Valid() {
		return ""
	}
	, ,  := .RGB()
	return fmt.Sprintf("#%02x%02x%02x", , , )
}

// styleFromTag takes the given style, defined by a foreground color (fgColor),
// a background color (bgColor), and style attributes, and modifies it based on
// the substrings (tagSubstrings) extracted by the regular expression for color
// tags. The new colors and attributes are returned where empty strings mean
// "don't modify" and a dash ("-") means "reset to default".
func styleFromTag(, ,  string,  [][]byte) (, ,  string) {
	if len([colorForegroundPos]) > 0 {
		 := string([colorForegroundPos])
		if  == "-" {
			 = "-"
		} else if  != "" {
			 = 
		}
	}

	if len([colorBackgroundPos-1]) > 0 {
		 := string([colorBackgroundPos])
		if  == "-" {
			 = "-"
		} else if  != "" {
			 = 
		}
	}

	if len([colorFlagPos-1]) > 0 {
		 := string([colorFlagPos])
		if  == "-" {
			 = "-"
		} else if  != "" {
			 = 
		}
	}

	return , , 
}

// overlayStyle mixes a background color with a foreground color (fgColor),
// a (possibly new) background color (bgColor), and style attributes, and
// returns the resulting style. For a definition of the colors and attributes,
// see styleFromTag(). Reset instructions cause the corresponding part of the
// default style to be used.
func overlayStyle( tcell.Color,  tcell.Style, , ,  string) tcell.Style {
	, ,  := .Decompose()
	 := .Background()

	 = .Foreground()
	if  != "" {
		if  == "-" {
			 = .Foreground()
		} else {
			 := tcell.GetColor()
			if TrueColorTags {
				 = .TrueColor()
			}
			 = .Foreground()
		}
	}

	if  == "-" ||  == "" &&  != tcell.ColorDefault {
		 = .Background()
	} else if  != "" {
		 := tcell.GetColor()
		if TrueColorTags {
			 = .TrueColor()
		}
		 = .Background()
	}

	if  == "-" {
		 = .Bold(&tcell.AttrBold > 0)
		 = .Dim(&tcell.AttrDim > 0)
		 = .Italic(&tcell.AttrItalic > 0)
		 = .Blink(&tcell.AttrBlink > 0)
		 = .Reverse(&tcell.AttrReverse > 0)
		 = .StrikeThrough(&tcell.AttrStrikeThrough > 0)
		 = .Underline(&tcell.AttrUnderline > 0)
	} else if  != "" {
		 = .Normal()
		for ,  := range  {
			switch  {
			case 'b':
				 = .Bold(true)
			case 'd':
				 = .Dim(true)
			case 'i':
				 = .Italic(true)
			case 'l':
				 = .Blink(true)
			case 'r':
				 = .Reverse(true)
			case 's':
				 = .StrikeThrough(true)
			case 'u':
				 = .Underline(true)
			}
		}
	}

	return 
}

// SetAttributes sets attributes on a style.
func ( tcell.Style,  tcell.AttrMask) tcell.Style {
	return .
		Bold(&tcell.AttrBold != 0).
		Dim(&tcell.AttrDim != 0).
		Italic(&tcell.AttrItalic != 0).
		Blink(&tcell.AttrBlink != 0).
		Reverse(&tcell.AttrReverse != 0).
		StrikeThrough(&tcell.AttrStrikeThrough != 0).
		Underline(&tcell.AttrUnderline != 0)
}

// decomposeText returns information about a string which may contain color
// tags or region tags, depending on which ones are requested to be found. It
// returns the indices of the color tags (as returned by
// re.FindAllStringIndex()), the color tags themselves (as returned by
// re.FindAllStringSubmatch()), the indices of region tags and the region tags
// themselves, the indices of an escaped tags (only if at least color tags or
// region tags are requested), the string stripped by any tags and escaped, and
// the screen width of the stripped string.
func decomposeText( []byte, ,  bool) ( [][]int,  [][][]byte,  [][]int,  [][][]byte,  [][]int,  []byte,  int) {
	// Shortcut for the trivial case.
	if ! && ! {
		return nil, nil, nil, nil, nil, , runewidth.StringWidth(string())
	}

	// Get positions of any tags.
	if  {
		 = colorPattern.FindAllIndex(, -1)
		 = colorPattern.FindAllSubmatch(, -1)
	}
	if  {
		 = regionPattern.FindAllIndex(, -1)
		 = regionPattern.FindAllSubmatch(, -1)
	}
	 = escapePattern.FindAllIndex(, -1)

	// Because the color pattern detects empty tags, we need to filter them out.
	for  := len() - 1;  >= 0; -- {
		if [][1]-[][0] == 2 {
			 = append([:], [+1:]...)
			 = append([:], [+1:]...)
		}
	}

	// Make a (sorted) list of all tags.
	var  [][]int
	if  &&  {
		 = 
		 = make([][]int, len()+len())
		copy(, )
		copy([len():], )
		sort.Slice(, func( int,  int) bool {
			return [][0] < [][0]
		})
	} else if  {
		 = 
	} else {
		 = 
	}

	// Remove the tags from the original string.
	var  int
	 := make([]byte, 0, len())
	for ,  := range  {
		 = append(, [:[0]]...)
		 = [1]
	}
	 = append(, [:]...)

	// Escape string.
	 = escapePattern.ReplaceAll(, []byte("[$1$2]"))

	// Get the width of the stripped string.
	 = runewidth.StringWidth(string())

	return
}

// Print prints text onto the screen into the given box at (x,y,maxWidth,1),
// not exceeding that box. "align" is one of AlignLeft, AlignCenter, or
// AlignRight. The screen's background color will not be changed.
//
// You can change the colors and text styles mid-text by inserting a color tag.
// See the package description for details.
//
// Returns the number of actual bytes of the text printed (including color tags)
// and the actual width used for the printed runes.
func ( tcell.Screen,  []byte, , , ,  int,  tcell.Color) (int, int) {
	return PrintStyle(, , , , , , tcell.StyleDefault.Foreground())
}

// PrintStyle works like Print() but it takes a style instead of just a
// foreground color.
func ( tcell.Screen,  []byte, , , ,  int,  tcell.Style) (int, int) {
	if  <= 0 || len() == 0 {
		return 0, 0
	}

	// Decompose the text.
	, , , , , ,  := decomposeText(, true, false)

	// We want to reduce all alignments to AlignLeft.
	if  == AlignRight {
		if  <=  {
			// There's enough space for the entire text.
			return (, , +-, , , AlignLeft, )
		}
		// Trim characters off the beginning.
		var (
			, , , ,  int
			, ,  string
		)
		, ,  := .Decompose()
		iterateString(string(), func( rune,  []rune, , , ,  int) bool {
			// Update color/escape tag offset and style.
			if  < len() && + >= [][0] && + < [][1] {
				, ,  = styleFromTag(, , , [])
				 = overlayStyle(, , , , )
				 += [][1] - [][0]
				++
			}
			if  < len() && + >= [][0] && + < [][1] {
				++
				++
			}
			if - <  {
				// We chopped off enough.
				if  > 0 && +-1 >= [-1][0] && +-1 < [-1][1] {
					// Unescape open escape sequences.
					 := [-1][1] - 2
					 = append([:], [+1:]...)
				}
				// Print and return.
				,  = (, [+:], , , , AlignLeft, )
				return true
			}
			return false
		})
		return , 
	} else if  == AlignCenter {
		if  ==  {
			// Use the exact space.
			return (, , , , , AlignLeft, )
		} else if  <  {
			// We have more space than we need.
			 := ( - ) / 2
			return (, , +, , -, AlignLeft, )
		} else {
			// Chop off runes until we have a perfect fit.
			var , , ,  int
			 = len()
			for -1 >  && -- >  {
				if  <  {
					// Iterate on the left by one character.
					iterateString(string([:]), func( rune,  []rune, , , ,  int) bool {
						 += 
						 += 
						return true
					})
				} else {
					// Iterate on the right by one character.
					iterateStringReverse(string([:]), func( rune,  []rune, , , ,  int) bool {
						 += 
						 -= 
						return true
					})
				}
			}

			// Add tag offsets and determine start style.
			var (
				, ,                int
				, ,  string
			)
			, ,  := .Decompose()
			for  := range  {
				// We only need the offset of the left index.
				if  >  {
					// We're done.
					if  > 0 && +-1 >= [-1][0] && +-1 < [-1][1] {
						// Unescape open escape sequences.
						 := [-1][1] - 2
						 = append([:], [+1:]...)
					}
					break
				}

				// Update color/escape tag offset.
				if  < len() && + >= [][0] && + < [][1] {
					if  <=  {
						, ,  = styleFromTag(, , , [])
						 = overlayStyle(, , , , )
					}
					 += [][1] - [][0]
					++
				}
				if  < len() && + >= [][0] && + < [][1] {
					++
					++
				}
			}
			return (, [+:], , , , AlignLeft, )
		}
	}

	// Draw text.
	var (
		, , , ,  int
		, ,       string
	)
	iterateString(string(), func( rune,  []rune, , , ,  int) bool {
		// Only continue if there is still space.
		if + >  {
			return true
		}

		// Handle color tags.
		for  < len() && + >= [][0] && + < [][1] {
			, ,  = styleFromTag(, , , [])
			 += [][1] - [][0]
			++
		}

		// Handle scape tags.
		if  < len() && + >= [][0] && + < [][1] {
			if + == [][1]-2 {
				++
				++
			}
		}

		// Print the rune sequence.
		 :=  + 
		, , ,  := .GetContent(, )
		, ,  := .Decompose()
		 = overlayStyle(, , , , )
		for  :=  - 1;  >= 0; -- {
			// To avoid undesired effects, we populate all cells.
			if  == 0 {
				.SetContent(+, , , , )
			} else {
				.SetContent(+, , ' ', nil, )
			}
		}

		// Advance.
		 += 
		 += 

		return false
	})

	return  +  + len(), 
}

// PrintSimple prints white text to the screen at the given position.
func ( tcell.Screen,  []byte, ,  int) {
	Print(, , , , math.MaxInt32, AlignLeft, Styles.PrimaryTextColor)
}

// TaggedTextWidth returns the width of the given string needed to print it on
// screen. The text may contain color tags which are not counted.
func ( []byte) int {
	, , , , , ,  := decomposeText(, true, false)
	return 
}

// TaggedStringWidth returns the width of the given string needed to print it on
// screen. The text may contain color tags which are not counted.
func ( string) int {
	return TaggedTextWidth([]byte())
}

// WordWrap splits a text such that each resulting line does not exceed the
// given screen width. Possible split points are after any punctuation or
// whitespace. Whitespace after split points will be dropped.
//
// This function considers color tags to have no width.
//
// Text is always split at newline characters ('\n').
//
// BUG(tslocum) Text containing square brackets is not escaped properly.
// Use TextView.SetWrapWidth where possible.
//
// Issue: https://code.rocketnine.space/tslocum/cview/issues/27
func ( string,  int) ( []string) {
	, , , , , ,  := decomposeText([]byte(), true, false)

	// Find candidate breakpoints.
	 := boundaryPattern.FindAllSubmatchIndex(, -1)
	// Results in one entry for each candidate. Each entry is an array a of
	// indices into strippedText where a[6] < 0 for newline/punctuation matches
	// and a[4] < 0 for whitespace matches.

	// Process stripped text one character at a time.
	var (
		, , ,       int
		, ,  int
		,                                 int
		                                         bool
	)
	 := func( string,  int) string {
		// A helper function to unescape escaped tags.
		for  := ;  >= 0; -- {
			if  < len() &&  > [][0] &&  < [][1]-1 {
				 := [][1] - 2 - 
				if  < 0 ||  > len() { // Workaround for issue #27
					return 
				}
				return [:] + [+1:]
			}
		}
		return 
	}
	iterateString(string(), func( rune,  []rune, , , ,  int) bool {
		// Handle tags.
		for {
			if  < len() && + >= [][0] && + < [][1] {
				// Colour tags.
				 += [][1] - [][0]
				++
			} else if  < len() && + == [][1]-2 {
				// Escape tags.
				++
				++
			} else {
				break
			}
		}

		// Is this a breakpoint?
		if  < len() && + == [][0] {
			// Yes, it is. Set up breakpoint infos depending on its type.
			 = [][0] + 
			 = [][1] + 
			 = 0
			 =  == '\n'
			if [][6] < 0 && ! {
				++ // Don't skip punctuation.
			}
			++
		}

		// Check if a break is warranted.
		if  ||  > 0 && + >  {
			 := 
			 := 
			if  {
				 =  + 
				 =  +  + 1
				 = 0
				 = 0
			} else if  <=  {
				 =  + 
				 =  + 
				 = 0
			}
			 = append(, ([:], ))
			, ,  = , , false
		}

		// Remember the characters since the last breakpoint.
		if  > 0 &&  <= + {
			 += 
		}

		// Advance.
		 += 

		// But if we're still inside a breakpoint, skip next character (whitespace).
		if + <  {
			 -= 
		}

		return false
	})

	// Flush the rest.
	if  < len() {
		 = append(, ([:], ))
	}

	return
}

// EscapeBytes escapes the given text such that color and/or region tags are not
// recognized and substituted by the print functions of this package. For
// example, to include a tag-like string in a box title or in a TextView:
//
//   box.SetTitle(cview.Escape("[squarebrackets]"))
//   fmt.Fprint(textView, cview.EscapeBytes(`["quoted"]`))
func ( []byte) []byte {
	return nonEscapePattern.ReplaceAll(, []byte("$1[]"))
}

// Escape escapes the given text such that color and/or region tags are not
// recognized and substituted by the print functions of this package. For
// example, to include a tag-like string in a box title or in a TextView:
//
//   box.SetTitle(cview.Escape("[squarebrackets]"))
//   fmt.Fprint(textView, cview.Escape(`["quoted"]`))
func ( string) string {
	return nonEscapePattern.ReplaceAllString(, "$1[]")
}

// iterateString iterates through the given string one printed character at a
// time. For each such character, the callback function is called with the
// Unicode code points of the character (the first rune and any combining runes
// which may be nil if there aren't any), the starting position (in bytes)
// within the original string, its length in bytes, the screen position of the
// character, and the screen width of it. The iteration stops if the callback
// returns true. This function returns true if the iteration was stopped before
// the last character.
func iterateString( string,  func( rune,  []rune, , , ,  int) bool) bool {
	var  int

	 := uniseg.NewGraphemes()
	for .Next() {
		 := .Runes()
		,  := .Positions()
		 := runewidth.StringWidth(.Str())
		var  []rune
		if len() > 1 {
			 = [1:]
		}

		if ([0], , , -, , ) {
			return true
		}

		 += 
	}

	return false
}

// iterateStringReverse iterates through the given string in reverse, starting
// from the end of the string, one printed character at a time. For each such
// character, the callback function is called with the Unicode code points of
// the character (the first rune and any combining runes which may be nil if
// there aren't any), the starting position (in bytes) within the original
// string, its length in bytes, the screen position of the character, and the
// screen width of it. The iteration stops if the callback returns true. This
// function returns true if the iteration was stopped before the last character.
func iterateStringReverse( string,  func( rune,  []rune, , , ,  int) bool) bool {
	type  struct {
		                                       rune
		                                       []rune
		, , ,  int
	}

	// Create the grapheme clusters.
	var  []
	iterateString(, func( rune,  []rune,  int,  int,  int,  int) bool {
		 = append(, {
			:        ,
			:        ,
			:     ,
			:   ,
			:   ,
			: ,
		})
		return false
	})

	// Iterate in reverse.
	for  := len() - 1;  >= 0; -- {
		if (
			[].,
			[].,
			[].,
			[].,
			[].,
			[].,
		) {
			return true
		}
	}

	return false
}

// ScrollBarVisibility specifies the display of a scroll bar.
type ScrollBarVisibility int

const (
	// ScrollBarNever never shows a scroll bar.
	ScrollBarNever ScrollBarVisibility = iota

	// ScrollBarAuto shows a scroll bar when there are items offscreen.
	ScrollBarAuto

	// ScrollBarAlways always shows a scroll bar.
	ScrollBarAlways
)

// Scroll bar render text (must be one cell wide)
var (
	ScrollBarArea          = []byte("[-:-:-]░")
	ScrollBarAreaFocused   = []byte("[-:-:-]▒")
	ScrollBarHandle        = []byte("[-:-:-]▓")
	ScrollBarHandleFocused = []byte("[::r] [-:-:-]")
)

// RenderScrollBar renders a scroll bar at the specified position.
func ( tcell.Screen,  ScrollBarVisibility,  int,  int,  int,  int,  int,  int,  bool,  tcell.Color) {
	if  == ScrollBarNever || ( == ScrollBarAuto &&  <= ) {
		return
	}

	// Place cursor at top when there are no items offscreen.
	if  <=  {
		 = 0
	}

	// Handle negative cursor.
	if  < 0 {
		 = 0
	}

	// Calculate handle position.
	 := int(float64(-1) * (float64() / float64(-1)))

	// Print scroll bar.
	var  []byte
	if  ==  {
		if  {
			 = ScrollBarHandleFocused
		} else {
			 = ScrollBarHandle
		}
	} else {
		if  {
			 = ScrollBarAreaFocused
		} else {
			 = ScrollBarArea
		}
	}
	Print(, , , , 1, AlignLeft, )
}