package ltsv

import (
	"encoding"
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

// UnmarshalError is an error type for Unmarshal()
type UnmarshalError map[string]error

func (m UnmarshalError) Error() string {
	if len(m) == 0 {
		return "(no error)"
	}

	ee := make([]string, 0, len(m))
	for name, err := range m {
		ee = append(ee, fmt.Sprintf("field %q: %s", name, err))
	}

	return strings.Join(ee, "\n")
}

// OfField returns the error correspoinding to a given field
func (m UnmarshalError) OfField(name string) error {
	return m[name]
}

// An UnmarshalTypeError describes a LTSV value that was
// not appropriate for a value of a specific Go type.
type UnmarshalTypeError struct {
	Value string
	Type  reflect.Type
}

func (e *UnmarshalTypeError) Error() string {
	return "ltsv: cannot unmarshal " + e.Value + " into Go value of type " + e.Type.String()
}

type ltsvMap map[string]string

func data2map(data []byte) (ltsvMap, error) {
	d := string(data)
	fields := strings.Split(d, "\t")
	l := ltsvMap{}
	for _, v := range fields {
		kv := strings.SplitN(strings.TrimSpace(v), ":", 2)
		if len(kv) != 2 {
			return nil, fmt.Errorf("not a ltsv: %s", d)
		}
		l[strings.TrimSpace(kv[0])] = strings.TrimSpace(kv[1])
	}
	return l, nil
}

// Unmarshal parses the LTSV-encoded data and stores the result
// in the value pointed to by v.
func Unmarshal(data []byte, v interface{}) error {
	rv := reflect.ValueOf(v)
	if rv.Kind() != reflect.Ptr {
		return fmt.Errorf("not a pointer: %v", v)
	}

	rv = rv.Elem()
	if rv.Kind() != reflect.Struct && rv.Kind() != reflect.Map {
		return fmt.Errorf("not a pointer to a struct/map: %v", v)
	}

	l, err := data2map(data)
	if err != nil {
		return err
	}
	if rv.Kind() == reflect.Map {
		kt := rv.Type().Key()
		vt := rv.Type().Elem()
		if kt.Kind() != reflect.String || vt.Kind() != reflect.String {
			return fmt.Errorf("not a map[string]string")
		}
		for k, v := range l {
			kv := reflect.ValueOf(k).Convert(kt)
			vv := reflect.ValueOf(v).Convert(vt)
			rv.SetMapIndex(kv, vv)
		}
		return nil
	}

	t := rv.Type()
	errs := UnmarshalError{}
	for i := 0; i < t.NumField(); i++ {
		ft := t.Field(i)
		fv := rv.Field(i)

		tag := ft.Tag.Get("ltsv")
		tags := strings.Split(tag, ",")
		key := tags[0]
		if key == "-" {
			continue
		}
		if key == "" {
			key = strings.ToLower(ft.Name)
		}
		s, ok := l[key]
		if !ok {
			continue
		}
		potentiallyNull := s == "-" || s == ""
		if fv.Kind() == reflect.Ptr {
			if fv.IsNil() {
				if potentiallyNull {
					switch fv.Type().Elem().Kind() {
					case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
						continue
					}
				}
				fv.Set(reflect.New(fv.Type().Elem()))
			}
			fv = fv.Elem()
		}
		if !fv.CanSet() {
			continue
		}

		switch fv.Kind() {
		case reflect.String:
			fv.SetString(s)
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			if potentiallyNull {
				continue
			}
			i, err := strconv.ParseInt(s, 10, 64)
			if err != nil || fv.OverflowInt(i) {
				errs[ft.Name] = &UnmarshalTypeError{"number " + s, fv.Type()}
				continue
			}
			fv.SetInt(i)
		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
			if potentiallyNull {
				continue
			}
			i, err := strconv.ParseUint(s, 10, 64)
			if err != nil || fv.OverflowUint(i) {
				errs[ft.Name] = &UnmarshalTypeError{"number " + s, fv.Type()}
				continue
			}
			fv.SetUint(i)
		case reflect.Float32, reflect.Float64:
			if potentiallyNull {
				continue
			}
			n, err := strconv.ParseFloat(s, fv.Type().Bits())
			if err != nil || fv.OverflowFloat(n) {
				errs[ft.Name] = &UnmarshalTypeError{"number " + s, fv.Type()}
				continue
			}
			fv.SetFloat(n)
		default:
			u := indirect(fv)
			if u == nil {
				errs[ft.Name] = &UnmarshalTypeError{s, fv.Type()}
			} else {
				err := u.UnmarshalText([]byte(s))
				if err != nil {
					errs[ft.Name] = err
				}
			}
		}
	}

	if len(errs) < 1 {
		return nil
	}
	return errs
}

func indirect(v reflect.Value) encoding.TextUnmarshaler {
	if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() {
		v = v.Addr()
	}
	for {
		if v.Kind() == reflect.Interface && !v.IsNil() {
			e := v.Elem()
			if e.Kind() == reflect.Ptr && !e.IsNil() && e.Elem().Kind() == reflect.Ptr {
				v = e
				continue
			}
		}
		if v.Kind() != reflect.Ptr {
			break
		}
		if v.Elem().Kind() != reflect.Ptr && v.CanSet() {
			break
		}
		if v.IsNil() {
			v.Set(reflect.New(v.Type().Elem()))
		}
		if v.Type().NumMethod() > 0 {
			if u, ok := v.Interface().(encoding.TextUnmarshaler); ok {
				return u
			}
		}
	}
	return nil
}