package kong

import (
	"fmt"
	"math"
	"os"
	"reflect"
	"strconv"
	"strings"

	"github.com/pkg/errors"
)

// A Visitable component in the model.
type Visitable interface {
	node()
}

// Application is the root of the Kong model.
type Application struct {
	*Node
	// Help flag, if the NoDefaultHelp() option is not specified.
	HelpFlag *Flag
}

// Argument represents a branching positional argument.
type Argument = Node

// Command represents a command in the CLI.
type Command = Node

// NodeType is an enum representing the type of a Node.
type NodeType int

// Node type enumerations.
const (
	ApplicationNode NodeType = iota
	CommandNode
	ArgumentNode
)

// Node is a branch in the CLI. ie. a command or positional argument.
type Node struct {
	Type       NodeType
	Parent     *Node
	Name       string
	Help       string // Short help displayed in summaries.
	Detail     string // Detailed help displayed when describing command/arg alone.
	Group      string
	Hidden     bool
	Flags      []*Flag
	Positional []*Positional
	Children   []*Node
	Target     reflect.Value // Pointer to the value in the grammar that this Node is associated with.
	Tag        *Tag

	Argument *Value // Populated when Type is ArgumentNode.
}

func (*Node) node() {}

// Leaf returns true if this Node is a leaf node.
func (n *Node) Leaf() bool {
	return len(n.Children) == 0
}

// Find a command/argument/flag by pointer to its field.
//
// Returns nil if not found. Panics if ptr is not a pointer.
func (n *Node) Find(ptr interface{}) *Node {
	key := reflect.ValueOf(ptr)
	if key.Kind() != reflect.Ptr {
		panic("expected a pointer")
	}
	return n.findNode(key)
}

func (n *Node) findNode(key reflect.Value) *Node {
	if n.Target == key {
		return n
	}
	for _, child := range n.Children {
		if found := child.findNode(key); found != nil {
			return found
		}
	}
	return nil
}

// AllFlags returns flags from all ancestor branches encountered.
//
// If "hide" is true hidden flags will be omitted.
func (n *Node) AllFlags(hide bool) (out [][]*Flag) {
	if n.Parent != nil {
		out = append(out, n.Parent.AllFlags(hide)...)
	}
	group := []*Flag{}
	for _, flag := range n.Flags {
		if !hide || !flag.Hidden {
			group = append(group, flag)
		}
	}
	if len(group) > 0 {
		out = append(out, group)
	}
	return
}

// Leaves returns the leaf commands/arguments under Node.
//
// If "hidden" is true hidden leaves will be omitted.
func (n *Node) Leaves(hide bool) (out []*Node) {
	_ = Visit(n, func(nd Visitable, next Next) error {
		if nd == n {
			return next(nil)
		}
		if node, ok := nd.(*Node); ok {
			if hide && node.Hidden {
				return nil
			}
			if len(node.Children) == 0 && node.Type != ApplicationNode {
				out = append(out, node)
			}
		}
		return next(nil)
	})
	return
}

// Depth of the command from the application root.
func (n *Node) Depth() int {
	depth := 0
	p := n.Parent
	for p != nil && p.Type != ApplicationNode {
		depth++
		p = p.Parent
	}
	return depth
}

// Summary help string for the node (not including application name).
func (n *Node) Summary() string {
	summary := n.Path()
	if flags := n.FlagSummary(true); flags != "" {
		summary += " " + flags
	}
	args := []string{}
	for _, arg := range n.Positional {
		args = append(args, arg.Summary())
	}
	if len(args) != 0 {
		summary += " " + strings.Join(args, " ")
	} else if len(n.Children) > 0 {
		summary += " <command>"
	}
	return summary
}

// FlagSummary for the node.
func (n *Node) FlagSummary(hide bool) string {
	required := []string{}
	count := 0
	for _, group := range n.AllFlags(hide) {
		for _, flag := range group {
			count++
			if flag.Required {
				required = append(required, flag.Summary())
			}
		}
	}
	return strings.Join(required, " ")
}

// FullPath is like Path() but includes the Application root node.
func (n *Node) FullPath() string {
	root := n
	for root.Parent != nil {
		root = root.Parent
	}
	return strings.TrimSpace(root.Name + " " + n.Path())
}

// Vars returns the combined Vars defined by all ancestors of this Node.
func (n *Node) Vars() Vars {
	if n == nil {
		return Vars{}
	}
	return n.Parent.Vars().CloneWith(n.Tag.Vars)
}

// Path through ancestors to this Node.
func (n *Node) Path() (out string) {
	if n.Parent != nil {
		out += " " + n.Parent.Path()
	}
	switch n.Type {
	case CommandNode:
		out += " " + n.Name
	case ArgumentNode:
		out += " " + "<" + n.Name + ">"
	}
	return strings.TrimSpace(out)
}

// A Value is either a flag or a variable positional argument.
type Value struct {
	Flag         *Flag // Nil if positional argument.
	Name         string
	Help         string
	Default      string
	DefaultValue reflect.Value
	Enum         string
	Mapper       Mapper
	Tag          *Tag
	Target       reflect.Value
	Required     bool
	Set          bool   // Set to true when this value is set through some mechanism.
	Format       string // Formatting directive, if applicable.
	Position     int    // Position (for positional arguments).
}

// EnumMap returns a map of the enums in this value.
func (v *Value) EnumMap() map[string]bool {
	parts := strings.Split(v.Enum, ",")
	out := make(map[string]bool, len(parts))
	for _, part := range parts {
		out[strings.TrimSpace(part)] = true
	}
	return out
}

// ShortSummary returns a human-readable summary of the value, not including any placeholders/defaults.
func (v *Value) ShortSummary() string {
	if v.Flag != nil {
		return fmt.Sprintf("--%s", v.Name)
	}
	argText := "<" + v.Name + ">"
	if v.IsCumulative() {
		argText += " ..."
	}
	if !v.Required {
		argText = "[" + argText + "]"
	}
	return argText
}

// Summary returns a human-readable summary of the value.
func (v *Value) Summary() string {
	if v.Flag != nil {
		if v.IsBool() {
			return fmt.Sprintf("--%s", v.Name)
		}
		return fmt.Sprintf("--%s=%s", v.Name, v.Flag.FormatPlaceHolder())
	}
	argText := "<" + v.Name + ">"
	if v.IsCumulative() {
		argText += " ..."
	}
	if !v.Required {
		argText = "[" + argText + "]"
	}
	return argText
}

// IsCumulative returns true if the type can be accumulated into.
func (v *Value) IsCumulative() bool {
	return v.IsSlice() || v.IsMap()
}

// IsSlice returns true if the value is a slice.
func (v *Value) IsSlice() bool {
	return v.Target.Type().Name() == "" && v.Target.Kind() == reflect.Slice
}

// IsMap returns true if the value is a map.
func (v *Value) IsMap() bool {
	return v.Target.Kind() == reflect.Map
}

// IsBool returns true if the underlying value is a boolean.
func (v *Value) IsBool() bool {
	if m, ok := v.Mapper.(BoolMapper); ok && m.IsBool() {
		return true
	}
	return v.Target.Kind() == reflect.Bool
}

// Parse tokens into value, parse, and validate, but do not write to the field.
func (v *Value) Parse(scan *Scanner, target reflect.Value) (err error) {
	defer func() {
		if rerr := recover(); rerr != nil {
			switch rerr := rerr.(type) {
			case Error:
				err = errors.Wrap(rerr, v.ShortSummary())
			default:
				panic(fmt.Sprintf("mapper %T failed to apply to %s: %s", v.Mapper, v.Summary(), rerr))
			}
		}
	}()
	err = v.Mapper.Decode(&DecodeContext{Value: v, Scan: scan}, target)
	if err != nil {
		return errors.Wrap(err, v.ShortSummary())
	}
	v.Set = true
	return nil
}

// Apply value to field.
func (v *Value) Apply(value reflect.Value) {
	v.Target.Set(value)
	v.Set = true
}

// ApplyDefault value to field if it is not already set.
func (v *Value) ApplyDefault() error {
	if reflectValueIsZero(v.Target) {
		return v.Reset()
	}
	v.Set = true
	return nil
}

// Reset this value to its default, either the zero value or the parsed result of its envar,
// or its "default" tag.
//
// Does not include resolvers.
func (v *Value) Reset() error {
	v.Target.Set(reflect.Zero(v.Target.Type()))
	if v.Tag.Env != "" {
		envar := os.Getenv(v.Tag.Env)
		if envar != "" {
			err := v.Parse(ScanFromTokens(Token{Type: FlagValueToken, Value: envar}), v.Target)
			if err != nil {
				return fmt.Errorf("%s (from envar %s=%q)", err, v.Tag.Env, envar)
			}
			return nil
		}
	}
	if v.Default != "" {
		return v.Parse(ScanFromTokens(Token{Type: FlagValueToken, Value: v.Default}), v.Target)
	}
	return nil
}

func (*Value) node() {}

// A Positional represents a non-branching command-line positional argument.
type Positional = Value

// A Flag represents a command-line flag.
type Flag struct {
	*Value
	Group       string // Logical grouping when displaying. May also be used by configuration loaders to group options logically.
	Xor         string
	PlaceHolder string
	Env         string
	Short       rune
	Hidden      bool
}

func (f *Flag) String() string {
	out := "--" + f.Name
	if f.Short != 0 {
		out = fmt.Sprintf("-%c, %s", f.Short, out)
	}
	if !f.IsBool() {
		out += "=" + f.FormatPlaceHolder()
	}
	return out
}

// FormatPlaceHolder formats the placeholder string for a Flag.
func (f *Flag) FormatPlaceHolder() string {
	tail := ""
	if f.Value.IsSlice() && f.Value.Tag.Sep != -1 {
		tail += string(f.Value.Tag.Sep) + "..."
	}
	if f.Default != "" {
		if f.Value.Target.Kind() == reflect.String {
			return strconv.Quote(f.Default) + tail
		}
		return f.Default + tail
	}
	if f.PlaceHolder != "" {
		return f.PlaceHolder + tail
	}
	if f.Value.IsMap() {
		if f.Value.Tag.MapSep != -1 {
			tail = string(f.Value.Tag.MapSep) + "..."
		}
		return "KEY=VALUE" + tail
	}
	return strings.ToUpper(f.Name) + tail
}

// This is directly from the Go 1.13 source code.
func reflectValueIsZero(v reflect.Value) bool {
	switch v.Kind() {
	case reflect.Bool:
		return !v.Bool()
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return v.Int() == 0
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
		return v.Uint() == 0
	case reflect.Float32, reflect.Float64:
		return math.Float64bits(v.Float()) == 0
	case reflect.Complex64, reflect.Complex128:
		c := v.Complex()
		return math.Float64bits(real(c)) == 0 && math.Float64bits(imag(c)) == 0
	case reflect.Array:
		for i := 0; i < v.Len(); i++ {
			if !reflectValueIsZero(v.Index(i)) {
				return false
			}
		}
		return true
	case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer:
		return v.IsNil()
	case reflect.String:
		return v.Len() == 0
	case reflect.Struct:
		for i := 0; i < v.NumField(); i++ {
			if !reflectValueIsZero(v.Field(i)) {
				return false
			}
		}
		return true
	default:
		// This should never happens, but will act as a safeguard for
		// later, as a default value doesn't makes sense here.
		panic(&reflect.ValueError{"reflect.Value.IsZero", v.Kind()})
	}
}
