package ishell

import (
	"bytes"
	"fmt"
	"os/exec"
	"runtime"
	"strings"
)

// Actions are actions that can be performed by a shell.
type Actions interface {
	// ReadLine reads a line from standard input.
	ReadLine() string
	// ReadLineErr is ReadLine but returns error as well
	ReadLineErr() (string, error)
	// ReadPassword reads password from standard input without echoing the characters.
	// Note that this only works as expected when the standard input is a terminal.
	ReadPassword() string
	// ReadPasswordErr is ReadPassword but returns error as well
	ReadPasswordErr() (string, error)
	// ReadMultiLinesFunc reads multiple lines from standard input. It passes each read line to
	// f and stops reading when f returns false.
	ReadMultiLinesFunc(f func(string) bool) string
	// ReadMultiLines reads multiple lines from standard input. It stops reading when terminator
	// is encountered at the end of the line. It returns the lines read including terminator.
	// For more control, use ReadMultiLinesFunc.
	ReadMultiLines(terminator string) string
	// Println prints to output and ends with newline character.
	Println(val ...interface{})
	// Print prints to output.
	Print(val ...interface{})
	// Printf prints to output using string format.
	Printf(format string, val ...interface{})
	// ShowPaged shows a paged text that is scrollable.
	// This leverages on "less" for unix and "more" for windows.
	ShowPaged(text string) error
	// MultiChoice presents options to the user.
	// returns the index of the selection or -1 if nothing is
	// selected.
	// text is displayed before the options.
	MultiChoice(options []string, text string) int
	// Checklist is similar to MultiChoice but user can choose multiple variants using Space.
	// init is initially selected options.
	Checklist(options []string, text string, init []int) []int
	// SetPrompt sets the prompt string. The string to be displayed before the cursor.
	SetPrompt(prompt string)
	// SetMultiPrompt sets the prompt string used for multiple lines. The string to be displayed before
	// the cursor; starting from the second line of input.
	SetMultiPrompt(prompt string)
	// ShowPrompt sets whether prompt should show when requesting input for ReadLine and ReadPassword.
	// Defaults to true.
	ShowPrompt(show bool)
	// Cmds returns all the commands added to the shell.
	Cmds() []*Cmd
	// HelpText returns the computed help of top level commands.
	HelpText() string
	// ClearScreen clears the screen. Same behaviour as running 'clear' in unix terminal or 'cls' in windows cmd.
	ClearScreen() error
	// Stop stops the shell. This will stop the shell from auto reading inputs and calling
	// registered functions. A stopped shell is only inactive but totally functional.
	// Its functions can still be called and can be restarted.
	Stop()
}

type shellActionsImpl struct {
	*Shell
}

// ReadLine reads a line from standard input.
func (s *shellActionsImpl) ReadLine() string {
	line, _ := s.readLine()
	return line
}

func (s *shellActionsImpl) ReadLineErr() (string, error) {
	return s.readLine()
}

func (s *shellActionsImpl) ReadPassword() string {
	return s.reader.readPassword()
}

func (s *shellActionsImpl) ReadPasswordErr() (string, error) {
	return s.reader.readPasswordErr()
}

func (s *shellActionsImpl) ReadMultiLinesFunc(f func(string) bool) string {
	lines, _ := s.readMultiLinesFunc(f)
	return lines
}

func (s *shellActionsImpl) ReadMultiLines(terminator string) string {
	return s.ReadMultiLinesFunc(func(line string) bool {
		if strings.HasSuffix(strings.TrimSpace(line), terminator) {
			return false
		}
		return true
	})
}

func (s *shellActionsImpl) Println(val ...interface{}) {
	s.reader.buf.Truncate(0)
	fmt.Fprintln(s.writer, val...)
}

func (s *shellActionsImpl) Print(val ...interface{}) {
	s.reader.buf.Truncate(0)
	fmt.Fprint(s.reader.buf, val...)
	fmt.Fprint(s.writer, val...)
}

func (s *shellActionsImpl) Printf(format string, val ...interface{}) {
	s.reader.buf.Truncate(0)
	fmt.Fprintf(s.reader.buf, format, val...)
	fmt.Fprintf(s.writer, format, val...)
}

func (s *shellActionsImpl) MultiChoice(options []string, text string) int {
	choice := s.multiChoice(options, text, nil, false)
	return choice[0]
}
func (s *shellActionsImpl) Checklist(options []string, text string, init []int) []int {
	return s.multiChoice(options, text, init, true)
}
func (s *shellActionsImpl) SetPrompt(prompt string) {
	s.reader.prompt = prompt
	s.reader.scanner.SetPrompt(s.reader.rlPrompt())
}

func (s *shellActionsImpl) SetMultiPrompt(prompt string) {
	s.reader.multiPrompt = prompt
}

func (s *shellActionsImpl) ShowPrompt(show bool) {
	s.reader.showPrompt = show
	s.reader.scanner.SetPrompt(s.reader.rlPrompt())
}

func (s *shellActionsImpl) Cmds() []*Cmd {
	var cmds []*Cmd
	for _, cmd := range s.rootCmd.children {
		cmds = append(cmds, cmd)
	}
	return cmds
}

func (s *shellActionsImpl) ClearScreen() error {
	return clearScreen(s.Shell)
}

func (s *shellActionsImpl) ShowPaged(text string) error {
	return showPaged(s.Shell, text)
}

func (s *shellActionsImpl) Stop() {
	s.stop()
}

func (s *shellActionsImpl) HelpText() string {
	return s.rootCmd.HelpText()
}

func showPaged(s *Shell, text string) error {
	var cmd *exec.Cmd

	if s.pager == "" {
		if runtime.GOOS == "windows" {
			s.pager = "more"
		} else {
			s.pager = "less"
		}
	}

	cmd = exec.Command(s.pager, s.pagerArgs...)
	cmd.Stdout = s.writer
	cmd.Stderr = s.writer
	cmd.Stdin = bytes.NewBufferString(text)
	return cmd.Run()
}
