package transformers

import (
	"fmt"
	"sigs.k8s.io/kustomize/pkg/expansion"
	"sigs.k8s.io/kustomize/pkg/resmap"
	"sigs.k8s.io/kustomize/pkg/transformers/config"
)

type RefVarTransformer struct {
	varMap            map[string]string
	replacementCounts map[string]int
	fieldSpecs        []config.FieldSpec
	mappingFunc       func(string) string
}

// NewRefVarTransformer returns a new RefVarTransformer
// that replaces $(VAR) style variables with values.
// The fieldSpecs are the places to look for occurrences of $(VAR).
func NewRefVarTransformer(
	varMap map[string]string, fs []config.FieldSpec) *RefVarTransformer {
	return &RefVarTransformer{
		varMap:     varMap,
		fieldSpecs: fs,
	}
}

// replaceVars accepts as 'in' a string, or string array, which can have
// embedded instances of $VAR style variables, e.g. a container command string.
// The function returns the string with the variables expanded to their final
// values.
func (rv *RefVarTransformer) replaceVars(in interface{}) (interface{}, error) {
	switch vt := in.(type) {
	case []interface{}:
		var xs []string
		for _, a := range in.([]interface{}) {
			xs = append(xs, expansion.Expand(a.(string), rv.mappingFunc))
		}
		return xs, nil
	case map[string]interface{}:
		inMap := in.(map[string]interface{})
		xs := make(map[string]interface{}, len(inMap))
		for k, v := range inMap {
			s, ok := v.(string)
			if !ok {
				return nil, fmt.Errorf("%#v is expected to be %T", v, s)
			}
			xs[k] = expansion.Expand(s, rv.mappingFunc)
		}
		return xs, nil
	case interface{}:
		s, ok := in.(string)
		if !ok {
			return nil, fmt.Errorf("%#v is expected to be %T", in, s)
		}
		return expansion.Expand(s, rv.mappingFunc), nil
	case nil:
		return nil, nil
	default:
		return "", fmt.Errorf("invalid type encountered %T", vt)
	}
}

// UnusedVars returns slice of Var names that were unused
// after a Transform run.
func (rv *RefVarTransformer) UnusedVars() []string {
	var unused []string
	for k := range rv.varMap {
		_, ok := rv.replacementCounts[k]
		if !ok {
			unused = append(unused, k)
		}
	}
	return unused
}

// Transform replaces $(VAR) style variables with values.
func (rv *RefVarTransformer) Transform(m resmap.ResMap) error {
	rv.replacementCounts = make(map[string]int)
	rv.mappingFunc = expansion.MappingFuncFor(
		rv.replacementCounts, rv.varMap)
	for id, res := range m {
		for _, fieldSpec := range rv.fieldSpecs {
			if id.Gvk().IsSelected(&fieldSpec.Gvk) {
				if err := mutateField(
					res.Map(), fieldSpec.PathSlice(),
					false, rv.replaceVars); err != nil {
					return err
				}
			}
		}
	}
	return nil
}