package eval

import (
	"os"
	"strings"

	"github.com/elves/elvish/pkg/eval/vars"
)

// Resolution and iteration of variables and namespaces.

// EachVariableInTop calls the passed function for each variable name in
// namespace ns that can be found from the top context.
func (ev *evalerScopes) EachVariableInTop(ns string, f func(s string)) {
	switch ns {
	case "builtin:":
		for name := range ev.Builtin {
			f(name)
		}
	case "", ":":
		for name := range ev.Global {
			f(name)
		}
		for name := range ev.Builtin {
			f(name)
		}
	case "e:":
		EachExternal(func(cmd string) {
			f(cmd + FnSuffix)
		})
	case "E:":
		for _, s := range os.Environ() {
			if i := strings.IndexByte(s, '='); i > 0 {
				f(s[:i])
			}
		}
	default:
		segs := SplitQNameNsSegs(ns)
		mod := ev.Global[segs[0]]
		if mod == nil {
			mod = ev.Builtin[segs[0]]
		}
		for _, seg := range segs[1:] {
			if mod == nil {
				return
			}
			mod = mod.Get().(Ns)[seg]
		}
		if mod != nil {
			for name := range mod.Get().(Ns) {
				f(name)
			}
		}
	}
}

// EachNsInTop calls the passed function for each namespace that can be used
// from the top context.
func (ev *evalerScopes) EachNsInTop(f func(s string)) {
	f("builtin:")
	f("e:")
	f("E:")

	for name := range ev.Global {
		if strings.HasSuffix(name, NsSuffix) {
			f(name)
		}
	}
	for name := range ev.Builtin {
		if strings.HasSuffix(name, NsSuffix) {
			f(name)
		}
	}
}

// ResolveVar resolves a variable. When the variable cannot be found, nil is
// returned.
func (fm *Frame) ResolveVar(qname string) vars.Var {
	ns, name := SplitQNameNsFirst(qname)

	switch ns {
	case "E:":
		return vars.FromEnv(name)
	case "e:":
		if strings.HasSuffix(name, FnSuffix) {
			return vars.NewReadOnly(ExternalCmd{name[:len(name)-len(FnSuffix)]})
		}
		return nil
	case "local:":
		return resolveNested(fm.local, name)
	case "up:":
		return resolveNested(fm.up, name)
	case "builtin:":
		return resolveNested(fm.Builtin, name)
	case "", ":":
		return fm.resolveNonPseudo(name)
	default:
		return fm.resolveNonPseudo(qname)
	}
}

func (fm *Frame) resolveNonPseudo(name string) vars.Var {
	if v := resolveNested(fm.local, name); v != nil {
		return v
	}
	if v := resolveNested(fm.up, name); v != nil {
		return v
	}
	return resolveNested(fm.Builtin, name)
}

func resolveNested(ns Ns, name string) vars.Var {
	if name == "" {
		return nil
	}
	segs := SplitQNameNsSegs(name)
	for _, seg := range segs[:len(segs)-1] {
		variable := ns[seg]
		if variable == nil {
			return nil
		}
		nestedNs, ok := variable.Get().(Ns)
		if !ok {
			return nil
		}
		ns = nestedNs
	}
	return ns[segs[len(segs)-1]]
}
