package cli

import (
	"errors"
	"fmt"
	"git.nspix.com/golang/micro/utils/console"
	"reflect"
	"sort"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

var (
	_seq int64

	ErrInvalidHandle         = errors.New("invalid handle kind")
	ErrInvalidHandleResponse = errors.New("invalid handle response")

	errInterface    = reflect.TypeOf((*error)(nil)).Elem()
	stringSliceKind = reflect.TypeOf([]string{}).Kind()
)

type Executor struct {
	name           string
	parent         *Executor
	usage          string
	description    string
	handle         interface{}
	children       map[string]*Executor
	createdTime    time.Time
	seq            int64
	locker         sync.RWMutex
	NotFoundHandle func(ctx *Context, cmd string) ([]byte, error)
}

type (
	executors []*Executor
)

func (p executors) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
func (p executors) Len() int           { return len(p) }
func (p executors) Less(i, j int) bool { return p[i].seq < p[j].seq }

func (exec *Executor) hasChildren(name string) bool {
	exec.locker.RLock()
	defer exec.locker.RUnlock()
	if name == "" {
		return len(exec.children) > 0
	}
	if _, ok := exec.children[name]; ok {
		return true
	}
	return false
}

func (exec *Executor) getDescription() string {
	if exec.description != "" {
		return exec.description
	}
	var p *Executor
	var text string
	p = exec
	for {
		if p.parent == nil {
			break
		}
		text = p.name + " " + text
		p = p.parent
	}
	return text
}

func (exec *Executor) Children(name string) (*Executor, error) {
	exec.locker.RLock()
	defer exec.locker.RUnlock()
	if cmd, ok := exec.children[name]; ok {
		return cmd, nil
	}
	return nil, errors.New("children not found")
}

func (exec *Executor) Completer(tokens ...string) []string {
	exec.locker.RLock()
	defer exec.locker.RUnlock()
	p := exec.children
	length := len(tokens)
	var token, prefix string
	result := make([]string, 0)
	if length > 0 {
		for i := 0; i < length; i++ {
			token = tokens[i]
			if i == length-1 {
				for k, e := range p {
					if token == k {
						if e.children != nil {
							for kk, _ := range e.children {
								result = append(result, prefix+token+" "+kk)
							}
						}
					} else if strings.HasPrefix(k, token) {
						result = append(result, prefix+k)
					}
				}
			} else {
				if next, ok := p[token]; !ok || next.children == nil {
					return []string{}
				} else {
					p = next.children
				}
			}
			prefix += token + " "
		}
	} else {
		for k, _ := range p {
			result = append(result, k)
		}
	}
	return result
}

func (exec *Executor) String() string {
	values := make([][]interface{}, 0)
	var loop func(string, *Executor)
	loop = func(prefix string, e *Executor) {
		if e.children == nil {
			return
		}
		vs := make(executors, len(e.children))
		var i int
		for _, v := range e.children {
			vs[i] = v
			i++
		}
		sort.Sort(vs)
		for _, v := range vs {
			if prefix == "" {
				values = append(values, []interface{}{v.name, v.getDescription()})
			} else {
				values = append(values, []interface{}{prefix + " " + v.name, v.getDescription()})
			}
			if prefix == "" {
				loop(v.name, v)
			} else {
				loop(prefix+" "+v.name, v)
			}
		}
	}
	loop("", exec)
	return string(console.Pretty(values, nil))
}

func (exec *Executor) Append(child ...*Executor) *Executor {
	exec.locker.Lock()
	defer exec.locker.Unlock()
	for _, v := range child {
		v.parent = exec
		exec.children[v.name] = v
	}
	return exec
}

func (exec *Executor) WithHandle(v interface{}) *Executor  {
	exec.handle = v
	return exec
}

func (exec *Executor) Do(ctx *Context, args ...string) (b []byte, err error) {
	var (
		root interface{}
	)
	if root = ctx.Get("ROOT"); root == nil {
		ctx.Set("ROOT", exec)
	}
	ctx.Set("arguments", args)
	if len(args) > 0 && exec.hasChildren(args[0]) {
		if len(args) > 1 {
			return exec.children[args[0]].Do(ctx, args[1:]...)
		} else {
			return exec.children[args[0]].Do(ctx)
		}
	}
	if exec.handle == nil {
		if exec.NotFoundHandle != nil {
			b, err = exec.NotFoundHandle(ctx, ctx.CmdStr)
		} else {
			err = fmt.Errorf("%s not found", ctx.CmdStr)
		}
		return
	}
	refVal := reflect.ValueOf(exec.handle)
	refType := refVal.Type()
	if refType.Kind() != reflect.Func {
		err = ErrInvalidHandle
		return
	}
	//checking args
	if refType.NumIn() > 0 && len(args)+1 < refType.NumIn() {
		var usage string
		if exec.usage == "" {
			usage = "Usage: " + ctx.CmdStr + " ["
			for i := 1; i < refType.NumIn(); i++ {
				usage += " " + refType.In(i).String()
			}
			usage += " ]"
		} else {
			usage = "Usage: " + exec.usage
		}
		err = errors.New(usage)
		return
	}
	arguments := make([]reflect.Value, refType.NumIn())
	for i := 0; i < refType.NumIn(); i++ {
		if i == 0 {
			arguments[i] = reflect.ValueOf(ctx)
			continue
		}
		switch refType.In(i).Kind() {
		case reflect.String:
			arguments[i] = reflect.ValueOf(args[i-1])
		case reflect.Int:
			n, _ := strconv.ParseInt(args[i-1], 10, 64)
			arguments[i] = reflect.ValueOf(int(n))
		case reflect.Int32:
			n, _ := strconv.ParseInt(args[i-1], 10, 32)
			arguments[i] = reflect.ValueOf(int32(n))
		case reflect.Int64:
			n, _ := strconv.ParseInt(args[i-1], 10, 64)
			arguments[i] = reflect.ValueOf(n)
		case reflect.Float32:
			n, _ := strconv.ParseFloat(args[i-1], 32)
			arguments[i] = reflect.ValueOf(float32(n))
		case reflect.Float64:
			n, _ := strconv.ParseFloat(args[i-1], 64)
			arguments[i] = reflect.ValueOf(n)
		case stringSliceKind:
			arguments[i] = reflect.ValueOf(args[i-1:])
		default:
			err = fmt.Errorf("unsupported argument %d kind %s", i-1, refType.In(i).Kind().String())
			return
		}
	}
	values := refVal.Call(arguments)
	for _, v := range values {
		if v.Type().Implements(errInterface) {
			err = v.Interface().(error)
		} else if v.Kind() == reflect.Slice {
			b = v.Bytes()
		}
	}
	return
}

func NewExecutor(args ...string) *Executor {
	var (
		name        string
		usage       string
		description string
	)
	if len(args) > 0 {
		name = args[0]
	}
	if len(args) > 1 {
		usage = args[1]
	}
	if len(args) > 2 {
		description = args[2]
	}
	return &Executor{
		name:        name,
		usage:       usage,
		createdTime: time.Now(),
		description: description,
		children:    make(map[string]*Executor),
		seq:         atomic.AddInt64(&_seq, 1),
	}
}
