package kommandant

import (
	"bytes"
	"context"
	"fmt"
	"github.com/chzyer/readline"
	"io"
	"os"
	"reflect"
	"runtime/trace"
	"sort"
	"strings"
)

// Debug levels
const (
	NODEBUG = iota
	ERROR
	INFO
	DEBUG
	DEEPDEBUG
)

type KmdtClient interface {
	SetCore(k *Kmdt)
}

var typeOfBool = reflect.TypeOf(false)
var typeOfString = reflect.TypeOf("")
var typeOfStringList = reflect.TypeOf([]string{})
var typeOfContext = reflect.TypeOf((*context.Context)(nil)).Elem()

type Kmdt struct {
	Debug        uint
	Sub          KmdtClient // The struct which will have all the handlers
	CommandQueue []string
	Intro        string
	LastCmd      string
	Ruler        string
	DocLeader    string
	DocHeader    string
	UnDocHeader  string
	MiscHeader   string
	NoHelp       string
	// Private fields
	isFile       bool
	stdin        io.ReadCloser
	stdinShim    *readShimmer
	stdout       io.Writer
	prompt       string // Handled through SetPrompt
	rlconfig     readline.Config
	// Characters used in command names
	IdentChars string
	// Special hooks
	// =============
	// Hook run in place of OneCmd if present
	OneCmdHook func(ctx context.Context, lineIn string) (stopOut bool)
	// Hook executed once when the cmdloop() method is called.
	PreLoopHook func(ctx context.Context)
	// Hook executed once when the cmdloop() method is about to return.
	PostLoopHook func(ctx context.Context)
	// Hook executed just before the command line is interpreted, but after
	// the input prompt is generated and issued.
	PreCmdHook func(ctx context.Context, lineIn string) (lineOut string)
	// Hook executed just after a command dispatch is finished.
	PostCmdHook func(ctx context.Context, stopIn bool, lineIn string) (stopOut bool)
	// Called when an empty line is entered in response to the prompt.
	// If this is not set, the last nonempty command entered will be repeated.
	EmptyLineHook func(ctx context.Context) (stopOut bool)
	// Called on an input line when the command prefix is not recognized.
	// If this is not set, an error message is printed and it and returns.
	DefaultHook func(ctx context.Context, lineIn string) (stopOut bool)
	// Function called to complete an input line when no command-specific
	// complete function is available. By default, it returns an empty list.
	CompleteDefaultHook func(line string) (options []string)
	// Bound methods
	handlers map[string]func(ctx context.Context, lineIn string) (stopOut bool)
	// Bound help methods
	helpers map[string]func(ctx context.Context)
	// Bound completers
	completers    map[string]func(string) []string
	rootCompleter *readline.PrefixCompleter
	// Readline data
	linecore *readline.Instance
}

// Instantiate an interpreter framework.

func NewKommandant(handlers KmdtClient) *Kmdt {
	return NewKommandantDebug(handlers, NODEBUG)
}

func NewKommandantDebug(handlers KmdtClient, debug uint) *Kmdt {
	kmdt := &Kmdt{}
	kmdt.Debug = debug
	// Weld the objects
	handlers.SetCore(kmdt)
	kmdt.Sub = handlers
	// Validate and load provided handlers
	kmdt.bind(handlers)
	// Setup basic IO
	kmdt.stdinShim = &readShimmer{}
	kmdt.SetStdin(os.Stdin)
	kmdt.SetStdout(os.Stdout)
	kmdt.init_Kommandant()
	kmdt.SetPrompt("(Kmdt) ")
	return kmdt
}

func (k *Kmdt) log(level uint, a ...interface{}) {
	if k.Debug >= level {
		fmt.Println(a...)
	}
}

func (k *Kmdt) EnableReadline(enable bool) (err error) {
	k.log(INFO, "EnableReadline", enable)
	if enable {
		cfg := k.buildReadlineConfig()
		k.linecore, err = readline.NewEx(&cfg)
		return err
	}
	if k.linecore != nil {
		k.linecore.Close()
	}
	k.linecore = nil
	return nil
}

// Combines any custom settings in rlconfig with required settings
func (k *Kmdt) buildReadlineConfig() (newConfig readline.Config) {
	newConfig = k.rlconfig
	newConfig.Prompt = k.prompt
	k.buildCompletion()
	newConfig.AutoComplete = k.rootCompleter
	newConfig.Stdin = k.stdinShim
	newConfig.Stdout = k.stdout
	newConfig.InterruptPrompt = "^C"
	return newConfig
}

func (k *Kmdt) ReadlineEnabled() (enabled bool) {
	return k.linecore != nil
}

func (k *Kmdt) SetStdin(stdin io.ReadCloser) {
	k.log(DEBUG, "KMDT SetStdin")
	k.stdin = stdin
	k.stdinShim.Input = k.stdin
	// Check to see if we are talking to stdin as terminal or pipe
	switch k.stdin.(type) {
	case *os.File:
		info, err := k.stdin.(*os.File).Stat()
		if err != nil {
			// Don't know how this can happen.... hope that it isn't a file
			k.log(ERROR, "KMDT SetStdin stat:", err)
			k.isFile = false
		}
		mode := info.Mode()
		k.isFile = uint32(mode & (os.ModeDevice | os.ModeCharDevice)) == 0
	default:
		k.isFile = false
	}
	k.log(DEBUG, "KMDT isFile:", k.isFile)
}

func (k *Kmdt) GetStdin() (stdin io.ReadCloser) {
	return k.stdin
}

func (k *Kmdt) SetStdout(stdout io.Writer) {
	k.log(DEBUG, "KMDT SetStdout")
	k.stdout = stdout
}

func (k *Kmdt) GetStdout() (stdout io.Writer) {
	return k.stdout
}

func (k *Kmdt) SetPrompt(prompt string) {
	k.log(INFO, "KMDT SetPrompt", prompt)
	k.prompt = prompt
	if k.linecore != nil {
		k.linecore.SetPrompt(prompt)
	}
}

func (k *Kmdt) GetPrompt() (prompt string) {
	return k.prompt
}

// ==== Hook wrapper methods ====

func (k *Kmdt) PreLoop(ctx context.Context) {
	k.log(DEBUG, "KMDT PreLoop, hook present:", k.PreLoopHook != nil)
	if k.PreLoopHook != nil {
		k.PreLoopHook(ctx)
	}
}

func (k *Kmdt) PostLoop(ctx context.Context) {
	k.log(DEBUG, "KMDT PostLoop, hook present:", k.PostLoopHook != nil)
	if k.PostLoopHook != nil {
		k.PostLoopHook(ctx)
	}
}

func (k *Kmdt) PreCmd(ctx context.Context, lineIn string) (lineOut string) {
	k.log(DEBUG, "KMDT PreCmd \"" + lineIn + "\", hook present:",
		k.PreCmdHook != nil)
	if k.PreCmdHook != nil {
		return k.PreCmdHook(ctx, lineIn)
	}
	return lineIn
}

func (k *Kmdt) PostCmd(ctx context.Context, stopIn bool, lineIn string) (stopOut bool) {
	k.log(DEBUG, "KMDT PostCmd", stopIn, "\"" + lineIn + "\", hook present:",
		k.PostCmdHook != nil)
	if k.PostCmdHook != nil {
		return k.PostCmdHook(ctx, stopIn, lineIn)
	}
	return stopIn
}

func (k *Kmdt) EmptyLine(ctx context.Context, ) (stopOut bool) {
	k.log(DEBUG, "KMDT EmptyLine, hook present:", k.EmptyLineHook != nil)
	if k.EmptyLineHook != nil {
		return k.EmptyLineHook(ctx)
	}
	return false
}

func (k *Kmdt) Default(ctx context.Context, lineIn string) (stopOut bool) {
	k.log(DEBUG, "KMDT Default \"" + lineIn + "\", hook present:",
		k.DefaultHook != nil)
	if k.DefaultHook != nil {
		return k.DefaultHook(ctx, lineIn)
	} else {
		return k.defaultCmd(ctx, lineIn)
	}
}

// CompleteDefault is handled a little differently. See buildCompletion()

func (k *Kmdt) OneCmd(ctx context.Context, lineIn string) (stopOut bool) {
	k.log(DEBUG, "KMDT OneCmd \"" + lineIn + "\", hook present:",
		k.OneCmdHook != nil)
	if k.OneCmdHook != nil {
		return k.OneCmdHook(ctx, lineIn)
	} else {
		return k.OneCmd_core(ctx, lineIn)
	}
}

func (k *Kmdt) init_Kommandant() {
	k.CommandQueue = make([]string, 0)
	k.IdentChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQUSTUVWXYZ0123456789_"
	k.Ruler = "="
	k.LastCmd = ""
	k.Intro = ""
	k.DocLeader = ""
	k.DocHeader = "Documented commands (type help <topic>):"
	k.UnDocHeader = "Undocumented commands:"
	k.MiscHeader = "Miscellaneous help topics:"
	k.NoHelp = "*** No help on %s"
}

func (k *Kmdt) bind(hand KmdtClient) {
	k.log(DEBUG, "KMDT Binding...")
	k.handlers = make(map[string]func(context.Context, string) bool, 0)
	k.helpers = make(map[string]func(context.Context), 0)
	k.completers = make(map[string]func(string) []string, 0)
	// Typecheck and store valid handlers from the KmdtClient
	v := reflect.ValueOf(hand)
	t := reflect.TypeOf(hand)
	for i := 0; i < t.NumMethod(); i++ {
		tm := t.Method(i)
		vm := v.Method(i)
		// Search for handlers
		// TODO: completion handler checks will be needed

		// Note that, unlike tm, vm has a Curry'ed receiver.
		// Specifically the receiver is v.
		if is, new := isHandler(tm); is {
			commandName := tm.Name
			commandName = strings.ToLower(commandName[len("Do"):])
			// Store the handler
			var checkedMethod func(context.Context, string) bool
			if new {
				checkedMethod = vm.Interface().(func(context.Context, string) bool)
			} else {
				checkedMethod = func (ctx context.Context, line string) bool {
					innerMethod := vm.Interface().(func(string) bool)
					return innerMethod(line)
				}
			}
			k.handlers[commandName] = checkedMethod
			k.log(DEEPDEBUG, "KMDT ...found handler", tm.Name)
		} else if is, new = isCompleter(tm); is {
			completerName := strings.ToLower(tm.Name[len("Complete"):])
			checkedMethod := vm.Interface().(func(string) []string)
			k.completers[completerName] = checkedMethod
			k.log(DEEPDEBUG, "KMDT ...found completer", tm.Name)
		} else if is, new = isHelper(tm); is {
			var checkedMethod func(context.Context)
			if new {
				checkedMethod = vm.Interface().(func(context.Context))
			} else {
				checkedMethod = func (ctx context.Context) {
					innerMethod := vm.Interface().(func())
					innerMethod()
				}
			}
			commandName := strings.ToLower(tm.Name[len("Help"):])
			k.helpers[commandName] = checkedMethod
			k.log(DEEPDEBUG, "KMDT ...found helper", tm.Name)
		} else if is, new = isPreLoop(tm); is {
			var checkedMethod func(context.Context)
			if new {
				checkedMethod = vm.Interface().(func(context.Context))
			} else {
				checkedMethod = func (ctx context.Context) {
					innerMethod := vm.Interface().(func())
					innerMethod()
				}
			}
			k.PreLoopHook = checkedMethod
			k.log(DEEPDEBUG, "KMDT ...found pre-loop", tm.Name)
		} else if is, new = isPostLoop(tm); is {
			var checkedMethod func(context.Context)
			if new {
				checkedMethod = vm.Interface().(func(context.Context))
			} else {
				checkedMethod = func (ctx context.Context) {
					innerMethod := vm.Interface().(func())
					innerMethod()
				}
			}
			k.PostLoopHook = checkedMethod
			k.log(DEEPDEBUG, "KMDT ...found post-loop", tm.Name)
		} else if is, new = isPreCommand(tm); is {
			var checkedMethod func(context.Context, string) string
			if new {
				checkedMethod = vm.Interface().(func(context.Context, string) string)
			} else {
				checkedMethod = func (ctx context.Context, line string) string {
					innerMethod := vm.Interface().(func(string) string)
					return innerMethod(line)
				}
			}
			k.PreCmdHook = checkedMethod
			k.log(DEEPDEBUG, "KMDT ...found pre-command", tm.Name)
		} else if is, new = isPostCommand(tm); is {
			var checkedMethod func(context.Context, bool, string) bool
			if new {
				checkedMethod = vm.Interface().(func(context.Context, bool, string) bool)
			} else {
				checkedMethod = func (ctx context.Context, stop bool, line string) bool {
					innerMethod := vm.Interface().(func(bool, string) bool)
					return innerMethod(stop, line)
				}
			}
			k.PostCmdHook = checkedMethod
			k.log(DEEPDEBUG, "KMDT ...found post-command", tm.Name)
		} else if is, new = isDefaultCommand(tm); is {
			var checkedMethod func(context.Context, string) bool
			if new {
				checkedMethod = vm.Interface().(func(context.Context, string) bool)
			} else {
				checkedMethod = func (ctx context.Context, line string) bool {
					innerMethod := vm.Interface().(func(string) bool)
					return innerMethod(line)
				}
			}
			k.DefaultHook = checkedMethod
			k.log(DEEPDEBUG, "KMDT ...found default", tm.Name)
		} else if is, new = isEmptyLine(tm); is {
			var checkedMethod func(context.Context) bool
			if new {
				checkedMethod = vm.Interface().(func(context.Context) bool)
			} else {
				checkedMethod = func (ctx context.Context) bool {
					innerMethod := vm.Interface().(func() bool)
					return innerMethod()
				}
			}
			k.EmptyLineHook = checkedMethod
			k.log(DEEPDEBUG, "KMDT ...found empty line handler", tm.Name)
		} else if is, new = isDefaultComplete(tm); is {
			checkedMethod := vm.Interface().(func(string) []string)
			k.CompleteDefaultHook = checkedMethod
			k.log(DEEPDEBUG, "KMDT ...found default completer", tm.Name)
		} else {
			// Not a valid handler
			k.log(DEEPDEBUG, "KMDT ...found non-handler", tm.Name)
			continue
		}
	}
	k.log(DEBUG, "KMDT ...bound", len(k.handlers), "commands")
}

func isHandler(method reflect.Method) (bool, bool) {
	count := method.Type.NumIn()
	maybe := strings.HasPrefix(method.Name, "Do") &&
		method.Type.NumOut() == 1 &&
		method.Type.Out(0) == typeOfBool &&
		(count == 2 || count == 3)
	args := false
	if maybe {
		if count == 2 {
			args = method.Type.In(1) == typeOfString
		} else if count == 3 {
			args = method.Type.In(1) == typeOfContext &&
				method.Type.In(2) == typeOfString
		}
		return args, count == 3
	}
	return false, false
}

func isCompleter(method reflect.Method) (bool, bool) {
	return (method.Type.NumIn() == 2) &&
		(method.Type.In(1) == typeOfString) &&
		(method.Type.NumOut() == 1) &&
		(method.Type.Out(0) == typeOfStringList) &&
		(strings.HasPrefix(method.Name, "Complete")), false
}

func isHelper(method reflect.Method) (bool, bool) {
	count := method.Type.NumIn()
	maybe := strings.HasPrefix(method.Name, "Help") &&
		method.Type.NumOut() == 0 &&
		(count == 1 || count == 2)
	args := false
	if maybe {
		if count == 1 {
			args = true
		} else if count == 2 {
			args = method.Type.In(1) == typeOfContext
		}
		return args, count == 2
	}
	return false, false
}

func isPreLoop(method reflect.Method) (bool, bool) {
	count := method.Type.NumIn()
	maybe := method.Name == "PreLoop" &&
		method.Type.NumOut() == 0 &&
		(count == 1 || count == 2)
	args := false
	if maybe {
		if count == 1 {
			args = true
		} else if count == 2 {
			args = method.Type.In(1) == typeOfContext
		}
		return args, count == 2
	}
	return false, false
}

func isPostLoop(method reflect.Method) (bool, bool) {
	count := method.Type.NumIn()
	maybe := method.Name == "PostLoop" &&
		method.Type.NumOut() == 0 &&
		(count == 1 || count == 2)
	args := false
	if maybe {
		if count == 1 {
			args = true
		} else if count == 2 {
			args = method.Type.In(1) == typeOfContext
		}
		return args, count == 2
	}
	return false, false
}

func isPreCommand(method reflect.Method) (bool, bool) {
	count := method.Type.NumIn()
	maybe := method.Name == "PreCmd" &&
		method.Type.NumOut() == 1 &&
		method.Type.Out(0) == typeOfString &&
		(count == 2 || count == 3)
	args := false
	if maybe {
		if count == 2 {
			args = method.Type.In(1) == typeOfString
		} else if count == 3 {
			args = method.Type.In(1) == typeOfContext &&
				method.Type.In(2) == typeOfString
		}
		return args, count == 3
	}
	return false, false
}

func isPostCommand(method reflect.Method) (bool, bool) {
	count := method.Type.NumIn()
	maybe := method.Name == "PostCmd" &&
		method.Type.NumOut() == 1 &&
		method.Type.Out(0) == typeOfBool &&
		(count == 3 || count == 4)
	args := false
	if maybe {
		if count == 3 {
			args = method.Type.In(1) == typeOfBool &&
				method.Type.In(2) == typeOfString
		} else if count == 4 {
			args = method.Type.In(1) == typeOfContext &&
				method.Type.In(2) == typeOfBool &&
				method.Type.In(3) == typeOfString
		}
		return args, count == 4
	}
	return false, false
}

func isDefaultCommand(method reflect.Method) (bool, bool) {
	count := method.Type.NumIn()
	maybe := method.Name == "Default" &&
		method.Type.NumOut() == 1 &&
		method.Type.Out(0) == typeOfBool &&
		(count == 2 || count == 3)
	args := false
	if maybe {
		if count == 2 {
			args = method.Type.In(1) == typeOfString
		} else if count == 3 {
			args = method.Type.In(1) == typeOfContext &&
				method.Type.In(2) == typeOfString
		}
		return args, count == 3
	}
	return false, false
}

func isEmptyLine(method reflect.Method) (bool, bool) {
	count := method.Type.NumIn()
	maybe := method.Name == "EmptyLine" &&
		method.Type.NumOut() == 1 &&
		method.Type.Out(0) == typeOfBool &&
		(count == 1 || count == 2)
	args := false
	if maybe {
		if count == 1 {
			args = true
		} else if count == 2 {
			args = method.Type.In(1) == typeOfContext
		}
		return args, count == 2
	}
	return false, false
}

func isDefaultComplete(method reflect.Method) (bool, bool) {
	count := method.Type.NumIn()
	maybe := method.Name == "DefaultComplete" &&
		method.Type.NumOut() == 1 &&
		method.Type.Out(0) == typeOfStringList &&
		(count == 2 || count == 3)
	args := false
	if maybe {
		if count == 2 {
			args = method.Type.In(1) == typeOfString
		} else if count == 3 {
			args = method.Type.In(1) == typeOfContext &&
				method.Type.In(2) == typeOfString
		}
		return args, count == 3
	}
	return false, false
}

// Repeatedly issue a prompt, accept input, parse an initial prefix
// off the received input, and dispatch to action methods, passing them
// the remainder of the line as argument.
func (k *Kmdt) CmdLoop(ctx context.Context, intro string) {
	k.PreLoop(ctx)
	if intro != "" {
		k.Intro = intro
	}
	if k.Intro != "" {
		if k.linecore == nil {
			k.WriteString(k.Intro + "\n")
		} else {
			println(k.Intro)
		}
	}
	stop := false
	line := ""
	for !stop {
		if len(k.CommandQueue) > 0 {
			line, k.CommandQueue = k.CommandQueue[0], k.CommandQueue[1:]
			k.log(DEEPDEBUG, "KMDT command queue pop:", line)
		} else {
			if k.linecore != nil {
				k.log(DEEPDEBUG, "KMDT readline reading...")
				var err error
				line, err = k.linecore.Readline()
				if err == readline.ErrInterrupt {
					p, err := os.FindProcess(os.Getpid())
					if err != nil {
						continue
					}
					if err := p.Signal(os.Interrupt); err != nil {
						continue
					}
				} else if err == io.EOF {
					k.log(DEEPDEBUG, "KMDT readline EOF")
					line = "EOF"
				} else if err != nil { // Unknown err
					k.log(ERROR, "KMDT readline error:", err)
				}
				line = strings.TrimSpace(line)
			} else {
				if !k.isFile { // Only emit prompt if a terminal
					k.WriteString(k.prompt)
				}
				var err error
				line, err = ReadToDelimiter(k.stdin, '\n')
				if err == io.EOF && line == "" {
					k.log(DEEPDEBUG, "KMDT basic read EOF")
					line = "EOF"
				} else if err == nil {
					line = strings.Trim(line, "\r\n")
				} else {
					k.log(ERROR, "KMDT basic read error:", err)
				}
			}
		}
		line = k.PreCmd(ctx, line)
		stop = k.OneCmd(ctx, line)
		stop = k.PostCmd(ctx, stop, line)
	}
	k.PostLoop(ctx)
}

// Parse the line into a command name and a string containing
// the arguments.  Returns a tuple containing (command, args, line).
// 'command' and 'args' may be None if the line couldn't be parsed.
func (k *Kmdt) ParseLine(lineIn string) (cmd string, arg string, lineOut string) {
	lineOut = strings.TrimSpace(lineIn)
	if lineOut == "" {
		return "", "", lineOut
	} else if lineOut[0] == '?' {
		lineOut = "help " + lineOut[1:]
	} else if lineOut[0] == '!' {
		// look for "shell" in handler map
		if _, ok := k.handlers["shell"]; ok == true {
			lineOut = "shell " + lineOut[1:]
		} else {
			return "", "", lineOut
		}
	}
	i, n := 0, len(lineOut)
	// while i < n and line[i] in self.identchars: i = i+1
	for i < n {
		found := false
		for charI := 0; charI < len(k.IdentChars); charI++ {
			if lineOut[i] == k.IdentChars[charI] {
				found = true
				break
			}
		}
		if found {
			i++
		} else {
			break
		}
	}
	cmd = lineOut[:i]
	arg = strings.TrimSpace(lineOut[i:])
	return cmd, arg, lineOut
}

// Interpret the argument as though it had been typed in response to the
// prompt.
func (k *Kmdt) OneCmd_core(ctx context.Context, lineIn string) (stopOut bool) {
	//fmt.Println("OneCmd:", lineIn)
	cmd, arg, line := k.ParseLine(lineIn)
	// Handle empty line
	if line == "" {
		return k.EmptyLine(ctx)
	}
	// Handle no command
	// The only way we get cmd == "" and arg == "" but line != "" is if one
	// of the special commands triggered and failed.
	if (cmd == "") && (arg == "") {
		return k.Default(ctx, line)
	}
	// Set last command
	k.LastCmd = line
	// Check for EOF
	if line == "EOF" {
		k.LastCmd = ""
	}
	// Check empty command
	if cmd == "" {
		return k.Default(ctx, line)
	} else {
		// Look for handler
		hand, ok := k.handlers[strings.ToLower(cmd)]
		// Check for special handlers
		// Do_help, completer
		if ok {
			defer trace.StartRegion(ctx, cmd).End()
			return hand(ctx, arg)
		} else if cmd == "help" {
			if hand, ok = k.handlers["help"]; ok == true {
				defer trace.StartRegion(ctx, cmd).End()
				hand(ctx, arg)
			} else {
				// Do default help
				k.DoHelp(ctx, arg)
			}
			return false
		} else {
			return k.Default(ctx, line)
		}
	}
}

func (k *Kmdt) defaultCmd(ctx context.Context, lineIn string) (stopOut bool) {
	k.WriteString("*** Unknown syntax: " + lineIn + "\n")
	return false
}

func (k *Kmdt) DoHelp(ctx context.Context, argIn string) (stopOut bool) {
	if argIn != "" {
		// Look for a Help* function
		helper, ok := k.helpers[argIn]
		if ok == true {
			helper(ctx)
			return false
		} else {
			// Nothing found, default
			k.WriteString(fmt.Sprintf(k.NoHelp+"\n", argIn))
		}
	} else {
		cmds_doc := []string{}
		cmds_undoc := []string{}
		misc_help_map := make(map[string]bool, 0)
		for c := range k.helpers {
			misc_help_map[c] = true
		}
		// Sort helpers into cmd+help, cmd-alone, help-alone
		for cmd, _ := range k.handlers {
			if _, ok := k.helpers[cmd]; ok {
				// Documented command
				cmds_doc = append(cmds_doc, cmd)
				delete(misc_help_map, cmd)
			} else {
				// Undocumented command
				if cmd == "eof" {
					continue
				}
				cmds_undoc = append(cmds_undoc, cmd)
			}
		}
		// Convert misc_help to a list
		misc_help := make([]string, len(misc_help_map))
		i := 0
		for c := range misc_help_map {
			misc_help[i] = c
			i++
		}
		// Sort to dodge map key ordering randomness
		sort.Strings(cmds_doc)
		sort.Strings(misc_help)
		sort.Strings(cmds_undoc)
		// Format and output
		k.WriteString(k.DocLeader + "\n")
		k.printTopics(k.DocHeader, cmds_doc, 80)
		k.printTopics(k.MiscHeader, misc_help, 80)
		k.printTopics(k.UnDocHeader, cmds_undoc, 80)
	}
	return false
}

func (k *Kmdt) Readline() (line string, err error) {
	if k.linecore != nil && k.isFile{
		return k.linecore.Readline()
	} else {
		if !k.isFile { // Only emit prompt if terminal
			k.WriteString(k.prompt)
		}
		return ReadToDelimiter(k.stdinShim, '\n')
	}
}

func (k *Kmdt) Write(data []byte) {
	if k.linecore != nil { // We are using readline here
		k.linecore.Write(data)
	} else if k.stdout != nil { // Using normal output
		k.stdout.Write(data)
	}
}

func (k *Kmdt) WriteString(data string) {
	k.Write([]byte(data))
}

func (k *Kmdt) printTopics(header string, cmds []string, maxcol int) {
	if len(cmds) > 0 {
		k.WriteString(header + "\n")
		if k.Ruler != "" {
			ruler := strings.Repeat(k.Ruler, len(header))
			k.WriteString(ruler + "\n")
		}
		col := columnize(cmds, maxcol-1)
		k.WriteString(col + "\n")
	}
}

// Display a list of strings as a compact set of columns.
// ------------------------------------------------------
// Each column is only as wide as necessary.
// Columns are separated by two spaces (one was not legible enough).
func columnize(strs []string, displayWidth int) (formatted string) {
	if (strs == nil) || (len(strs) < 1) {
		return "<empty>\n"
	}
	nrows, colwidths := calculateColumns(strs, displayWidth)
	formatted = formatColumns(strs, nrows, colwidths)
	return formatted
}

// Calculate position data for columnize
func calculateColumns(strs []string, displayWidth int) (nrows int, colwidths []int) {
	var colwidth, totwidth, xlen int
	size := len(strs)
	foundMatch := false // Go does not have for...else construct
	for nrows = 1; nrows < size; nrows++ {
		ncols := (size + nrows - 1) / nrows
		colwidths = make([]int, 0)
		totwidth = -2
		for col := 0; col < ncols; col++ {
			colwidth = 0
			for row := 0; row < nrows; row++ {
				i := row + (nrows * col)
				if i >= size {
					break
				}
				x := strs[i]
				// colwidth = max(colwidth, len(x))
				xlen = len(x)
				if colwidth > xlen {
					colwidth = colwidth
				} else {
					colwidth = xlen
				}
			}
			colwidths = append(colwidths, colwidth)
			totwidth += colwidth + 2
			if totwidth > displayWidth {
				break
			}
		}
		if totwidth <= displayWidth {
			foundMatch = true
			break
		}
	}
	if foundMatch == false {
		nrows = size
		colwidths = []int{0}
	}
	return nrows, colwidths
}

// Format according to precalculated position data
func formatColumns(strs []string, nrows int, colwidths []int) (formatted string) {
	formatted = ""
	ncols := len(colwidths)
	size := len(strs)
	for row := 0; row < nrows; row++ {
		texts := make([]string, 0)
		for col := 0; col < ncols; col++ {
			i := row + (nrows * col)
			if i >= size {
				texts = append(texts, "")
			} else {
				texts = append(texts, strs[i])
			}
		}
		// while texts and not texts[-1]:
		//     del texts[-1]
		textsLimit := len(texts) - 1
		for (textsLimit > 0) && (texts[textsLimit] == "") {
			textsLimit--
		}
		for col := 0; col < textsLimit; col++ {
			// texts[col] = texts[col].ljust(colwidths[col])
			spaceCount := colwidths[col] - len(texts[col])
			spaces := strings.Repeat(" ", spaceCount)
			texts[col] = texts[col] + spaces
		}
		// self.stdout.write("%s\n"%str("  ".join(texts)))
		formatted = formatted + strings.Join(texts[:textsLimit+1], "  ") + "\n"
	}
	return formatted
}

// The normal readline completion handling requires a seperate dynamic
// callback for each layer of the completion. This allows the same callback
// to handle multiple arguments on a single command.
type RecursiveCompleter struct {
	Completer      readline.PrefixCompleter
	MaxRecurse     int
	CurrentRecurse int
}

func (r *RecursiveCompleter) Print(prefix string, level int, buf *bytes.Buffer) {
	r.Completer.Print(prefix, level, buf)
}

func (r *RecursiveCompleter) Do(line []rune, pos int) (newLine [][]rune, length int) {
	return r.Completer.Do(line, pos)
}

func (r *RecursiveCompleter) GetName() []rune {
	return r.Completer.GetName()
}

func (r *RecursiveCompleter) GetChildren() []readline.PrefixCompleterInterface {
	children := r.Completer.GetChildren()
	if children == nil || len(children) < 1 {
		// No defined children, recurse
		if r.CurrentRecurse < r.MaxRecurse {
			r.CurrentRecurse++
			return []readline.PrefixCompleterInterface{r}
		} else {
			r.CurrentRecurse = 0
			return children
		}
	} else {
		return children
	}
}

func (r *RecursiveCompleter) SetChildren(children []readline.PrefixCompleterInterface) {
	r.Completer.SetChildren(children)
}

func (r *RecursiveCompleter) IsDynamic() bool {
	return r.Completer.IsDynamic()
}

func (r *RecursiveCompleter) GetDynamicNames(line []rune) [][]rune {
	return r.Completer.GetDynamicNames(line)
}

func PcItemDynamicRecursive(callback readline.DynamicCompleteFunc) readline.PrefixCompleterInterface {
	return &RecursiveCompleter{
		Completer: readline.PrefixCompleter{
			Callback: callback,
			Dynamic:  true,
		},
		MaxRecurse:     31,
		CurrentRecurse: 0,
	}
}

func (k *Kmdt) buildCompletion() {
	// Construct completion tree from handlers
	var handList []readline.PrefixCompleterInterface
	for handlerName, _ := range k.handlers {
		if completer, ok := k.completers[handlerName]; ok {
			// Custom completer available
			comp := PcItemDynamicRecursive(completer)
			handList = append(handList, readline.PcItem(handlerName, comp))
		} else {
			if k.CompleteDefaultHook == nil {
				// No custom completer, no default completer
				handList = append(handList, readline.PcItem(handlerName))
			} else {
				// No custom completer, but have a default completer
				comp := readline.PcItemDynamic(k.CompleteDefaultHook)
				handList = append(handList, readline.PcItem(handlerName, comp))
			}
		}
	}
	var helpList []readline.PrefixCompleterInterface
	for helperName, _ := range k.helpers {
		helpList = append(helpList, readline.PcItem(helperName))
	}
	handList = append(handList, readline.PcItem("help", helpList...))
	k.rootCompleter = readline.NewPrefixCompleter(handList...)
}

// Performs the same function as bufio's ReadString without the gluttony
func ReadToDelimiter(r io.Reader, delimiter byte) (s string, err error) {
	buf := make([]byte, 0)
	charBuf := make([]byte, 1)
	for true {
		count, err := r.Read(charBuf)
		if err != nil {
			return string(buf), err
		}
		if count == 0 {
			// no data
			// spin?
		} else if count != 1 {
			// can't happen, so it will
		}
		buf = append(buf, charBuf[0])
		if charBuf[0] == delimiter {
			break
		}
	}
	return string(buf), nil
}

type readShimmer struct {
	Input io.Reader
}

func (r readShimmer) Read(b []byte) (count int, err error) {
	count, err = r.Input.Read(b)
	return count, err
}

func (r readShimmer) Close() (err error) {
	return nil
}
