package inputrc

import (
	
	
	
	
	
	
	
	
	
	
	
	
)

const (
	emacs           = "emacs"
	hexValNum       = 10
	metaSeqLength   = 6
	setDirectiveLen = 4
)

// Parser is a inputrc parser.
type Parser struct {
	haltOnErr bool
	strict    bool
	name      string
	app       string
	term      string
	mode      string
	keymap    string
	line      int
	conds     []bool
	errs      []error
}

// New creates a new inputrc parser.
func ( ...Option) *Parser {
	// build parser state
	 := &Parser{
		line: 1,
	}
	for ,  := range  {
		()
	}

	return 
}

// Parse parses inputrc data from the reader, passing sets and binding keys to
// h based on the configured options.
func ( *Parser) ( io.Reader,  Handler) error {
	var  error
	// reset parser state
	.keymap, .line, .conds, .errs = emacs, 1, append(.conds[:0], true), .errs[:0]
	// scan file by lines
	var  []rune
	var ,  int
	 := bufio.NewScanner()

	for ; .Scan(); .line++ {
		 = []rune(.Text())
		 = len()

		if  = findNonSpace(, 0, );  ==  {
			continue
		}
		// skip blank/comment
		switch [] {
		case 0, '\r', '\n', '#':
			continue
		}

		// next
		if  = .next(, , , );  != nil {
			.errs = append(.errs, )
			if .haltOnErr {
				return 
			}
		}
	}

	if  = .Err();  != nil {
		.errs = append(.errs, )
		return 
	}

	return nil
}

// Errs returns the parse errors encountered.
func ( *Parser) () []error {
	return .errs
}

// next handles the next statement.
func ( *Parser) ( Handler,  []rune, ,  int) error {
	, , ,  := .readNext(, , )
	if  != nil {
		return 
	}

	switch  {
	case tokenBind, tokenBindMacro:
		return .doBind(, , ,  == tokenBindMacro)
	case tokenSet:
		return .doSet(, , )
	case tokenConstruct:
		return .do(, , )
	}

	return nil
}

// readNext reads the next statement.
func ( *Parser) ( []rune, ,  int) (string, string, token, error) {
	 = findNonSpace(, , )

	switch {
	case [] == 's' && grab(, +1, ) == 'e' && grab(, +2, ) == 't' && unicode.IsSpace(grab(, +3, )):
		// read set
		return .readSymbols(, +setDirectiveLen, , tokenSet, true)
	case [] == '$':
		// read construct
		return .readSymbols(, , , tokenConstruct, false)
	}
	// read key keySeq
	var  string

	if [] == '"' || [] == '\'' {
		var  bool
		 := 

		if ,  = findStringEnd(, , ); ! {
			return "", "", tokenNone, &ParseError{
				Name: .name,
				Line: .line,
				Text: string([:]),
				Err:  ErrBindMissingClosingQuote,
			}
		}

		 = unescapeRunes(, +1, -1)
	} else {
		var  error
		if , ,  = decodeKey(, , );  != nil {
			return "", "", tokenNone, &ParseError{
				Name: .name,
				Line: .line,
				Text: string(),
				Err:  ,
			}
		}
	}
	// NOTE: this is technically different than the actual readline
	// implementation, as it doesn't allow whitespace, but silently fails (ie
	// does not bind a key) if a space follows the key declaration. made a
	// decision to instead return an error if the : is missing in all cases.
	// seek :
	for ;  <  && [] != ':'; ++ {
	}

	if  ==  || [] != ':' {
		return "", "", tokenNone, &ParseError{
			Name: .name,
			Line: .line,
			Text: string(),
			Err:  ErrMissingColon,
		}
	}
	// seek non space
	if  = findNonSpace(, +1, );  ==  || [] == '#' {
		return , "", tokenNone, nil
	}
	// seek
	if [] == '"' || [] == '\'' {
		var  bool
		 := 

		if ,  = findStringEnd(, , ); ! {
			return "", "", tokenNone, &ParseError{
				Name: .name,
				Line: .line,
				Text: string([:]),
				Err:  ErrMacroMissingClosingQuote,
			}
		}

		return , unescapeRunes(, +1, -1), tokenBindMacro, nil
	}

	return , string([:findEnd(, , )]), tokenBind, nil
}

// readSet reads the next two symbols.
func ( *Parser) ( []rune, ,  int,  token,  bool) (string, string, token, error) {
	 := findNonSpace(, , )
	 = findEnd(, , )
	 := string([:])
	 = findNonSpace(, , )
	var  bool

	if  := grab(, , );  ||  == '"' ||  == '\'' {
		var  int
		if ,  = findStringEnd(, , );  {
			 = 
		}
	}

	if ! || ! {
		 = findEnd(, , )
	}

	return , string([:]), , nil
}

// doBind handles a bind.
func ( *Parser) ( Handler, ,  string,  bool) error {
	if !.conds[len(.conds)-1] {
		return nil
	}

	return .Bind(.keymap, , , )
}

// doSet handles a set.
func ( *Parser) ( Handler, ,  string) error {
	if !.conds[len(.conds)-1] {
		return nil
	}

	switch  {
	case "keymap":
		if .strict {
			switch  {
			// see: man readline
			// see: https://unix.stackexchange.com/questions/303479/what-are-readlines-modes-keymaps-and-their-default-bindings
			case "emacs", "emacs-standard", "emacs-meta", "emacs-ctlx",
				"vi", "vi-move", "vi-command", "vi-insert":
			default:
				return &ParseError{
					Name: .name,
					Line: .line,
					Text: ,
					Err:  ErrInvalidKeymap,
				}
			}
		}

		.keymap = 

		return nil

	case "editing-mode":
		switch  {
		case "emacs", "vi":
		default:
			return &ParseError{
				Name: .name,
				Line: .line,
				Text: ,
				Err:  ErrInvalidEditingMode,
			}
		}

		return .Set(, )
	}

	if  := .Get();  != nil {
		// defined in vars, so pass to set only as that type
		var  interface{}
		switch .(type) {
		case bool:
			 = strings.ToLower() == "on" ||  == "1"
		case string:
			 = 
		case int:
			,  := strconv.Atoi()
			if  != nil {
				return 
			}

			 = 

		default:
			panic(fmt.Sprintf("unsupported type %T", ))
		}

		return .Set(, )
	}
	// not set, so try to convert to usable value
	if ,  := strconv.Atoi();  == nil {
		return .Set(, )
	}

	switch strings.ToLower() {
	case "off":
		return .Set(, false)
	case "on":
		return .Set(, true)
	}

	return .Set(, )
}

// do handles a construct.
func ( *Parser) ( Handler, ,  string) error {
	switch  {
	case "$if":
		var  bool

		switch {
		case strings.HasPrefix(, "mode="):
			 = strings.TrimPrefix(, "mode=") == .mode
		case strings.HasPrefix(, "term="):
			 = strings.TrimPrefix(, "term=") == .term
		default:
			 = strings.ToLower() == .app
		}

		.conds = append(.conds, )

		return nil

	case "$else":
		if len(.conds) == 1 {
			return &ParseError{
				Name: .name,
				Line: .line,
				Text: "$else",
				Err:  ErrElseWithoutMatchingIf,
			}
		}

		.conds[len(.conds)-1] = !.conds[len(.conds)-1]

		return nil

	case "$endif":
		if len(.conds) == 1 {
			return &ParseError{
				Name: .name,
				Line: .line,
				Text: "$endif",
				Err:  ErrEndifWithoutMatchingIf,
			}
		}

		.conds = .conds[:len(.conds)-1]

		return nil

	case "$include":
		if !.conds[len(.conds)-1] {
			return nil
		}

		 := expandIncludePath()
		,  := .ReadFile()

		switch {
		case  != nil && errors.Is(, os.ErrNotExist):
			return nil
		case  != nil:
			return 
		}

		return Parse(bytes.NewReader(), , WithName(), WithApp(.app), WithTerm(.term), WithMode(.mode))
	}

	if !.conds[len(.conds)-1] {
		return nil
	}
	// delegate unknown construct
	if  := .Do(, );  != nil {
		return &ParseError{
			Name: .name,
			Line: .line,
			Text:  + " " + ,
			Err:  ,
		}
	}

	return nil
}

// Option is a parser option.
type Option func(*Parser)

// WithHaltOnErr is a parser option to set halt on every encountered error.
func ( bool) Option {
	return func( *Parser) {
		.haltOnErr = 
	}
}

// WithStrict is a parser option to set strict keymap parsing.
func ( bool) Option {
	return func( *Parser) {
		.strict = 
	}
}

// WithName is a parser option to set the file name.
func ( string) Option {
	return func( *Parser) {
		.name = 
	}
}

// WithApp is a parser option to set the app name.
func ( string) Option {
	return func( *Parser) {
		.app = 
	}
}

// WithTerm is a parser option to set the term name.
func ( string) Option {
	return func( *Parser) {
		.term = 
	}
}

// WithMode is a parser option to set the mode name.
func ( string) Option {
	return func( *Parser) {
		.mode = 
	}
}

// ParseError is a parse error.
type ParseError struct {
	Name string
	Line int
	Text string
	Err  error
}

// Error satisfies the error interface.
func ( *ParseError) () string {
	var  string
	if .Name != "" {
		 = " " + .Name + ":"
	}

	return fmt.Sprintf("inputrc:%s line %d: %s: %v", , .Line, .Text, .Err)
}

// Unwrap satisfies the errors.Unwrap call.
func ( *ParseError) () error {
	return .Err
}

// token is a inputrc line token.
type token int

// inputrc line tokens.
const (
	tokenNone token = iota
	tokenBind
	tokenBindMacro
	tokenSet
	tokenConstruct
)

// String satisfies the fmt.Stringer interface.
func ( token) () string {
	switch  {
	case tokenNone:
		return "none"
	case tokenBind:
		return "bind"
	case tokenBindMacro:
		return "bind-macro"
	case tokenSet:
		return "set"
	case tokenConstruct:
		return "construct"
	}

	return fmt.Sprintf("token(%d)", )
}

// findNonSpace finds first non space rune in r, returning end if not found.
func findNonSpace( []rune, ,  int) int {
	for ;  <  && unicode.IsSpace([]); ++ {
	}
	return 
}

// findEnd finds end of the current symbol (position of next #, space, or line
// end), returning end if not found.
func findEnd( []rune, ,  int) int {
	for  := grab(, +1, );  <  &&  != '#' && !unicode.IsSpace() && !unicode.IsControl(); ++ {
		 = grab(, +1, )
	}

	return 
}

// findStringEnd finds end of the string, returning end if not found.
func findStringEnd( []rune, ,  int) (int, bool) {
	var  rune
	 := []

	for ++;  < ; ++ {
		switch  = []; {
		case  == '\\':
			++
			continue
		case  == :
			return  + 1, true
		}
	}

	return , false
}

// grab returns r[i] when i < end, 0 otherwise.
func grab( []rune, ,  int) rune {
	if  <  {
		return []
	}

	return 0
}

// decodeKey decodes named key sequence.
func decodeKey( []rune, ,  int) (string, int, error) {
	// seek end of sequence
	 := 

	for  := grab(, +1, );  <  &&  != ':' &&  != '#' && !unicode.IsSpace() && !unicode.IsControl(); ++ {
		 = grab(, +1, )
	}

	 := strings.ToLower(string([:]))
	,  := false, false

	for  := strings.Index(, "-");  != -1;  = strings.Index(, "-") {
		switch [:] {
		case "control", "ctrl", "c":
			 = true
		case "meta", "m":
			 = true
		default:
			return "", , ErrUnknownModifier
		}

		 = [+1:]
	}

	var  rune

	switch  {
	case "":
		return "", , nil

	case "delete", "del", "rubout":
		 = Delete
	case "escape", "esc":
		 = Esc
	case "newline", "linefeed", "lfd":
		 = Newline
	case "return", "ret":
		 = Return
	case "tab":
		 = Tab
	case "space", "spc":
		 = Space
	case "formfeed", "ffd":
		 = Formfeed
	case "vertical", "vrt":
		 = Vertical
	default:
		, _ = utf8.DecodeRuneInString()
	}

	switch {
	case  && :
		return string([]rune{Esc, Encontrol()}), , nil
	case :
		 = Encontrol()
	case :
		 = Enmeta()
	}

	return string(), , nil
}

/*
// decodeRunes decodes runes.
func decodeRunes(r []rune, i, end int) string {
	r = []rune(unescapeRunes(r, i, end))
	var s []rune
	var c0, c1, c3 rune
	for i, end = 0, len(r); i < end; i++ {
		c0, c1, c3 = grab(r, i, end), grab(r, i+1, end), grab(r, i+2, end)
		switch {
		case c0 == Meta && c1 == Control, c0 == Control && c1 == Meta:
			s = append(s, Esc, Encontrol(c3))
			i += 2
		case c0 == Control:
			s = append(s, Encontrol(c1))
			i++
		case c0 == Meta:
			s = append(s, Enmeta(c1))
			i++
		default:
			s = append(s, c0)
		}
	}
	return string(s)
}
*/

// unescapeRunes decodes escaped string sequence.
func unescapeRunes( []rune, ,  int) string {
	var  []rune
	var , , , , ,  rune

	if len() == 1 {
		return string()
	}

	for ;  < ; ++ {
		if  = [];  == '\\' {
			, , , ,  = grab(, +1, ), grab(, +2, ), grab(, +3, ), grab(, +4, ), grab(, +5, )

			switch {
			case  == 'a': // \a alert (bell)
				 = append(, Alert)
				++
			case  == 'b': // \b backspace
				 = append(, Backspace)
				++
			case  == 'd': // \d delete
				 = append(, Delete)
				++
			case  == 'e': // \e escape
				 = append(, Esc)
				++
			case  == 'f': // \f form feed
				 = append(, Formfeed)
				++
			case  == 'n': // \n new line
				 = append(, Newline)
				++
			case  == 'r': // \r carriage return
				 = append(, Return)
				++
			case  == 't': // \t tab
				 = append(, Tab)
				++
			case  == 'v': // \v vertical
				 = append(, Vertical)
				++
			case  == '\\',  == '"',  == '\'': // \\ \" \' literal
				 = append(, )
				++
			case  == 'x' && hexDigit() && hexDigit(): // \xHH hex
				 = append(, hexVal()<<4|hexVal())
				 += 2
			case  == 'x' && hexDigit(): // \xH hex
				 = append(, hexVal())
				++
			case octDigit() && octDigit() && octDigit(): // \nnn octal
				 = append(, (-'0')<<6|(-'0')<<3|(-'0'))
				 += 3
			case octDigit() && octDigit(): // \nn octal
				 = append(, (-'0')<<3|(-'0'))
				 += 2
			case octDigit(): // \n octal
				 = append(, -'0')
				++
			case (( == 'C' &&  == 'M') || ( == 'M' &&  == 'C')) &&  == '-' &&  == '\\' &&  == '-':
				// \C-\M- or \M-\C- control meta prefix
				if  := grab(, +metaSeqLength, );  != 0 {
					 = append(, Esc, Encontrol())
				}

				 += 6
			case  == 'C' &&  == '-': // \C- control prefix
				if  == '?' {
					 = append(, Delete)
				} else {
					 = append(, Encontrol())
				}

				 += 3
			case  == 'M' &&  == '-': // \M- meta prefix
				if  == 0 {
					 = append(, Esc)
					 += 2
				} else {
					 = append(, Enmeta())
					 += 3
				}
			default:
				 = append(, )
				++
			}

			continue
		}

		 = append(, )
	}

	return string()
}

// octDigit returns true when r is 0-7.
func octDigit( rune) bool {
	return '0' <=  &&  <= '7'
}

// hexDigit returns true when r is 0-9A-Fa-f.
func hexDigit( rune) bool {
	return '0' <=  &&  <= '9' || 'A' <=  &&  <= 'F' || 'a' <=  &&  <= 'f'
}

// hexVal converts a rune to its hex value.
func hexVal( rune) rune {
	switch {
	case 'a' <=  &&  <= 'f':
		return  - 'a' + hexValNum
	case 'A' <=  &&  <= 'F':
		return  - 'A' + hexValNum
	}

	return  - '0'
}

// expandIncludePath handles tilde home directory expansion in $include path directives.
func expandIncludePath( string) string {
	if !strings.HasPrefix(, "~/") {
		return 
	}

	,  := user.Current()
	if  != nil ||  == nil || .HomeDir == "" {
		return 
	}

	return filepath.Join(.HomeDir, [2:])
}