// Copyright 2021 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
// You may obtain a copy of the license at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// The dynamic package is used to generate a terminal description dynamically,
// using infocmp.  This is really a method of last resort, as the performance
// will be slow, and it requires a working infocmp.  But, the hope is that it
// will assist folks who have to deal with a terminal description that isn't
// already built in.  This requires infocmp to be in the user's path, and to
// support reasonably the -1 option.

package dynamic

import (
	
	
	
	
	
	

	
)

type termcap struct {
	name    string
	desc    string
	aliases []string
	bools   map[string]bool
	nums    map[string]int
	strs    map[string]string
}

func ( *termcap) ( string) int {
	return (.nums[])
}

func ( *termcap) ( string) bool {
	return (.bools[])
}

func ( *termcap) ( string) string {
	return (.strs[])
}

const (
	none = iota
	control
	escaped
)

var errNotAddressable = errors.New("terminal not cursor addressable")

func unescape( string) string {
	// Various escapes are in \x format.  Control codes are
	// encoded as ^M (carat followed by ASCII equivalent).
	// escapes are: \e, \E - escape
	//  \0 NULL, \n \l \r \t \b \f \s for equivalent C escape.
	 := &bytes.Buffer{}
	 := none

	for  := 0;  < len(); ++ {
		 := []
		switch  {
		case none:
			switch  {
			case '\\':
				 = escaped
			case '^':
				 = control
			default:
				.WriteByte()
			}
		case control:
			.WriteByte( ^ 1<<6)
			 = none
		case escaped:
			switch  {
			case 'E', 'e':
				.WriteByte(0x1b)
			case '0', '1', '2', '3', '4', '5', '6', '7':
				if +2 < len() && [+1] >= '0' && [+1] <= '7' && [+2] >= '0' && [+2] <= '7' {
					.WriteByte((( - '0') * 64) + (([+1] - '0') * 8) + ([+2] - '0'))
					 =  + 2
				} else if  == '0' {
					.WriteByte(0)
				}
			case 'n':
				.WriteByte('\n')
			case 'r':
				.WriteByte('\r')
			case 't':
				.WriteByte('\t')
			case 'b':
				.WriteByte('\b')
			case 'f':
				.WriteByte('\f')
			case 's':
				.WriteByte(' ')
			default:
				.WriteByte()
			}
			 = none
		}
	}
	return (.String())
}

func ( *termcap) ( string) error {
	 := exec.Command("infocmp", "-1", )
	 := &bytes.Buffer{}
	.Stdout = 

	.strs = make(map[string]string)
	.bools = make(map[string]bool)
	.nums = make(map[string]int)

	if  := .Run();  != nil {
		return 
	}

	// Now parse the output.
	// We get comment lines (starting with "#"), followed by
	// a header line that looks like "<name>|<alias>|...|<desc>"
	// then capabilities, one per line, starting with a tab and ending
	// with a comma and newline.
	 := strings.Split(.String(), "\n")
	for len() > 0 && strings.HasPrefix([0], "#") {
		 = [1:]
	}

	// Ditch trailing empty last line
	if [len()-1] == "" {
		 = [:len()-1]
	}
	 := [0]
	if strings.HasSuffix(, ",") {
		 = [:len()-1]
	}
	 := strings.Split(, "|")
	.name = [0]
	 = [1:]
	if len() > 0 {
		.desc = [len()-1]
		 = [:len()-1]
	}
	.aliases = 
	for ,  := range [1:] {
		if (!strings.HasPrefix(, "\t")) ||
			(!strings.HasSuffix(, ",")) {
			return (errors.New("malformed infocmp: " + ))
		}

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

		if  := strings.SplitN(, "=", 2); len() == 2 {
			.strs[[0]] = unescape([1])
		} else if  := strings.SplitN(, "#", 2); len() == 2 {
			,  := strconv.ParseUint([1], 0, 0)
			if  != nil {
				return ()
			}
			.nums[[0]] = int()
		} else {
			.bools[] = true
		}
	}
	return nil
}

// LoadTerminfo creates a Terminfo by for named terminal by attempting to parse
// the output from infocmp.  This returns the terminfo entry, a description of
// the terminal, and either nil or an error.
func ( string) (*terminfo.Terminfo, string, error) {
	var  termcap
	if  := .setupterm();  != nil {
		return nil, "", 
	}
	 := &terminfo.Terminfo{}
	.Name = .name
	.Aliases = .aliases
	.Colors = .getnum("colors")
	.Columns = .getnum("cols")
	.Lines = .getnum("lines")
	.Bell = .getstr("bel")
	.Clear = .getstr("clear")
	.EnterCA = .getstr("smcup")
	.ExitCA = .getstr("rmcup")
	.ShowCursor = .getstr("cnorm")
	.HideCursor = .getstr("civis")
	.AttrOff = .getstr("sgr0")
	.Underline = .getstr("smul")
	.Bold = .getstr("bold")
	.Blink = .getstr("blink")
	.Dim = .getstr("dim")
	.Italic = .getstr("sitm")
	.Reverse = .getstr("rev")
	.EnterKeypad = .getstr("smkx")
	.ExitKeypad = .getstr("rmkx")
	.SetFg = .getstr("setaf")
	.SetBg = .getstr("setab")
	.SetCursor = .getstr("cup")
	.CursorBack1 = .getstr("cub1")
	.CursorUp1 = .getstr("cuu1")
	.KeyF1 = .getstr("kf1")
	.KeyF2 = .getstr("kf2")
	.KeyF3 = .getstr("kf3")
	.KeyF4 = .getstr("kf4")
	.KeyF5 = .getstr("kf5")
	.KeyF6 = .getstr("kf6")
	.KeyF7 = .getstr("kf7")
	.KeyF8 = .getstr("kf8")
	.KeyF9 = .getstr("kf9")
	.KeyF10 = .getstr("kf10")
	.KeyF11 = .getstr("kf11")
	.KeyF12 = .getstr("kf12")
	.KeyF13 = .getstr("kf13")
	.KeyF14 = .getstr("kf14")
	.KeyF15 = .getstr("kf15")
	.KeyF16 = .getstr("kf16")
	.KeyF17 = .getstr("kf17")
	.KeyF18 = .getstr("kf18")
	.KeyF19 = .getstr("kf19")
	.KeyF20 = .getstr("kf20")
	.KeyF21 = .getstr("kf21")
	.KeyF22 = .getstr("kf22")
	.KeyF23 = .getstr("kf23")
	.KeyF24 = .getstr("kf24")
	.KeyF25 = .getstr("kf25")
	.KeyF26 = .getstr("kf26")
	.KeyF27 = .getstr("kf27")
	.KeyF28 = .getstr("kf28")
	.KeyF29 = .getstr("kf29")
	.KeyF30 = .getstr("kf30")
	.KeyF31 = .getstr("kf31")
	.KeyF32 = .getstr("kf32")
	.KeyF33 = .getstr("kf33")
	.KeyF34 = .getstr("kf34")
	.KeyF35 = .getstr("kf35")
	.KeyF36 = .getstr("kf36")
	.KeyF37 = .getstr("kf37")
	.KeyF38 = .getstr("kf38")
	.KeyF39 = .getstr("kf39")
	.KeyF40 = .getstr("kf40")
	.KeyF41 = .getstr("kf41")
	.KeyF42 = .getstr("kf42")
	.KeyF43 = .getstr("kf43")
	.KeyF44 = .getstr("kf44")
	.KeyF45 = .getstr("kf45")
	.KeyF46 = .getstr("kf46")
	.KeyF47 = .getstr("kf47")
	.KeyF48 = .getstr("kf48")
	.KeyF49 = .getstr("kf49")
	.KeyF50 = .getstr("kf50")
	.KeyF51 = .getstr("kf51")
	.KeyF52 = .getstr("kf52")
	.KeyF53 = .getstr("kf53")
	.KeyF54 = .getstr("kf54")
	.KeyF55 = .getstr("kf55")
	.KeyF56 = .getstr("kf56")
	.KeyF57 = .getstr("kf57")
	.KeyF58 = .getstr("kf58")
	.KeyF59 = .getstr("kf59")
	.KeyF60 = .getstr("kf60")
	.KeyF61 = .getstr("kf61")
	.KeyF62 = .getstr("kf62")
	.KeyF63 = .getstr("kf63")
	.KeyF64 = .getstr("kf64")
	.KeyInsert = .getstr("kich1")
	.KeyDelete = .getstr("kdch1")
	.KeyBackspace = .getstr("kbs")
	.KeyHome = .getstr("khome")
	.KeyEnd = .getstr("kend")
	.KeyUp = .getstr("kcuu1")
	.KeyDown = .getstr("kcud1")
	.KeyRight = .getstr("kcuf1")
	.KeyLeft = .getstr("kcub1")
	.KeyPgDn = .getstr("knp")
	.KeyPgUp = .getstr("kpp")
	.KeyBacktab = .getstr("kcbt")
	.KeyExit = .getstr("kext")
	.KeyCancel = .getstr("kcan")
	.KeyPrint = .getstr("kprt")
	.KeyHelp = .getstr("khlp")
	.KeyClear = .getstr("kclr")
	.AltChars = .getstr("acsc")
	.EnterAcs = .getstr("smacs")
	.ExitAcs = .getstr("rmacs")
	.EnableAcs = .getstr("enacs")
	.Mouse = .getstr("kmous")
	.KeyShfRight = .getstr("kRIT")
	.KeyShfLeft = .getstr("kLFT")
	.KeyShfHome = .getstr("kHOM")
	.KeyShfEnd = .getstr("kEND")

	// Terminfo lacks descriptions for a bunch of modified keys,
	// but modern XTerm and emulators often have them.  Let's add them,
	// if the shifted right and left arrows are defined.
	if .KeyShfRight == "\x1b[1;2C" && .KeyShfLeft == "\x1b[1;2D" {
		.Modifiers = terminfo.ModifiersXTerm

		.KeyShfUp = "\x1b[1;2A"
		.KeyShfDown = "\x1b[1;2B"
		.KeyMetaUp = "\x1b[1;9A"
		.KeyMetaDown = "\x1b[1;9B"
		.KeyMetaRight = "\x1b[1;9C"
		.KeyMetaLeft = "\x1b[1;9D"
		.KeyAltUp = "\x1b[1;3A"
		.KeyAltDown = "\x1b[1;3B"
		.KeyAltRight = "\x1b[1;3C"
		.KeyAltLeft = "\x1b[1;3D"
		.KeyCtrlUp = "\x1b[1;5A"
		.KeyCtrlDown = "\x1b[1;5B"
		.KeyCtrlRight = "\x1b[1;5C"
		.KeyCtrlLeft = "\x1b[1;5D"
		.KeyAltShfUp = "\x1b[1;4A"
		.KeyAltShfDown = "\x1b[1;4B"
		.KeyAltShfRight = "\x1b[1;4C"
		.KeyAltShfLeft = "\x1b[1;4D"

		.KeyMetaShfUp = "\x1b[1;10A"
		.KeyMetaShfDown = "\x1b[1;10B"
		.KeyMetaShfRight = "\x1b[1;10C"
		.KeyMetaShfLeft = "\x1b[1;10D"

		.KeyCtrlShfUp = "\x1b[1;6A"
		.KeyCtrlShfDown = "\x1b[1;6B"
		.KeyCtrlShfRight = "\x1b[1;6C"
		.KeyCtrlShfLeft = "\x1b[1;6D"

		.KeyShfPgUp = "\x1b[5;2~"
		.KeyShfPgDn = "\x1b[6;2~"
	}
	// And also for Home and End
	if .KeyShfHome == "\x1b[1;2H" && .KeyShfEnd == "\x1b[1;2F" {
		.KeyCtrlHome = "\x1b[1;5H"
		.KeyCtrlEnd = "\x1b[1;5F"
		.KeyAltHome = "\x1b[1;9H"
		.KeyAltEnd = "\x1b[1;9F"
		.KeyCtrlShfHome = "\x1b[1;6H"
		.KeyCtrlShfEnd = "\x1b[1;6F"
		.KeyAltShfHome = "\x1b[1;4H"
		.KeyAltShfEnd = "\x1b[1;4F"
		.KeyMetaShfHome = "\x1b[1;10H"
		.KeyMetaShfEnd = "\x1b[1;10F"
	}

	// And the same thing for rxvt and workalikes (Eterm, aterm, etc.)
	// It seems that urxvt at least send escaped as ALT prefix for these,
	// although some places seem to indicate a separate ALT key sesquence.
	if .KeyShfRight == "\x1b[c" && .KeyShfLeft == "\x1b[d" {
		.KeyShfUp = "\x1b[a"
		.KeyShfDown = "\x1b[b"
		.KeyCtrlUp = "\x1b[Oa"
		.KeyCtrlDown = "\x1b[Ob"
		.KeyCtrlRight = "\x1b[Oc"
		.KeyCtrlLeft = "\x1b[Od"
	}
	if .KeyShfHome == "\x1b[7$" && .KeyShfEnd == "\x1b[8$" {
		.KeyCtrlHome = "\x1b[7^"
		.KeyCtrlEnd = "\x1b[8^"
	}

	// Technically the RGB flag that is provided for xterm-direct is not
	// quite right.  The problem is that the -direct flag that was introduced
	// with ncurses 6.1 requires a parsing for the parameters that we lack.
	// For this case we'll just assume it's XTerm compatible.  Someday this
	// may be incorrect, but right now it is correct, and nobody uses it
	// anyway.
	if .getflag("Tc") {
		// This presumes XTerm 24-bit true color.
		.TrueColor = true
	} else if .getflag("RGB") {
		// This is for xterm-direct, which uses a different scheme entirely.
		// (ncurses went a very different direction from everyone else, and
		// so it's unlikely anything is using this definition.)
		.TrueColor = true
		.SetBg = "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m"
		.SetFg = "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m"
	}

	// We only support colors in ANSI 8 or 256 color mode.
	if .Colors < 8 || .SetFg == "" {
		.Colors = 0
	}
	if .SetCursor == "" {
		return nil, "", errNotAddressable
	}

	// For padding, we lookup the pad char.  If that isn't present,
	// and npc is *not* set, then we assume a null byte.
	.PadChar = .getstr("pad")
	if .PadChar == "" {
		if !.getflag("npc") {
			.PadChar = "\u0000"
		}
	}

	// For terminals that use "standard" SGR sequences, lets combine the
	// foreground and background together.
	if strings.HasPrefix(.SetFg, "\x1b[") &&
		strings.HasPrefix(.SetBg, "\x1b[") &&
		strings.HasSuffix(.SetFg, "m") &&
		strings.HasSuffix(.SetBg, "m") {
		 := .SetFg[:len(.SetFg)-1]
		 := regexp.MustCompile("%p1")
		 := .ReplaceAllString(.SetBg[2:], "%p2")
		.SetFgBg =  + ";" + 
	}

	return , .desc, nil
}