package pkg

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"strconv"
	"strings"

	"gitlab.com/M0M097/pkg/cli"
	"gitlab.com/M0M097/pkg/sborepo"
)

// RunArgs groups the command-line arguments, configuration, and streams used
// by Run and RunWithUI.
type RunArgs struct {
	// Argv contains the command-line arguments. Check pkg's user documentation
	// for more information about them.
	Argv []string

	Config  Config  // quasi-constant values (file paths, commands, etc.)
	Streams Streams // All i/o operations are done via these streams
}

// Run is the high-level, real main function of the pkg tool.
// It is a dedicated function to ease testing and maybe embedding in other
// programs. It calls the still-high-level RunWithUI function with a CliUI
// implementation.
func Run(args RunArgs) {
	stdout := bufio.NewWriter(args.Streams.Out)
	defer stdout.Flush()
	var (
		executor = cli.Executor{
			Stdout: args.Streams.Out,
			Stderr: args.Streams.Err,
			Log:    true,
		}
		ui = CliUI{
			stdout: stdout,
			stdin:  args.Streams.In,
			stderr: args.Streams.Err,
			pager:  cli.Pager(executor, args.Config.FallbackPager), // To display files
		}
		settings, err = NewSettings(args.Argv, args.Config.DefaultPkgFile) // Parse cli args
	)
	if err != nil {
		ui.Fatal(err)
	}
	log.SetOutput(args.Streams.Err) // Log to stderr
	RunWithUI(args.Config, &ui, settings, executor)
}

// CliUI implements UI with command-line streams and a pager.
type CliUI struct {
	stdout *bufio.Writer
	stdin  io.Reader
	stderr io.Writer
	pager  func(files ...string) error
}

// Fatal reports a fatal error and exits the process.
func (c *CliUI) Fatal(v ...interface{}) { log.Fatal(v...) }

// Prompt asks the user a yes/no question on the configured streams.
func (c *CliUI) Prompt(msg string) bool { return cli.PromptUser(c.stdout, c.stdin, msg) }

// Display shows files using the configured pager.
func (c *CliUI) Display(files ...string) error { return c.pager(files...) }

// ListLines writes each line followed by a newline.
func (c *CliUI) ListLines(lines []string) {
	for _, l := range lines {
		c.Alert(l)
	}
}

// Alert writes msg followed by a newline.
func (c *CliUI) Alert(msg string) {
	c.stdout.WriteString(msg)
	c.stdout.WriteString("\n")
}

// ChoosePkgs prompts the user to select packages from listPkgs and returns an
// iterator over the selected packages.
func (c *CliUI) ChoosePkgs(listPkgs sborepo.PkgIter) sborepo.PkgIter {
	var (
		i        int
		pkgArray []*sborepo.Package
	)
	// Ask user which packages to exclude
	listPkgs(func(pkg *sborepo.Package) {
		i++
		fmt.Fprintln(c.stdout, i, pkg.Description())
		pkgArray = append(pkgArray, pkg)
	})
	c.stdout.WriteString("Space separated list of packages to exclude:\n")
	c.stdout.Flush()
	// Parse user input
	scanner := bufio.NewScanner(c.stdin)
	scanner.Scan()
	var indices []int
	for _, field := range strings.Fields(scanner.Text()) {
		num, err := strconv.Atoi(field)
		if err != nil || num < 1 || num > len(pkgArray) {
			fmt.Fprintf(c.stdout, "Invalid input: `%s` Repeat...\n", field)
			return c.ChoosePkgs(listPkgs)
		}
		indices = append(indices, num-1)
	}
	return func(f sborepo.PkgFunc) {
		for _, idx := range indices {
			f(pkgArray[idx])
		}
	}
}

// NewProgressReporter creates a CLI progress reporter for taskLabels.
func (c *CliUI) NewProgressReporter(taskLabels []sborepo.PkgName) cli.ProgressReporter {
	return cli.NewProgressReporter(taskLabels, c.stderr)
}
