package JsonSerialization

import (
	"math"
	"reflect"
)

// isIntegerType returns whether the type is an integer and if it's unsigned.
// See: https://github.com/Kangaroux/go-map-schema/blob/master/schema.go#L328
func isIntegerType(t reflect.Type) (yes bool, unsigned bool) {
	switch t.Kind() {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		yes = true
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		yes = true
		unsigned = true
	}

	return
}

// isFloatType returns true if the type is a floating point. Note that this doesn't
// care about the value -- unmarshaling the number "0" gives a float, not an int.
// See: https://github.com/Kangaroux/go-map-schema/blob/master/schema.go#L319
func isFloatType(t reflect.Type) (yes bool) {
	switch t.Kind() {
	case reflect.Float32, reflect.Float64:
		yes = true
	}

	return
}

// CanConvert returns whether value v is convertible to type t.
//
// If t is a pointer and v is not nil, it checks if v is convertible to the type that
// t points to.
// Modified due to not handling slices (DefaultCanConvert fails on PhotoUrls and Tags)
// See: https://github.com/Kangaroux/go-map-schema/blob/master/schema.go#L191
func CanConvert(t reflect.Type, v reflect.Value) bool {
	isPtr := t.Kind() == reflect.Ptr
	isStruct := t.Kind() == reflect.Struct
	dstType := t

	// Check if v is a nil value.
	if !v.IsValid() || (v.CanAddr() && v.IsNil()) {
		return isPtr
	}

	// If the dst is a pointer, check if we can convert to the type it's pointing to.
	if isPtr {
		dstType = t.Elem()
		isStruct = t.Elem().Kind() == reflect.Struct
	}

	// If the dst is a struct, we should check its nested fields.
	if isStruct {
		return v.Kind() == reflect.Map
	}

	if t.Kind() == reflect.Slice {
		return v.Kind() == reflect.Slice
	}

	if !v.Type().ConvertibleTo(dstType) {
		return false
	}

	// Handle converting to an integer type.
	if dstInt, unsigned := isIntegerType(dstType); dstInt {
		if isFloatType(v.Type()) {
			f := v.Float()

			if math.Trunc(f) != f {
				return false
			} else if unsigned && f < 0 {
				return false
			}
		} else if srcInt, _ := isIntegerType(v.Type()); srcInt {
			if unsigned && v.Int() < 0 {
				return false
			}
		}
	}

	return true
}
