package gou

import (
	"encoding/json"
	"fmt"
	"math"
	"strconv"
	"strings"
)

// Coerce types (string,int,int64, float, []byte) into String type
func CoerceString(v interface{}) (string, error) {
	switch val := v.(type) {
	case string:
		if val == "null" || val == "NULL" {
			return "", nil
		}
		return val, nil
	case int:
		return strconv.Itoa(val), nil
	case int32:
		return strconv.FormatInt(int64(val), 10), nil
	case int64:
		return strconv.FormatInt(val, 10), nil
	case uint32:
		return strconv.FormatUint(uint64(val), 10), nil
	case uint64:
		return strconv.FormatUint(val, 10), nil
	case float32:
		return strconv.FormatFloat(float64(val), 'f', -1, 32), nil
	case float64:
		return strconv.FormatFloat(val, 'f', -1, 64), nil
	case bool:
		if val {
			return "true", nil
		}
		return "false", nil
	case []byte:
		if string(val) == "null" || string(val) == "NULL" {
			return "", nil
		}
		return string(val), nil
	case json.RawMessage:
		if string(val) == "null" || string(val) == "NULL" {
			return "", nil
		}
		return string(val), nil
	}
	return "", fmt.Errorf("Could not coerce to string: %v", v)
}

// Coerce type to string, returning zero length string if error or nil
func CoerceStringShort(v interface{}) string {
	val, _ := CoerceString(v)
	return val
}

// CoerceStrings Coerce type to strings, will split on comma by default.
func CoerceStrings(v interface{}) []string {
	switch val := v.(type) {
	case string:
		if val == "" {
			return nil
		}
		return strings.Split(val, ",")
	case []string:
		return val
	case []interface{}:
		sva := make([]string, 0)
		for _, av := range val {
			switch aval := av.(type) {
			case string:
				sva = append(sva, aval)
			default:
				sv, err := CoerceString(av)
				if err == nil && sv != "" {
					sva = append(sva, sv)
				}
			}
		}
		return sva
	}
	return []string{CoerceStringShort(v)}
}

func CoerceFloats(v interface{}) []float64 {
	switch val := v.(type) {
	case float64:
		return []float64{val}
	case []string:
		fa := make([]float64, 0)
		for _, av := range val {
			f, err := CoerceFloat(av)
			if err == nil && !math.IsNaN(f) {
				fa = append(fa, f)
			}
		}
		return fa
	case []interface{}:
		fa := make([]float64, 0)
		for _, av := range val {
			switch aval := av.(type) {
			case float64:
				fa = append(fa, aval)
			default:
				f, err := CoerceFloat(av)
				if err == nil && !math.IsNaN(f) {
					fa = append(fa, f)
				}
			}
		}
		return fa
	}
	fv, err := CoerceFloat(v)
	if err == nil {
		return []float64{fv}
	}
	return nil
}

func CoerceFloat(v interface{}) (float64, error) {
	switch val := v.(type) {
	case int:
		return float64(val), nil
	case int32:
		return float64(val), nil
	case int64:
		return float64(val), nil
	case uint32:
		return float64(val), nil
	case uint64:
		return float64(val), nil
	case float64:
		return val, nil
	case string:
		if len(val) > 0 {
			if iv, err := strconv.ParseFloat(val, 64); err == nil {
				return iv, nil
			}
		}
	case []byte:
		if len(val) > 0 {
			if iv, err := strconv.ParseFloat(string(val), 64); err == nil {
				return iv, nil
			}
		}
	case json.RawMessage:
		if len(val) > 0 {
			if iv, err := strconv.ParseFloat(string(val), 64); err == nil {
				return iv, nil
			}
		}
	case nil:
		return math.NaN(), nil
	}
	return 0, fmt.Errorf("Could not Coerce Value: %v", v)
}
func CoerceFloatShort(v interface{}) float64 {
	val, _ := CoerceFloat(v)
	return val
}

func CoerceInt64(v interface{}) (int64, error) {
	val, ok := valToInt64(v)
	if ok {
		return val, nil
	}
	return 0, fmt.Errorf("Could not coerce to int64: %v", v)
}
func CoerceInt64Short(v interface{}) int64 {
	val, ok := valToInt64(v)
	if ok {
		return val
	}
	return 0
}

func CoerceInt(v interface{}) (int, error) {
	val, ok := valToInt(v)
	if ok {
		return val, nil
	}
	return 0, fmt.Errorf("Could not coerce to int64: %v", v)
}
func CoerceIntShort(v interface{}) int {
	val, ok := valToInt(v)
	if ok {
		return val
	}
	return 0
}

func CoerceInts(v interface{}) []int {
	switch val := v.(type) {
	case []string:
		iva := make([]int, 0)
		for _, av := range val {
			avAsInt, ok := valToInt(av)
			if ok {
				iva = append(iva, avAsInt)
			}
		}
		return iva
	case []interface{}:
		iva := make([]int, 0)
		for _, av := range val {
			avAsInt, ok := valToInt(av)
			if ok {
				iva = append(iva, avAsInt)
			}
		}
		return iva
	}
	return []int{CoerceIntShort(v)}
}

// Coerce a val(interface{}) into a Uint64
func CoerceUint(v interface{}) (uint64, error) {
	u64, ok := valToUint64(v)
	if !ok {
		return 0, fmt.Errorf("Could not Coerce %v", v)
	}
	return u64, nil
}

// Coerce a Val(interface{}) into Uint64
func CoerceUintShort(v interface{}) uint64 {
	val, _ := CoerceUint(v)
	return val
}

// Given any numeric type (float*, int*, uint*, string) return an int. Returns false if it would
// overflow or if the the argument is not numeric.
func valToInt(i interface{}) (int, bool) {
	i64, ok := valToInt64(i)
	if !ok {
		return -1, false
	}
	if i64 > MaxInt || i64 < MinInt {
		return -1, false
	}
	return int(i64), true
}

// Given any simple type (float*, int*, uint*, string, []byte, json.RawMessage) return an int64.
// Returns false if it would overflow or if the the argument is not numeric.
func valToInt64(i interface{}) (int64, bool) {
	switch x := i.(type) {
	case float32:
		return int64(x), true
	case float64:
		return int64(x), true
	case uint8:
		return int64(x), true
	case uint16:
		return int64(x), true
	case uint32:
		return int64(x), true
	case uint64:
		if x > math.MaxInt64 {
			return 0, false
		}
		return int64(x), true
	case int8:
		return int64(x), true
	case int16:
		return int64(x), true
	case int32:
		return int64(x), true
	case int64:
		return int64(x), true
	case int:
		return int64(x), true
	case uint:
		if uint64(x) > math.MaxInt64 {
			return 0, false
		}
		return int64(x), true
	case string:
		if len(x) > 0 {
			if iv, err := strconv.ParseInt(x, 10, 64); err == nil {
				return iv, true
			}
			if iv, err := strconv.ParseFloat(x, 64); err == nil {
				return valToInt64(iv)
			}
		}
	case []byte:
		if len(x) > 0 {
			if iv, err := strconv.ParseInt(string(x), 10, 64); err == nil {
				return iv, true
			}
			if iv, err := strconv.ParseFloat(string(x), 64); err == nil {
				return valToInt64(iv)
			}
		}
	case json.RawMessage:
		if len(x) > 0 {
			if iv, err := strconv.ParseInt(string(x), 10, 64); err == nil {
				return iv, true
			}
			if iv, err := strconv.ParseFloat(string(x), 64); err == nil {
				return valToInt64(iv)
			}
		}
	}
	return 0, false
}

// Given any simple type (float*, int*, uint*, string, []byte, json.RawMessage) return an int64.
// Returns false if it would overflow or if the the argument is not numeric.
func valToUint64(i interface{}) (uint64, bool) {
	switch x := i.(type) {
	case float32:
		return uint64(x), true
	case float64:
		return uint64(x), true
	case uint8:
		return uint64(x), true
	case uint16:
		return uint64(x), true
	case uint32:
		return uint64(x), true
	case uint64:
		return x, true
	case int8:
		return uint64(x), true
	case int16:
		return uint64(x), true
	case int32:
		return uint64(x), true
	case int64:
		return uint64(x), true
	case int:
		return uint64(x), true
	case uint:
		return uint64(x), true
	case string:
		if len(x) > 0 {
			if uiv, err := strconv.ParseUint(x, 10, 64); err == nil {
				return uiv, true
			}
			if fv, err := strconv.ParseFloat(x, 64); err == nil {
				return uint64(fv), true
			}
		}
	case []byte:
		if len(x) > 0 {
			if uiv, err := strconv.ParseUint(string(x), 10, 64); err == nil {
				return uiv, true
			}
			if fv, err := strconv.ParseFloat(string(x), 64); err == nil {
				return uint64(fv), true
			}
		}
	case json.RawMessage:
		if len(x) > 0 {
			if uiv, err := strconv.ParseUint(string(x), 10, 64); err == nil {
				return uiv, true
			}
			if fv, err := strconv.ParseFloat(string(x), 64); err == nil {
				return uint64(fv), true
			}
		}
	}
	return 0, false
}
