package reflection

import "reflect"

// Go allows for `interface{}` which we can think as as just
// any type. (any is just an alias for interface{}) this allows for
// some metaprogramming features in the language reflection being one
// of them. Reflection allows us to examine structure at runtime.

// The downside, you lose type safety and performance impact.

// Only use reflection if you really need to.

// See also https://blog.golang.org/laws-of-reflection

func walk(x interface{}, fn func(input string)) {
	// This code is very unsafe and very naive,
	val := getValue(x)

	// if val.Kind() == reflect.Slice {
	// 	for i := 0; i < val.Len(); i++ {
	// 		walk(val.Index(i).Interface(), fn)
	// 	}
	// 	return
	// }

	// // This would panic if there were no fields
	// field := val.Field(0)
	// // This would be wrong if the field had any value other than a string
	// fn(field.String())

	// // change for test table ( will fail case 2)
	// for i := 0; i < val.NumField(); i++ {
	// 	field := val.Field(i)
	// 	fn(field.String())
	// }

	// THIS GOT messy
	// for i := 0; i < val.NumField(); i++ {
	// 	field := val.Field(i)

	// 	// if field.Kind() == reflect.String {
	// 	// 	fn(field.String())
	// 	// }

	// 	// if field.Kind() == reflect.Struct {
	// 	// 	// it got recursive
	// 	// 	walk(field.Interface(), fn)
	// 	// }

	// 	switch field.Kind() {
	// 	case reflect.String:
	// 		fn(field.String())
	// 	case reflect.Struct:
	// 		walk(field.Interface(), fn)
	// 	}
	// }

	switch val.Kind() {
	case reflect.Struct:
		for i := 0; i < val.NumField(); i++ {
			walk(val.Field(i).Interface(), fn)
		}
	case reflect.Slice, reflect.Array:
		for i := 0; i < val.Len(); i++ {
			walk(val.Index(i).Interface(), fn)
		}
	case reflect.Map:
		for _, key := range val.MapKeys() {
			walk(val.MapIndex(key).Interface(), fn)
		}

	case reflect.Chan:
		for {
			if v, ok := val.Recv(); ok {
				walk(v.Interface(), fn)
			} else {
				break
			}
		}
	case reflect.Func:
		valFnResult := val.Call(nil)
		for _, res := range valFnResult {
			walk(res.Interface(), fn)
		}
	case reflect.String:
		fn(val.String())
	}
}

func getValue(x interface{}) reflect.Value {
	val := reflect.ValueOf(x)

	if val.Kind() == reflect.Pointer {
		val = val.Elem()
	}

	return val
}