package goneo

import (
	"fmt"
	"reflect"
	"strings"
)

func unmarshal(data map[string]interface{}, out interface{}) error {
	val := reflect.ValueOf(out).Elem()
	valE := val
	valV := val
	switch valE.Kind() {
	case reflect.Ptr, reflect.Interface:
		valE = valE.Elem()
		valV = valV.Elem()
	}

	tmp := reflect.New(valE.Type()).Elem()
	tmp.Set(valV)
	defer val.Set(tmp)

	repr := getTagsRepresentation(out)
	for k, v := range data {
		entry := repr[k]
		k = entry.FieldName
		// Ignored
		if !entry.Enabled {
			continue
		}
		// omitEmpty
		if entry.Attributes[omitEmpty] {
			if isZero(v) {
				continue
			}
		}
		tmp.FieldByName(k).Set(reflect.ValueOf(v))
	}
	return nil
}

func marshal(in interface{}) string {
	var out []string
	val := reflect.ValueOf(in)
	typ := reflect.TypeOf(in)
	switch typ.Kind() {
	case reflect.Slice, reflect.Array:
		panic("only single & pointer types of structs are currently supported")
	case reflect.Ptr, reflect.Interface:
		typ = typ.Elem()
		val = val.Elem()
	}

	repr := getTagsRepresentation(in)
	for i := 0; i < typ.NumField(); i++ {
		fld := typ.Field(i)
		entry := repr[fld.Name]
		fldV := val.FieldByName(fld.Name).Interface()
		// Ignored
		if !entry.Enabled {
			continue
		}
		// omitEmpty
		if entry.Attributes[omitEmpty] {
			if isZero(fldV) {
				continue
			}
		}

		out = append(out, fmt.Sprintf(`%s: "%v"`, entry.Identifier, fldV))
	}
	return fmt.Sprintf("{ %s }", strings.Join(out, ", "))
}

func identifier(i interface{}) string {
	rValue := reflect.TypeOf(i)
	switch rValue.Kind() {
	case reflect.Ptr, reflect.Interface:
		rValue = rValue.Elem()
	}
	return strings.ToLower(rValue.Name())
}

func identifierWithSuffix(i interface{}, suffix string) string {
	return fmt.Sprintf("%s%s", identifier(i), suffix)
}

func marshalToMap(in interface{}) (out map[string]interface{}) {
	out = make(map[string]interface{})
	val := reflect.ValueOf(in)
	switch val.Kind() {
	case reflect.Ptr, reflect.Interface:
		val = val.Elem()
	}

	repr := getTagsRepresentation(in)
	for i := 0; i < val.NumField(); i++ {
		entry := repr[val.Type().Field(i).Name]
		fldV := val.Field(i).Interface()
		// Ignored
		if !entry.Enabled {
			continue
		}
		// omitEmpty
		if entry.Attributes[omitEmpty] {
			if isZero(fldV) {
				continue
			}
		}
		out[entry.Identifier] = fldV
	}
	return
}

func returns(i interface{}) string {
	var out []string
	for k, _ := range marshalToMap(i) {
		out = append(out, fmt.Sprintf("%s.%s", identifier(i), k))
	}
	return strings.Join(out, ", ")
}

func filter(i interface{}) string {
	var attrs []string
	for k, v := range marshalToMap(i) {
		attrs = append(attrs, fmt.Sprintf(`(%s.%s = "%s")`, strings.ToLower(identifier(i)), k, v))
	}
	return strings.Join(attrs, " AND ")
}

func filterWithSuffix(i interface{}, suffix string) string {
	var attrs []string
	for k, v := range marshalToMap(i) {
		attrs = append(attrs, fmt.Sprintf(`(%s.%s = "%s")`, strings.ToLower(identifierWithSuffix(i, suffix)), k, v))
	}
	return strings.Join(attrs, " AND ")
}

func finder(i interface{}) string {
	var attrs []string
	for k, v := range marshalToMap(i) {
		attrs = append(attrs, fmt.Sprintf(`(%s.%s = "%s")`, strings.ToLower(identifier(i)), k, v))
	}
	return strings.Join(attrs, " OR ")
}

func isZero(i interface{}) bool {
	if i == nil {
		return true
	}
	typ := reflect.TypeOf(i)
	switch typ.Kind() {
	case reflect.Ptr, reflect.Interface:
		if reflect.ValueOf(i).IsNil() {
			return true
		}
	}
	val := reflect.ValueOf(&i).Elem()
	if i == nil || val.IsNil() {
		return true
	}

	return asRef(val).IsZero()
}

func asRef(val reflect.Value) reflect.Value {
	switch val.Kind() {
	case reflect.Interface, reflect.Ptr:
		val = val.Elem()
	}
	if val.Kind() == reflect.Interface || val.Kind() == reflect.Ptr {
		return asRef(val.Elem())
	}
	return val
}

func asRefType(typ reflect.Type) reflect.Type {
	switch typ.Kind() {
	case reflect.Interface, reflect.Ptr:
		typ = typ.Elem()
	}
	if typ.Kind() == reflect.Interface || typ.Kind() == reflect.Ptr {
		return asRefType(typ)
	}
	return typ
}