package structs

import (
	"fmt"
	"reflect"
	"testing"
	"time"
)

func TestMapNonStruct(t *testing.T) {
	foo := []string{"foo"}

	defer func() {
		err := recover()
		if err == nil {
			t.Error("Passing a non struct into Map should panic")
		}
	}()

	// this should panic. We are going to recover and and test it
	_ = Map(foo)
}

func TestStructIndexes(t *testing.T) {
	type C struct {
		something int
		Props     map[string]interface{}
	}

	defer func() {
		err := recover()
		if err != nil {
			fmt.Printf("err %+v\n", err)
			t.Error("Using mixed indexes should not panic")
		}
	}()

	// They should not panic
	_ = Map(&C{})
	_ = Fields(&C{})
	_ = Values(&C{})
	_ = IsZero(&C{})
	_ = HasZero(&C{})
}

func TestMap(t *testing.T) {
	var T = struct {
		A string
		B int
		C bool
	}{
		A: "a-value",
		B: 2,
		C: true,
	}

	a := Map(T)

	if typ := reflect.TypeOf(a).Kind(); typ != reflect.Map {
		t.Errorf("Map should return a map type, got: %v", typ)
	}

	// we have three fields
	if len(a) != 3 {
		t.Errorf("Map should return a map of len 3, got: %d", len(a))
	}

	inMap := func(val interface{}) bool {
		for _, v := range a {
			if reflect.DeepEqual(v, val) {
				return true
			}
		}

		return false
	}

	for _, val := range []interface{}{"a-value", 2, true} {
		if !inMap(val) {
			t.Errorf("Map should have the value %v", val)
		}
	}

}

func TestMap_Tag(t *testing.T) {
	var T = struct {
		A string `structs:"x"`
		B int    `structs:"y"`
		C bool   `structs:"z"`
	}{
		A: "a-value",
		B: 2,
		C: true,
	}

	a := Map(T)

	inMap := func(key interface{}) bool {
		for k := range a {
			if reflect.DeepEqual(k, key) {
				return true
			}
		}
		return false
	}

	for _, key := range []string{"x", "y", "z"} {
		if !inMap(key) {
			t.Errorf("Map should have the key %v", key)
		}
	}

}

func TestMap_CustomTag(t *testing.T) {
	var T = struct {
		A string `json:"x"`
		B int    `json:"y"`
		C bool   `json:"z"`
		D struct {
			E string `json:"jkl"`
		} `json:"nested"`
	}{
		A: "a-value",
		B: 2,
		C: true,
	}
	T.D.E = "e-value"

	s := New(T)
	s.TagName = "json"

	a := s.Map()

	inMap := func(key interface{}) bool {
		for k := range a {
			if reflect.DeepEqual(k, key) {
				return true
			}
		}
		return false
	}

	for _, key := range []string{"x", "y", "z"} {
		if !inMap(key) {
			t.Errorf("Map should have the key %v", key)
		}
	}

	nested, ok := a["nested"].(map[string]interface{})
	if !ok {
		t.Fatalf("Map should contain the D field that is tagged as 'nested'")
	}

	e, ok := nested["jkl"].(string)
	if !ok {
		t.Fatalf("Map should contain the D.E field that is tagged as 'jkl'")
	}

	if e != "e-value" {
		t.Errorf("D.E field should be equal to 'e-value', got: '%v'", e)
	}

}

func TestMap_MultipleCustomTag(t *testing.T) {
	var A = struct {
		X string `aa:"ax"`
	}{"a_value"}

	aStruct := New(A)
	aStruct.TagName = "aa"

	var B = struct {
		X string `bb:"bx"`
	}{"b_value"}

	bStruct := New(B)
	bStruct.TagName = "bb"

	a, b := aStruct.Map(), bStruct.Map()
	if !reflect.DeepEqual(a, map[string]interface{}{"ax": "a_value"}) {
		t.Error("Map should have field ax with value a_value")
	}

	if !reflect.DeepEqual(b, map[string]interface{}{"bx": "b_value"}) {
		t.Error("Map should have field bx with value b_value")
	}
}

func TestMap_OmitEmpty(t *testing.T) {
	type A struct {
		Name  string
		Value string    `structs:",omitempty"`
		Time  time.Time `structs:",omitempty"`
	}
	a := A{}

	m := Map(a)

	_, ok := m["Value"].(map[string]interface{})
	if ok {
		t.Error("Map should not contain the Value field that is tagged as omitempty")
	}

	_, ok = m["Time"].(map[string]interface{})
	if ok {
		t.Error("Map should not contain the Time field that is tagged as omitempty")
	}
}

func TestMap_OmitNested(t *testing.T) {
	type A struct {
		Name  string
		Value string
		Time  time.Time `structs:",omitnested"`
	}
	a := A{Time: time.Now()}

	type B struct {
		Desc string
		A    A
	}
	b := &B{A: a}

	m := Map(b)

	in, ok := m["A"].(map[string]interface{})
	if !ok {
		t.Error("Map nested structs is not available in the map")
	}

	// should not happen
	if _, ok := in["Time"].(map[string]interface{}); ok {
		t.Error("Map nested struct should omit recursiving parsing of Time")
	}

	if _, ok := in["Time"].(time.Time); !ok {
		t.Error("Map nested struct should stop parsing of Time at is current value")
	}
}

func TestMap_Nested(t *testing.T) {
	type A struct {
		Name string
	}
	a := &A{Name: "example"}

	type B struct {
		A *A
	}
	b := &B{A: a}

	m := Map(b)

	if typ := reflect.TypeOf(m).Kind(); typ != reflect.Map {
		t.Errorf("Map should return a map type, got: %v", typ)
	}

	in, ok := m["A"].(map[string]interface{})
	if !ok {
		t.Error("Map nested structs is not available in the map")
	}

	if name := in["Name"].(string); name != "example" {
		t.Errorf("Map nested struct's name field should give example, got: %s", name)
	}
}

func TestMap_NestedMapWithStructValues(t *testing.T) {
	type A struct {
		Name string
	}

	type B struct {
		A map[string]*A
	}

	a := &A{Name: "example"}

	b := &B{
		A: map[string]*A{
			"example_key": a,
		},
	}

	m := Map(b)

	if typ := reflect.TypeOf(m).Kind(); typ != reflect.Map {
		t.Errorf("Map should return a map type, got: %v", typ)
	}

	in, ok := m["A"].(map[string]interface{})
	if !ok {
		t.Errorf("Nested type of map should be of type map[string]interface{}, have %T", m["A"])
	}

	example := in["example_key"].(map[string]interface{})
	if name := example["Name"].(string); name != "example" {
		t.Errorf("Map nested struct's name field should give example, got: %s", name)
	}
}

func TestMap_NestedMapWithStringValues(t *testing.T) {
	type B struct {
		Foo map[string]string
	}

	type A struct {
		B *B
	}

	b := &B{
		Foo: map[string]string{
			"example_key": "example",
		},
	}

	a := &A{B: b}

	m := Map(a)

	if typ := reflect.TypeOf(m).Kind(); typ != reflect.Map {
		t.Errorf("Map should return a map type, got: %v", typ)
	}

	in, ok := m["B"].(map[string]interface{})
	if !ok {
		t.Errorf("Nested type of map should be of type map[string]interface{}, have %T", m["B"])
	}

	foo := in["Foo"].(map[string]string)
	if name := foo["example_key"]; name != "example" {
		t.Errorf("Map nested struct's name field should give example, got: %s", name)
	}
}
func TestMap_NestedMapWithInterfaceValues(t *testing.T) {
	type B struct {
		Foo map[string]interface{}
	}

	type A struct {
		B *B
	}

	b := &B{
		Foo: map[string]interface{}{
			"example_key": "example",
		},
	}

	a := &A{B: b}

	m := Map(a)

	if typ := reflect.TypeOf(m).Kind(); typ != reflect.Map {
		t.Errorf("Map should return a map type, got: %v", typ)
	}

	in, ok := m["B"].(map[string]interface{})
	if !ok {
		t.Errorf("Nested type of map should be of type map[string]interface{}, have %T", m["B"])
	}

	foo := in["Foo"].(map[string]interface{})
	if name := foo["example_key"]; name != "example" {
		t.Errorf("Map nested struct's name field should give example, got: %s", name)
	}
}

func TestMap_NestedMapWithSliceIntValues(t *testing.T) {
	type B struct {
		Foo map[string][]int
	}

	type A struct {
		B *B
	}

	b := &B{
		Foo: map[string][]int{
			"example_key": {80},
		},
	}

	a := &A{B: b}

	m := Map(a)

	if typ := reflect.TypeOf(m).Kind(); typ != reflect.Map {
		t.Errorf("Map should return a map type, got: %v", typ)
	}

	in, ok := m["B"].(map[string]interface{})
	if !ok {
		t.Errorf("Nested type of map should be of type map[string]interface{}, have %T", m["B"])
	}

	foo := in["Foo"].(map[string][]int)
	if name := foo["example_key"]; name[0] != 80 {
		t.Errorf("Map nested struct's name field should give example, got: %v", name)
	}
}

func TestMap_NestedMapWithSliceStructValues(t *testing.T) {
	type address struct {
		Country string `structs:"country"`
	}

	type B struct {
		Foo map[string][]address
	}

	type A struct {
		B *B
	}

	b := &B{
		Foo: map[string][]address{
			"example_key": {
				{Country: "Turkey"},
			},
		},
	}

	a := &A{B: b}
	m := Map(a)

	if typ := reflect.TypeOf(m).Kind(); typ != reflect.Map {
		t.Errorf("Map should return a map type, got: %v", typ)
	}

	in, ok := m["B"].(map[string]interface{})
	if !ok {
		t.Errorf("Nested type of map should be of type map[string]interface{}, have %T", m["B"])
	}

	foo := in["Foo"].(map[string]interface{})

	addresses := foo["example_key"].([]interface{})

	addr, ok := addresses[0].(map[string]interface{})
	if !ok {
		t.Errorf("Nested type of map should be of type map[string]interface{}, have %T", m["B"])
	}

	if _, exists := addr["country"]; !exists {
		t.Errorf("Expecting country, but found Country")
	}
}

func TestMap_NestedSliceWithStructValues(t *testing.T) {
	type address struct {
		Country string `structs:"customCountryName"`
	}

	type person struct {
		Name      string    `structs:"name"`
		Addresses []address `structs:"addresses"`
	}

	p := person{
		Name: "test",
		Addresses: []address{
			{Country: "England"},
			{Country: "Italy"},
		},
	}
	mp := Map(p)

	mpAddresses := mp["addresses"].([]interface{})
	if _, exists := mpAddresses[0].(map[string]interface{})["Country"]; exists {
		t.Errorf("Expecting customCountryName, but found Country")
	}

	if _, exists := mpAddresses[0].(map[string]interface{})["customCountryName"]; !exists {
		t.Errorf("customCountryName key not found")
	}
}

func TestMap_NestedSliceWithPointerOfStructValues(t *testing.T) {
	type address struct {
		Country string `structs:"customCountryName"`
	}

	type person struct {
		Name      string     `structs:"name"`
		Addresses []*address `structs:"addresses"`
	}

	p := person{
		Name: "test",
		Addresses: []*address{
			{Country: "England"},
			{Country: "Italy"},
		},
	}
	mp := Map(p)

	mpAddresses := mp["addresses"].([]interface{})
	if _, exists := mpAddresses[0].(map[string]interface{})["Country"]; exists {
		t.Errorf("Expecting customCountryName, but found Country")
	}

	if _, exists := mpAddresses[0].(map[string]interface{})["customCountryName"]; !exists {
		t.Errorf("customCountryName key not found")
	}
}

func TestMap_NestedSliceWithIntValues(t *testing.T) {
	type person struct {
		Name  string `structs:"name"`
		Ports []int  `structs:"ports"`
	}

	p := person{
		Name:  "test",
		Ports: []int{80},
	}
	m := Map(p)

	ports, ok := m["ports"].([]int)
	if !ok {
		t.Errorf("Nested type of map should be of type []int, have %T", m["ports"])
	}

	if ports[0] != 80 {
		t.Errorf("Map nested struct's ports field should give 80, got: %v", ports)
	}
}

func TestMap_Anonymous(t *testing.T) {
	type A struct {
		Name string
	}
	a := &A{Name: "example"}

	type B struct {
		*A
	}
	b := &B{}
	b.A = a

	m := Map(b)

	if typ := reflect.TypeOf(m).Kind(); typ != reflect.Map {
		t.Errorf("Map should return a map type, got: %v", typ)
	}

	in, ok := m["A"].(map[string]interface{})
	if !ok {
		t.Error("Embedded structs is not available in the map")
	}

	if name := in["Name"].(string); name != "example" {
		t.Errorf("Embedded A struct's Name field should give example, got: %s", name)
	}
}

func TestMap_Flatnested(t *testing.T) {
	type A struct {
		Name string
	}
	a := A{Name: "example"}

	type B struct {
		A `structs:",flatten"`
		C int
	}
	b := &B{C: 123}
	b.A = a

	m := Map(b)

	_, ok := m["A"].(map[string]interface{})
	if ok {
		t.Error("Embedded A struct with tag flatten has to be flat in the map")
	}

	expectedMap := map[string]interface{}{"Name": "example", "C": 123}
	if !reflect.DeepEqual(m, expectedMap) {
		t.Errorf("The exprected map %+v does't correspond to %+v", expectedMap, m)
	}

}

func TestMap_FlatnestedOverwrite(t *testing.T) {
	type A struct {
		Name string
	}
	a := A{Name: "example"}

	type B struct {
		A    `structs:",flatten"`
		Name string
		C    int
	}
	b := &B{C: 123, Name: "bName"}
	b.A = a

	m := Map(b)

	_, ok := m["A"].(map[string]interface{})
	if ok {
		t.Error("Embedded A struct with tag flatten has to be flat in the map")
	}

	expectedMap := map[string]interface{}{"Name": "bName", "C": 123}
	if !reflect.DeepEqual(m, expectedMap) {
		t.Errorf("The exprected map %+v does't correspond to %+v", expectedMap, m)
	}
}

func TestMap_TimeField(t *testing.T) {
	type A struct {
		CreatedAt time.Time
	}

	a := &A{CreatedAt: time.Now().UTC()}
	m := Map(a)

	_, ok := m["CreatedAt"].(time.Time)
	if !ok {
		t.Error("Time field must be final")
	}
}

func TestFillMap(t *testing.T) {
	var T = struct {
		A string
		B int
		C bool
	}{
		A: "a-value",
		B: 2,
		C: true,
	}

	a := make(map[string]interface{}, 0)
	FillMap(T, a)

	// we have three fields
	if len(a) != 3 {
		t.Errorf("FillMap should fill a map of len 3, got: %d", len(a))
	}

	inMap := func(val interface{}) bool {
		for _, v := range a {
			if reflect.DeepEqual(v, val) {
				return true
			}
		}

		return false
	}

	for _, val := range []interface{}{"a-value", 2, true} {
		if !inMap(val) {
			t.Errorf("FillMap should have the value %v", val)
		}
	}
}

func TestFillMap_Nil(t *testing.T) {
	var T = struct {
		A string
		B int
		C bool
	}{
		A: "a-value",
		B: 2,
		C: true,
	}

	defer func() {
		err := recover()
		if err != nil {
			t.Error("FillMap should not panic if a nil map is passed")
		}
	}()

	// nil should no
	FillMap(T, nil)
}
func TestStruct(t *testing.T) {
	var T = struct{}{}

	if !IsStruct(T) {
		t.Errorf("T should be a struct, got: %T", T)
	}

	if !IsStruct(&T) {
		t.Errorf("T should be a struct, got: %T", T)
	}

}

func TestValues(t *testing.T) {
	var T = struct {
		A string
		B int
		C bool
	}{
		A: "a-value",
		B: 2,
		C: true,
	}

	s := Values(T)

	if typ := reflect.TypeOf(s).Kind(); typ != reflect.Slice {
		t.Errorf("Values should return a slice type, got: %v", typ)
	}

	inSlice := func(val interface{}) bool {
		for _, v := range s {
			if reflect.DeepEqual(v, val) {
				return true
			}
		}
		return false
	}

	for _, val := range []interface{}{"a-value", 2, true} {
		if !inSlice(val) {
			t.Errorf("Values should have the value %v", val)
		}
	}
}

func TestValues_OmitEmpty(t *testing.T) {
	type A struct {
		Name  string
		Value int `structs:",omitempty"`
	}

	a := A{Name: "example"}
	s := Values(a)

	if len(s) != 1 {
		t.Errorf("Values of omitted empty fields should be not counted")
	}

	if s[0].(string) != "example" {
		t.Errorf("Values of omitted empty fields should left the value example")
	}
}

func TestValues_OmitNested(t *testing.T) {
	type A struct {
		Name  string
		Value int
	}

	a := A{
		Name:  "example",
		Value: 123,
	}

	type B struct {
		A A `structs:",omitnested"`
		C int
	}
	b := &B{A: a, C: 123}

	s := Values(b)

	if len(s) != 2 {
		t.Errorf("Values of omitted nested struct should be not counted")
	}

	inSlice := func(val interface{}) bool {
		for _, v := range s {
			if reflect.DeepEqual(v, val) {
				return true
			}
		}
		return false
	}

	for _, val := range []interface{}{123, a} {
		if !inSlice(val) {
			t.Errorf("Values should have the value %v", val)
		}
	}
}

func TestValues_Nested(t *testing.T) {
	type A struct {
		Name string
	}
	a := A{Name: "example"}

	type B struct {
		A A
		C int
	}
	b := &B{A: a, C: 123}

	s := Values(b)

	inSlice := func(val interface{}) bool {
		for _, v := range s {
			if reflect.DeepEqual(v, val) {
				return true
			}
		}
		return false
	}

	for _, val := range []interface{}{"example", 123} {
		if !inSlice(val) {
			t.Errorf("Values should have the value %v", val)
		}
	}
}

func TestValues_Anonymous(t *testing.T) {
	type A struct {
		Name string
	}
	a := A{Name: "example"}

	type B struct {
		A
		C int
	}
	b := &B{C: 123}
	b.A = a

	s := Values(b)

	inSlice := func(val interface{}) bool {
		for _, v := range s {
			if reflect.DeepEqual(v, val) {
				return true
			}
		}
		return false
	}

	for _, val := range []interface{}{"example", 123} {
		if !inSlice(val) {
			t.Errorf("Values should have the value %v", val)
		}
	}
}

func TestNames(t *testing.T) {
	var T = struct {
		A string
		B int
		C bool
	}{
		A: "a-value",
		B: 2,
		C: true,
	}

	s := Names(T)

	if len(s) != 3 {
		t.Errorf("Names should return a slice of len 3, got: %d", len(s))
	}

	inSlice := func(val string) bool {
		for _, v := range s {
			if reflect.DeepEqual(v, val) {
				return true
			}
		}
		return false
	}

	for _, val := range []string{"A", "B", "C"} {
		if !inSlice(val) {
			t.Errorf("Names should have the value %v", val)
		}
	}
}

func TestFields(t *testing.T) {
	var T = struct {
		A string
		B int
		C bool
	}{
		A: "a-value",
		B: 2,
		C: true,
	}

	s := Fields(T)

	if len(s) != 3 {
		t.Errorf("Fields should return a slice of len 3, got: %d", len(s))
	}

	inSlice := func(val string) bool {
		for _, v := range s {
			if reflect.DeepEqual(v.Name(), val) {
				return true
			}
		}
		return false
	}

	for _, val := range []string{"A", "B", "C"} {
		if !inSlice(val) {
			t.Errorf("Fields should have the value %v", val)
		}
	}
}

func TestFields_OmitNested(t *testing.T) {
	type A struct {
		Name    string
		Enabled bool
	}
	a := A{Name: "example"}

	type B struct {
		A      A
		C      int
		Value  string `structs:"-"`
		Number int
	}
	b := &B{A: a, C: 123}

	s := Fields(b)

	if len(s) != 3 {
		t.Errorf("Fields should omit nested struct. Expecting 2 got: %d", len(s))
	}

	inSlice := func(val interface{}) bool {
		for _, v := range s {
			if reflect.DeepEqual(v.Name(), val) {
				return true
			}
		}
		return false
	}

	for _, val := range []interface{}{"A", "C"} {
		if !inSlice(val) {
			t.Errorf("Fields should have the value %v", val)
		}
	}
}

func TestFields_Anonymous(t *testing.T) {
	type A struct {
		Name string
	}
	a := A{Name: "example"}

	type B struct {
		A
		C int
	}
	b := &B{C: 123}
	b.A = a

	s := Fields(b)

	inSlice := func(val interface{}) bool {
		for _, v := range s {
			if reflect.DeepEqual(v.Name(), val) {
				return true
			}
		}
		return false
	}

	for _, val := range []interface{}{"A", "C"} {
		if !inSlice(val) {
			t.Errorf("Fields should have the value %v", val)
		}
	}
}

func TestIsZero(t *testing.T) {
	var T = struct {
		A string
		B int
		C bool `structs:"-"`
		D []string
	}{}

	ok := IsZero(T)
	if !ok {
		t.Error("IsZero should return true because none of the fields are initialized.")
	}

	var X = struct {
		A string
		F *bool
	}{
		A: "a-value",
	}

	ok = IsZero(X)
	if ok {
		t.Error("IsZero should return false because A is initialized")
	}

	var Y = struct {
		A string
		B int
	}{
		A: "a-value",
		B: 123,
	}

	ok = IsZero(Y)
	if ok {
		t.Error("IsZero should return false because A and B is initialized")
	}
}

func TestIsZero_OmitNested(t *testing.T) {
	type A struct {
		Name string
		D    string
	}
	a := A{Name: "example"}

	type B struct {
		A A `structs:",omitnested"`
		C int
	}
	b := &B{A: a, C: 123}

	ok := IsZero(b)
	if ok {
		t.Error("IsZero should return false because A, B and C are initialized")
	}

	aZero := A{}
	bZero := &B{A: aZero}

	ok = IsZero(bZero)
	if !ok {
		t.Error("IsZero should return true because neither A nor B is initialized")
	}

}

func TestIsZero_Nested(t *testing.T) {
	type A struct {
		Name string
		D    string
	}
	a := A{Name: "example"}

	type B struct {
		A A
		C int
	}
	b := &B{A: a, C: 123}

	ok := IsZero(b)
	if ok {
		t.Error("IsZero should return false because A, B and C are initialized")
	}

	aZero := A{}
	bZero := &B{A: aZero}

	ok = IsZero(bZero)
	if !ok {
		t.Error("IsZero should return true because neither A nor B is initialized")
	}

}

func TestIsZero_Anonymous(t *testing.T) {
	type A struct {
		Name string
		D    string
	}
	a := A{Name: "example"}

	type B struct {
		A
		C int
	}
	b := &B{C: 123}
	b.A = a

	ok := IsZero(b)
	if ok {
		t.Error("IsZero should return false because A, B and C are initialized")
	}

	aZero := A{}
	bZero := &B{}
	bZero.A = aZero

	ok = IsZero(bZero)
	if !ok {
		t.Error("IsZero should return true because neither A nor B is initialized")
	}
}

func TestHasZero(t *testing.T) {
	var T = struct {
		A string
		B int
		C bool `structs:"-"`
		D []string
	}{
		A: "a-value",
		B: 2,
	}

	ok := HasZero(T)
	if !ok {
		t.Error("HasZero should return true because A and B are initialized.")
	}

	var X = struct {
		A string
		F *bool
	}{
		A: "a-value",
	}

	ok = HasZero(X)
	if !ok {
		t.Error("HasZero should return true because A is initialized")
	}

	var Y = struct {
		A string
		B int
	}{
		A: "a-value",
		B: 123,
	}

	ok = HasZero(Y)
	if ok {
		t.Error("HasZero should return false because A and B is initialized")
	}
}

func TestHasZero_OmitNested(t *testing.T) {
	type A struct {
		Name string
		D    string
	}
	a := A{Name: "example"}

	type B struct {
		A A `structs:",omitnested"`
		C int
	}
	b := &B{A: a, C: 123}

	// Because the Field A inside B is omitted  HasZero should return false
	// because it will stop iterating deeper andnot going to lookup for D
	ok := HasZero(b)
	if ok {
		t.Error("HasZero should return false because A and C are initialized")
	}
}

func TestHasZero_Nested(t *testing.T) {
	type A struct {
		Name string
		D    string
	}
	a := A{Name: "example"}

	type B struct {
		A A
		C int
	}
	b := &B{A: a, C: 123}

	ok := HasZero(b)
	if !ok {
		t.Error("HasZero should return true because D is not initialized")
	}
}

func TestHasZero_Anonymous(t *testing.T) {
	type A struct {
		Name string
		D    string
	}
	a := A{Name: "example"}

	type B struct {
		A
		C int
	}
	b := &B{C: 123}
	b.A = a

	ok := HasZero(b)
	if !ok {
		t.Error("HasZero should return false because D is not initialized")
	}
}

func TestName(t *testing.T) {
	type Foo struct {
		A string
		B bool
	}
	f := &Foo{}

	n := Name(f)
	if n != "Foo" {
		t.Errorf("Name should return Foo, got: %s", n)
	}

	unnamed := struct{ Name string }{Name: "Cihangir"}
	m := Name(unnamed)
	if m != "" {
		t.Errorf("Name should return empty string for unnamed struct, got: %s", n)
	}

	defer func() {
		err := recover()
		if err == nil {
			t.Error("Name should panic if a non struct is passed")
		}
	}()

	Name([]string{})
}

func TestNestedNilPointer(t *testing.T) {
	type Collar struct {
		Engraving string
	}

	type Dog struct {
		Name   string
		Collar *Collar
	}

	type Person struct {
		Name string
		Dog  *Dog
	}

	person := &Person{
		Name: "John",
	}

	personWithDog := &Person{
		Name: "Ron",
		Dog: &Dog{
			Name: "Rover",
		},
	}

	personWithDogWithCollar := &Person{
		Name: "Kon",
		Dog: &Dog{
			Name: "Ruffles",
			Collar: &Collar{
				Engraving: "If lost, call Kon",
			},
		},
	}

	defer func() {
		err := recover()
		if err != nil {
			fmt.Printf("err %+v\n", err)
			t.Error("Internal nil pointer should not panic")
		}
	}()

	_ = Map(person)                  // Panics
	_ = Map(personWithDog)           // Panics
	_ = Map(personWithDogWithCollar) // Doesn't panic
}

func TestSetValueOnNestedField(t *testing.T) {
	type Base struct {
		ID int
	}

	type User struct {
		Base
		Name string
	}

	u := User{}
	s := New(&u)
	f := s.Field("Base").Field("ID")
	err := f.Set(10)
	if err != nil {
		t.Errorf("Error %v", err)
	}
	if f.Value().(int) != 10 {
		t.Errorf("Value should be equal to 10, got %v", f.Value())
	}
}

type Person struct {
	Name string
	Age  int
}

func (p *Person) String() string {
	return fmt.Sprintf("%s(%d)", p.Name, p.Age)
}

func TestTagWithStringOption(t *testing.T) {

	type Address struct {
		Country string  `json:"country"`
		Person  *Person `json:"person,string"`
	}

	person := &Person{
		Name: "John",
		Age:  23,
	}

	address := &Address{
		Country: "EU",
		Person:  person,
	}

	defer func() {
		err := recover()
		if err != nil {
			fmt.Printf("err %+v\n", err)
			t.Error("Internal nil pointer should not panic")
		}
	}()

	s := New(address)

	s.TagName = "json"
	m := s.Map()

	if m["person"] != person.String() {
		t.Errorf("Value for field person should be %s, got: %s", person.String(), m["person"])
	}

	vs := s.Values()
	if vs[1] != person.String() {
		t.Errorf("Value for 2nd field (person) should be %T, got: %T", person.String(), vs[1])
	}
}

type Animal struct {
	Name string
	Age  int
}

type Dog struct {
	Animal *Animal `json:"animal,string"`
}

func TestNonStringerTagWithStringOption(t *testing.T) {
	a := &Animal{
		Name: "Fluff",
		Age:  4,
	}

	d := &Dog{
		Animal: a,
	}

	defer func() {
		err := recover()
		if err != nil {
			fmt.Printf("err %+v\n", err)
			t.Error("Internal nil pointer should not panic")
		}
	}()

	s := New(d)

	s.TagName = "json"
	m := s.Map()

	if _, exists := m["animal"]; exists {
		t.Errorf("Value for field Animal should not exist")
	}
}

func TestMap_InterfaceValue(t *testing.T) {
	type TestStruct struct {
		A interface{}
	}

	expected := []byte("test value")

	a := TestStruct{A: expected}
	s := Map(a)
	if !reflect.DeepEqual(s["A"], expected) {
		t.Errorf("Value does not match expected: %q != %q", s["A"], expected)
	}
}

func TestPointer2Pointer(t *testing.T) {
	defer func() {
		err := recover()
		if err != nil {
			fmt.Printf("err %+v\n", err)
			t.Error("Internal nil pointer should not panic")
		}
	}()
	a := &Animal{
		Name: "Fluff",
		Age:  4,
	}
	_ = Map(&a)

	b := &a
	_ = Map(&b)

	c := &b
	_ = Map(&c)
}

func TestMap_InterfaceTypeWithMapValue(t *testing.T) {
	type A struct {
		Name    string      `structs:"name"`
		IP      string      `structs:"ip"`
		Query   string      `structs:"query"`
		Payload interface{} `structs:"payload"`
	}

	a := A{
		Name:    "test",
		IP:      "127.0.0.1",
		Query:   "",
		Payload: map[string]string{"test_param": "test_param"},
	}

	defer func() {
		err := recover()
		if err != nil {
			t.Error("Converting Map with an interface{} type with map value should not panic")
		}
	}()

	_ = Map(a)
}
