package history

import (
	
	
	
	

	
	
	
	
	
)

// Sources manages and serves all history sources for the current shell.
type Sources struct {
	// Shell parameters
	line   *core.Line
	cursor *core.Cursor
	hint   *ui.Hint
	config *inputrc.Config

	// History sources
	list       map[string]Source // Sources of history lines
	names      []string          // Names of histories stored in rl.histories
	maxEntries int               // Inputrc configured maximum number of entries.
	sourcePos  int               // The index of the currently used history
	hpos       int               // Index used for navigating the history lines with arrows/j/k
	cpos       int               // A temporary cursor position used when searching/moving around.

	// Line changes history
	skip    bool                            // Skip saving the current line state.
	undoing bool                            // The last command executed was an undo.
	last    inputrc.Bind                    // The last command being ran.
	lines   map[string]map[int]*lineHistory // Each line in each history source has its own buffer history.

	// Lines accepted
	infer      bool      // If the last command ran needs to infer the history line.
	accepted   bool      // The line has been accepted and must be returned.
	acceptHold bool      // Should we reuse the same accepted line on the next loop.
	acceptLine core.Line // The line to return to the caller.
	acceptErr  error     // An error to return to the caller.
}

// NewSources is a required constructor for the history sources manager type.
func ( *core.Line,  *core.Cursor,  *ui.Hint,  *inputrc.Config) *Sources {
	 := &Sources{
		// History sources
		list: make(map[string]Source),
		// Line history
		lines: make(map[string]map[int]*lineHistory),
		// Shell parameters
		line:   ,
		cursor: ,
		cpos:   -1,
		hpos:   -1,
		hint:   ,
		config: ,
	}

	.names = append(.names, defaultSourceName)
	.list[defaultSourceName] = new(memory)

	// Inputrc settings.
	.maxEntries = .GetInt("history-size")
	 := .GetString("history-size") != ""

	if .maxEntries == 0 && ! {
		.maxEntries = -1
	} else if .maxEntries == 0 &&  {
		.maxEntries = 500
	}

	return 
}

// Init initializes the history sources positions and buffers
// at the start of each readline loop. If the last command asked
// to infer a command line from the history, it is performed now.
func ( *Sources) {
	defer func() {
		.accepted = false
		.acceptLine = nil
		.acceptErr = nil
		.cpos = -1
	}()

	if .acceptHold {
		.hpos = -1
		.line.Set(.acceptLine...)
		.cursor.Set(.line.Len())

		return
	}

	if !.infer {
		.hpos = -1
		 := .getHistoryLineChanges()
		[.hpos] = &lineHistory{}

		return
	}

	switch .hpos {
	case -1:
	case 0:
		.InferNext()
	default:
		.Walk(-1)
	}

	.infer = false
}

// Add adds a source of history lines bound to a given name (printed above this source when used).
// If the shell currently has only an in-memory (default) history source available, the call will
// drop this source and replace it with the provided one. Following calls add to the list.
func ( *Sources) ( string,  Source) {
	if len(.list) == 1 && .names[0] == defaultSourceName {
		delete(.list, defaultSourceName)
		.names = make([]string, 0)
	}

	.names = append(.names, )
	.list[] = 
}

// AddFromFile adds a command history source from a file path.
// The name is used when using/searching the history source.
func ( *Sources) (,  string) {
	 := new(fileHistory)
	.file = 
	.lines, _ = openHist()

	.Add(, )
}

// Delete deletes one or more history source by name.
// If no arguments are passed, all currently bound sources are removed.
func ( *Sources) ( ...string) {
	if len() == 0 {
		.list = make(map[string]Source)
		.names = make([]string, 0)

		return
	}

	for ,  := range  {
		delete(.list, )

		for ,  := range .names {
			if  ==  {
				.names = append(.names[:], .names[+1:]...)
				break
			}
		}
	}

	.sourcePos = 0
	if !.infer {
		.hpos = -1
	}
}

// Walk goes to the next or previous history line in the active source.
// If at the beginning of the history, the first history line is kept.
// If at the end of it, the main input buffer and cursor position is restored.
func ( *Sources) ( int) {
	 := .Current()

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

	// Can't go back further than the first line.
	if .hpos == .Len() &&  == 1 {
		return
	}

	// Save the current line buffer if we are leaving it.
	if .hpos == -1 &&  > 0 {
		.skip = false
		.Save()
		.cpos = -1
		.hpos = 0
	}

	.hpos += 

	switch {
	case .hpos < -1:
		.hpos = -1
		return
	case .hpos == 0:
		.restoreLineBuffer()
		return
	case .hpos > .Len():
		.hpos = .Len()
	}

	var  string
	var  error

	// When there is an available change history for
	// this line, use it instead of the fetched line.
	if  := .getLineHistory();  != nil && len(.items) > 0 {
		 = .items[len(.items)-1].line
	} else if ,  = .GetLine(.Len() - .hpos);  != nil {
		.hint.Set(color.FgRed + "history error: " + .Error())
		return
	}

	// Update line buffer and cursor position.
	.setLineCursorMatch()
}

// Fetch fetches the history event at the provided
// index position and makes it the current buffer.
func ( *Sources) ( int) {
	 := .Current()

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

	if  < 0 ||  >= .Len() {
		return
	}

	,  := .GetLine()
	if  != nil {
		.hint.Set(color.FgRed + "history error: " + .Error())
		return
	}

	.setLineCursorMatch()
}

// GetLast returns the last saved history line in the active history source.
func ( *Sources) () string {
	 := .Current()

	if  == nil || .Len() == 0 {
		return ""
	}

	,  := .GetLine(.Len() - 1)
	if  != nil {
		return ""
	}

	return 
}

// Cycle checks for the next history source (if any) and makes it the active one.
// The active one is used in completions, and all history-related commands.
// If next is false, the engine cycles to the previous source.
func ( *Sources) ( bool) {
	switch  {
	case true:
		.sourcePos++

		if .sourcePos == len(.names) {
			.sourcePos = 0
		}
	case false:
		.sourcePos--

		if .sourcePos < 0 {
			.sourcePos = len(.names) - 1
		}
	}
}

// OnLastSource returns true if the currently active
// history source is the last one in the list.
func ( *Sources) () bool {
	return .sourcePos == len(.names)-1
}

// Current returns the current/active history source.
func ( *Sources) () Source {
	if len(.list) == 0 {
		return nil
	}

	return .list[.names[.sourcePos]]
}

// Write writes the accepted input line to all available sources.
// If infer is true, the next history initialization will automatically insert the next
// history line event after the first match of the line, which one is then NOT written.
func ( *Sources) ( bool) {
	if  {
		.infer = true
		return
	}

	 := string(*.line)

	if len(strings.TrimSpace()) == 0 {
		return
	}

	for ,  := range .list {
		if  == nil {
			continue
		}

		// Don't write it if the history source has reached
		// the maximum number of lines allowed (inputrc)
		if .maxEntries == 0 || .maxEntries >= .Len() {
			continue
		}

		var  error

		// Don't write the line if it's identical to the last one.
		,  := .GetLine(.Len() - 1)
		if  == nil &&  != "" && strings.TrimSpace() == strings.TrimSpace() {
			return
		}

		// Save the line and notify through hints if an error raised.
		_,  = .Write()
		if  != nil {
			.hint.Set(color.FgRed + .Error())
		}
	}
}

// Accept is used to signal the line has been accepted by the user and must be
// returned to the readline caller. If hold is true, the line is preserved
// and redisplayed on the next loop. If infer, the line is not written to
// the history, but preserved as a line to match against on the next loop.
// If infer is false, the line is automatically written to active sources.
func ( *Sources) (,  bool,  error) {
	.accepted = true
	.acceptHold = 
	.acceptLine = *.line
	.acceptErr = 

	// Write the line to the history sources only when the line is not
	// returned along with an error (generally, a CtrlC/CtrlD keypress).
	if  == nil {
		.Write()
	}
}

// LineAccepted returns true if the user has accepted the line, signaling
// that the shell must return from its loop. The error can be nil, but may
// indicate a CtrlC/CtrlD style error.
func ( *Sources) () (bool, string, error) {
	if !.accepted {
		return false, "", nil
	}

	 := string(.acceptLine)

	// Revert all state changes to all lines.
	if .config.GetBool("revert-all-at-newline") {
		for  := range .lines {
			.lines[] = make(map[int]*lineHistory)
		}
	}

	return true, , .acceptErr
}

// InsertMatch replaces the buffer with the first history line matching the
// provided buffer, either as a substring (if regexp is true), or as a prefix.
// If the line argument is nil, the current line buffer is used to match against.
func ( *Sources) ( *core.Line,  *core.Cursor, , ,  bool) {
	if len(.list) == 0 {
		return
	}

	if .Current() == nil {
		return
	}

	// When the provided line is empty, we must use
	// the last known state of the main input line.
	,  = .getLine(, )
	 := .Pos() != 0

	// Don't go back to the beginning of
	// history if we are at the end of it.
	if  && .hpos <= -1 {
		.hpos = -1
		return
	}

	, ,  := .match(, , , , )

	// If no match was found, return anyway, but if we were going forward
	// (down to the current input line), reinstore the main line buffer.
	if ! {
		if  {
			.hpos = -1
			.Undo()
		}

		return
	}

	// Update the line/cursor, and save the history position
	.hpos = .Current().Len() - 
	.line.Set([]rune()...)

	if  {
		.cursor.Set(.Pos())
	} else {
		.cursor.Set(.line.Len())
	}
}

// InferNext finds a line matching the current line in the history,
// then finds the line event following it and, if any, inserts it.
func ( *Sources) () {
	if len(.list) == 0 {
		return
	}

	 := .Current()
	if  == nil {
		return
	}

	, ,  := .match(.line, nil, false, false, false)
	if ! {
		return
	}

	// If we have no match we return, or check for the next line.
	if .Len() <= (.Len()-)+1 {
		return
	}

	// Insert the next line
	,  := .GetLine( + 1)
	if  != nil {
		return
	}

	.line.Set([]rune()...)
	.cursor.Set(.line.Len())
}

// Suggest returns the first line matching the current line buffer,
// so that caller can use for things like history autosuggestion.
// If no line matches the current line, it will return the latter.
func ( *Sources) ( *core.Line) core.Line {
	if len(.list) == 0 || len(*) == 0 {
		return *
	}

	if .Current() == nil {
		return *
	}

	, ,  := .match(, nil, false, false, false)
	if ! {
		return *
	}

	return core.Line([]rune())
}

// Complete returns completions with the current history source values.
// If forward is true, the completions are proposed from the most ancient
// line in the history source to the most recent. If filter is true,
// only lines that match the current input line as a prefix are given.
func ( *Sources, ,  bool,  int,  *regexp.Regexp) completion.Values {
	if len(.list) == 0 {
		return completion.Values{}
	}

	 := .Current()
	if  == nil {
		return completion.Values{}
	}

	.hint.Set(color.Bold + color.FgCyanBright + .names[.sourcePos] + color.Reset)

	 := make([]completion.Candidate, 0)
	 := make([]string, 0)

	// Set up iteration clauses
	var (
		 int
		    func( int) bool
		    func( int) int
	)

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

	// And generate the completions.
	for () {
		 = ()

		,  := .GetLine()
		if  != nil {
			continue
		}

		if strings.TrimSpace() == "" {
			continue
		}

		if  && !strings.HasPrefix(, string(*.line)) {
			continue
		} else if  != nil && !.MatchString() {
			continue
		}

		// If this history line is a duplicate of an existing one,
		// remove the existing one and keep this one as it's more recent.
		if ,  := contains(, );  {
			 = append([:], [+1:]...)
			 = append(, )

			continue
		}

		// Add to the list of printed lines if we have a new one.
		 = append(, )

		 := strings.ReplaceAll(, "\n", ` `)

		// Proper pad for indexes
		 := strconv.Itoa()
		 := strings.Repeat(" ", len(strconv.Itoa(.Len()))-len())
		 = fmt.Sprintf("%s%s %s%s", color.Dim, +, color.DimReset, )

		 := completion.Candidate{
			Display: ,
			Value:   ,
		}

		 = append(, )

		--
	}

	 := completion.AddRaw()
	.NoSort["*"] = true
	.ListLong["*"] = true
	.PREFIX = string(*.line)

	return 
}

// Name returns the name of the currently active history source.
func ( *Sources) () string {
	return .names[.sourcePos]
}

func ( *Sources) ( *core.Line,  *core.Cursor, , ,  bool) ( string,  int,  bool) {
	if len(.list) == 0 {
		return , , 
	}

	 := .Current()
	if  == nil {
		return , , 
	}

	// Set up iteration clauses
	var  int
	var  func( int) bool
	var  func( int) int

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

	if  && .hpos > -1 {
		 = .Len() - .hpos
	}

	for () {
		// Fetch the next/prev line and adapt its length.
		 = ()

		,  := .GetLine()
		if  != nil {
			return , , 
		}

		 := string(*)
		if  != nil && .Pos() < .Len() {
			 = [:.Pos()]
		}

		// Matching: either as substring (regex) or since beginning.
		switch {
		case :
			,  := regexp.Compile(regexp.QuoteMeta())
			if  != nil {
				continue
			}

			// Go to next line if not matching as a substring.
			if !.MatchString() {
				continue
			}

		default:
			// If too short or if not fully matching
			if len() < len() || (len() > 0 && !strings.HasPrefix(, )) {
				continue
			}
		}

		// Else we have our history match.
		return , , true
	}

	// We should have returned a match from the loop.
	return "", 0, false
}

// use the "main buffer" and its cursor if no line/cursor has been provided to match against.
func ( *Sources) ( *core.Line,  *core.Cursor) (*core.Line, *core.Cursor) {
	if .hpos == -1 {
		 := .skip
		.skip = false
		.Save()
		.skip = 
	}

	if  == nil {
		 = new(core.Line)
		 = core.NewCursor()

		 := .getHistoryLineChanges()
		if  == nil {
			return , 
		}

		 := [0]
		if  == nil || len(.items) == 0 {
			return , 
		}

		 := .items[len(.items)-1]
		.Set([]rune(.line)...)
		.Set(.pos)
	}

	if  == nil {
		 = core.NewCursor()
	}

	return , 
}

// when walking down/up the lines (not search-matching them),
// this updates the buffer and the cursor position.
func ( *Sources) ( string) {
	// Save the current cursor position when not saved before.
	if .cpos == -1 && .line.Len() > 0 && .cursor.Pos() <= .line.Len() {
		.cpos = .cursor.Pos()
	}

	.line.Set([]rune()...)

	// Set cursor depending on inputrc options and line length.
	if .config.GetBool("history-preserve-point") && .line.Len() > .cpos && .cpos != -1 {
		.cursor.Set(.cpos)
	} else {
		.cursor.Set(.line.Len())
	}
}

func contains( []string,  string) (bool, int) {
	for ,  := range  {
		if  ==  {
			return true, 
		}
	}

	return false, 0
}

func removeDuplicates( []string) []string {
	 := []string{}

	for ,  := range  {
		if ,  := contains(, );  {
			 = append(, )
		}
	}

	return 
}