// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.

// utils.go
package expr

import (
	"fmt"
	"os"
	"os/user"
	"path"
	"reflect"
	"strings"
)

func IsString(v any) (ok bool) {
	_, ok = v.(string)
	return ok
}

func IsInteger(v any) (ok bool) {
	_, ok = v.(int64)
	return ok
}

func IsFloat(v any) (ok bool) {
	_, ok = v.(float64)
	return ok
}

func IsBool(v any) (ok bool) {
	_, ok = v.(bool)
	return ok
}

func IsList(v any) (ok bool) {
	_, ok = v.(*ListType)
	return ok
}

func IsDict(v any) (ok bool) {
	_, ok = v.(*DictType)
	return ok
}

func IsFract(v any) (ok bool) {
	_, ok = v.(*FractionType)
	return ok
}

func IsRational(v any) (ok bool) {
	if _, ok = v.(*FractionType); !ok {
		_, ok = v.(int64)
	}
	return ok
}

func IsNumber(v any) (ok bool) {
	return IsFloat(v) || IsInteger(v)
}

func isNumOrFract(v any) (ok bool) {
	return IsFloat(v) || IsInteger(v) || isFraction(v)
}

func isNumberString(v any) (ok bool) {
	return IsString(v) || IsNumber(v)
}

func isFunctor(v any) (ok bool) {
	_, ok = v.(Functor)
	return
}

func isIterator(v any) (ok bool) {
	_, ok = v.(Iterator)
	return
}

func numAsFloat(v any) (f float64) {
	var ok bool
	if f, ok = v.(float64); !ok {
		if fract, ok := v.(*FractionType); ok {
			f = fract.toFloat()
		} else {
			i, _ := v.(int64)
			f = float64(i)
		}
	}
	return
}

func ToBool(v any) (b bool, ok bool) {
	ok = true
	switch x := v.(type) {
	case string:
		b = len(x) > 0
	case float64:
		b = x != 0.0
	case int64:
		b = x != 0
	case bool:
		b = x
	default:
		ok = false
	}
	return
}

func isFunc(v any) bool {
	return reflect.TypeOf(v).Kind() == reflect.Func
}

func anyInteger(v any) (i int64, ok bool) {
	ok = true
	switch intval := v.(type) {
	case int:
		i = int64(intval)
	case uint8:
		i = int64(intval)
	case uint16:
		i = int64(intval)
	case uint64:
		i = int64(intval)
	case uint32:
		i = int64(intval)
	case int8:
		i = int64(intval)
	case int16:
		i = int64(intval)
	case int32:
		i = int64(intval)
	case int64:
		i = intval
	default:
		ok = false
	}
	return
}

func fromGenericAny(v any) (exprAny any, ok bool) {
	if v != nil {
		if exprAny, ok = v.(bool); ok {
			return
		}
		if exprAny, ok = v.(string); ok {
			return
		}
		if exprAny, ok = anyInteger(v); ok {
			return
		}
		if exprAny, ok = anyFloat(v); ok {
			return
		}
		if exprAny, ok = v.(*DictType); ok {
			return
		}
		if exprAny, ok = v.(*ListType); ok {
			return
		}
	}
	return
}

func anyFloat(v any) (float float64, ok bool) {
	ok = true
	switch floatval := v.(type) {
	case float32:
		float = float64(floatval)
	case float64:
		float = floatval
	default:
		ok = false
	}
	return
}

func CopyMap[K comparable, V any](dest, source map[K]V) map[K]V {
	for k, v := range source {
		dest[k] = v
	}
	return dest
}

// func CloneMap[K comparable, V any](source map[K]V) map[K]V {
// 	dest := make(map[K]V, len(source))
// 	return CopyMap(dest, source)
// }

func CopyFilteredMap[K comparable, V any](dest, source map[K]V, filter func(key K) (accept bool)) map[K]V {
	// fmt.Printf("--- Clone with filter %p\n", filter)
	if filter == nil {
		return CopyMap(dest, source)
	} else {
		for k, v := range source {
			if filter(k) {
				// fmt.Printf("\tClone var %q\n", k)
				dest[k] = v
			}
		}
	}
	return dest
}

func CloneFilteredMap[K comparable, V any](source map[K]V, filter func(key K) (accept bool)) map[K]V {
	dest := make(map[K]V, len(source))
	return CopyFilteredMap(dest, source, filter)
}

func ToGoInt(value any, description string) (i int, err error) {
	if valueInt64, ok := value.(int64); ok {
		i = int(valueInt64)
	} else if valueInt, ok := value.(int); ok {
		i = valueInt
	} else {
		err = fmt.Errorf("%s expected integer, got %s (%v)", description, TypeName(value), value)
	}
	return
}

func ForAll[T, V any](ts []T, fn func(T) V) []V {
	result := make([]V, len(ts))
	for i, t := range ts {
		result[i] = fn(t)
	}
	return result
}

func ExpandPath(sourcePath string) (expandedPath string, err error) {
	for expandedPath = os.ExpandEnv(sourcePath); expandedPath != sourcePath; expandedPath = os.ExpandEnv(sourcePath) {
		sourcePath = expandedPath
	}

	if strings.HasPrefix(sourcePath, "~") {
		var home, userName, remainder string

		slashPos := strings.IndexRune(sourcePath, '/')
		if slashPos > 0 {
			userName = sourcePath[1:slashPos]
			remainder = sourcePath[slashPos:]
		} else {
			userName = sourcePath[1:]
		}

		if len(userName) == 0 {
			home, err = os.UserHomeDir()
			if err != nil {
				return
			}
		} else {
			var userInfo *user.User
			userInfo, err = user.Lookup(userName)
			if err != nil {
				return
			}
			home = userInfo.HomeDir
		}
		expandedPath = path.Join(home, remainder)
	}
	return
}