// Package complete provides a tool for bash writing bash completion in go.
//
// Writing bash completion scripts is a hard work. This package provides an easy way
// to create bash completion scripts for any command, and also an easy way to install/uninstall
// the completion of the command.
package complete

import (
	"flag"
	"fmt"
	"io"
	"os"
	"strconv"

	"github.com/posener/complete/cmd"
	"github.com/posener/complete/match"
)

const (
	envLine  = "COMP_LINE"
	envPoint = "COMP_POINT"
	envDebug = "COMP_DEBUG"
)

// Complete structs define completion for a command with CLI options
type Complete struct {
	Command Command
	cmd.CLI
	Out io.Writer
}

// New creates a new complete command.
// name is the name of command we want to auto complete.
// IMPORTANT: it must be the same name - if the auto complete
// completes the 'go' command, name must be equal to "go".
// command is the struct of the command completion.
func New(name string, command Command) *Complete {
	return &Complete{
		Command: command,
		CLI:     cmd.CLI{Name: name},
		Out:     os.Stdout,
	}
}

// Run runs the completion and add installation flags beforehand.
// The flags are added to the main flag CommandLine variable.
func (c *Complete) Run() bool {
	c.AddFlags(nil)
	flag.Parse()
	return c.Complete()
}

// Complete a command from completion line in environment variable,
// and print out the complete options.
// returns success if the completion ran or if the cli matched
// any of the given flags, false otherwise
// For installation: it assumes that flags were added and parsed before
// it was called.
func (c *Complete) Complete() bool {
	line, point, ok := getEnv()
	if !ok {
		// make sure flags parsed,
		// in case they were not added in the main program
		return c.CLI.Run()
	}

	if point >= 0 && point < len(line) {
		line = line[:point]
	}

	Log("Completing phrase: %s", line)
	a := newArgs(line)
	Log("Completing last field: %s", a.Last)
	options := c.Command.Predict(a)
	Log("Options: %s", options)

	// filter only options that match the last argument
	matches := []string{}
	for _, option := range options {
		if match.Prefix(option, a.Last) {
			matches = append(matches, option)
		}
	}
	Log("Matches: %s", matches)
	c.output(matches)
	return true
}

func getEnv() (line string, point int, ok bool) {
	line = os.Getenv(envLine)
	if line == "" {
		return
	}
	point, err := strconv.Atoi(os.Getenv(envPoint))
	if err != nil {
		// If failed parsing point for some reason, set it to point
		// on the end of the line.
		Log("Failed parsing point %s: %v", os.Getenv(envPoint), err)
		point = len(line)
	}
	return line, point, true
}

func (c *Complete) output(options []string) {
	// stdout of program defines the complete options
	for _, option := range options {
		fmt.Fprintln(c.Out, option)
	}
}
