package xvdoc

import (
	"encoding/json"
	"fmt"
	"reflect"
	"strconv"
)

type JSONMap map[string]interface{}

func (s JSONMap) Bool(key string, defaultVal bool) bool {
	if res, check := s[key].(bool); check {
		return res
	}
	return defaultVal
}

func (s JSONMap) Int(key string, defaultVal int64) int64 {
	if iface, check := s[key]; check {
		rVal := reflect.ValueOf(iface)
		switch rVal.Kind() {
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			return rVal.Int()
		case reflect.Float32, reflect.Float64:
			return int64(rVal.Float())
		}
	}
	return defaultVal
}

func (s JSONMap) Value(key string, defaultVal interface{}) interface{} {
	if iface, check := s[key]; check {
		dVal := reflect.ValueOf(defaultVal)
		if !dVal.IsValid() {
			return iface
		} else {
			lVal := reflect.ValueOf(iface)
			if !lVal.IsValid() {
				return defaultVal
			} else if lVal.Kind() == dVal.Kind() {
				return iface
			} else if lVal.Type().ConvertibleTo(dVal.Type()) {
				return lVal.Convert(dVal.Type()).Interface()
			} else {
				if lVal.Kind() == reflect.String {
					switch dVal.Kind() {
					case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
						if val, err := strconv.Atoi(lVal.String()); err != nil {
							return defaultVal
						} else {
							lVal = reflect.ValueOf(val)
							return lVal.Convert(dVal.Type())
						}
					case reflect.Float32, reflect.Float64:
						if val, err := strconv.ParseFloat(lVal.String(), 64); err != nil {
							return defaultVal
						} else {
							lVal = reflect.ValueOf(val)
							return lVal.Convert(dVal.Type())
						}
					}
				}
			}
		}
	}
	return defaultVal
}

func (s JSONMap) StringVal(key, defaultVal string) string {
	if iface, check := s[key]; check {
		return fmt.Sprint(iface)
	}
	return defaultVal
}

func (s JSONMap) Interface(key string) interface{} {
	return s[key]
}

func (s JSONMap) StringArray(key string) (res []string) {
	if arr, check := s[key].([]interface{}); check {
		res = make([]string, len(arr))
		for i, v := range arr {
			res[i] = fmt.Sprint(v)
		}
	}
	return
}

func (s JSONMap) Array(key string) (res []interface{}) {
	if arr, check := s[key].([]interface{}); check {
		res = arr
	}
	return
}

func (s JSONMap) JSONMap(key string) (res JSONMap) {
	if m, check := s[key].(map[string]interface{}); check {
		res = JSONMap(m)
	}
	return
}

func (s JSONMap) KeyExists(key string) (check bool) {
	_, check = s[key]
	return check
}

func (s JSONMap) Copy() JSONMap {
	res := make(JSONMap)
	for key, val := range s {
		res[key] = val
	}
	return res
}

func (s JSONMap) JSON() (res []byte) {
	res, _ = json.Marshal(&s)
	return
}

func (s JSONMap) Map() map[string]interface{} {
	return map[string]interface{}(s)
}

func copyMap(m map[string]interface{}) (res map[string]interface{}) {
	res = make(map[string]interface{})
	for key, val := range m {
		switch val.(type) {
		case []interface{}:
			res[key] = copySlice(val.([]interface{}))
		case map[string]interface{}:
			res[key] = copyMap(val.(map[string]interface{}))
		default:
			res[key] = val
		}
	}
	return
}

func copySlice(arr []interface{}) (res []interface{}) {
	res = make([]interface{}, len(arr))
	for i, v := range arr {
		switch v.(type) {
		case []interface{}:
			res[i] = copySlice(v.([]interface{}))
		case map[string]interface{}:
			res[i] = copyMap(v.(map[string]interface{}))
		default:
			res[i] = v
		}
	}
	return
}

func mergeSlices(aSource, aMerge []interface{}) []interface{} {
	return append(aSource, aMerge...)
}

func mergeMaps(mSource, mMerge map[string]interface{}) (res map[string]interface{}) {
	res = copyMap(mSource)

	// merge result with result map
	for key, val := range mMerge {
		if sVal, check := res[key]; check {
			switch val.(type) {
			case map[string]interface{}:
				{
					if m, check := sVal.(map[string]interface{}); check {
						res[key] = mergeMaps(m, val.(map[string]interface{}))
					} else if val == nil {
						res[key] = val
					}
				}
			case []interface{}:
				{
					if arr, check := sVal.([]interface{}); check {
						res[key] = mergeSlices(arr, val.([]interface{}))
					} else if val == nil {
						res[key] = val
					}
				}
			default:
				res[key] = val
			}
		} else {
			switch val.(type) {
			case map[string]interface{}:
				res[key] = copyMap(val.(map[string]interface{}))
			case []interface{}:
				res[key] = copySlice(val.([]interface{}))
			default:
				res[key] = val
			}
		}
	}
	return
}

func mapJSON(m map[string]interface{}) (res []byte) {
	res, _ = json.Marshal(&m)
	return
}