package structs

import (
	"reflect"
	"testing"
)

// A test struct that defines all cases
type Foo struct {
	A    string
	B    int    `structs:"y"`
	C    bool   `json:"c"`
	d    string // not exported
	E    *Baz
	x    string `xml:"x"` // not exported, with tag
	Y    []string
	Z    map[string]interface{}
	*Bar // embedded
}

type Baz struct {
	A string
	B int
}

type Bar struct {
	E string
	F int
	g []string
}

func newStruct() *Struct {
	b := &Bar{
		E: "example",
		F: 2,
		g: []string{"zeynep", "fatih"},
	}

	// B and x is not initialized for testing
	f := &Foo{
		A: "gopher",
		C: true,
		d: "small",
		E: nil,
		Y: []string{"example"},
		Z: nil,
	}
	f.Bar = b

	return New(f)
}

func TestField_Set(t *testing.T) {
	s := newStruct()

	f := s.Field("A")
	err := f.Set("fatih")
	if err != nil {
		t.Error(err)
	}

	if f.Value().(string) != "fatih" {
		t.Errorf("Setted value is wrong: %s want: %s", f.Value().(string), "fatih")
	}

	f = s.Field("Y")
	err = f.Set([]string{"override", "with", "this"})
	if err != nil {
		t.Error(err)
	}

	sliceLen := len(f.Value().([]string))
	if sliceLen != 3 {
		t.Errorf("Setted values slice length is wrong: %d, want: %d", sliceLen, 3)
	}

	f = s.Field("C")
	err = f.Set(false)
	if err != nil {
		t.Error(err)
	}

	if f.Value().(bool) {
		t.Errorf("Setted value is wrong: %t want: %t", f.Value().(bool), false)
	}

	// let's pass a different type
	f = s.Field("A")
	err = f.Set(123) // Field A is of type string, but we are going to pass an integer
	if err == nil {
		t.Error("Setting a field's value with a different type than the field's type should return an error")
	}

	// old value should be still there :)
	if f.Value().(string) != "fatih" {
		t.Errorf("Setted value is wrong: %s want: %s", f.Value().(string), "fatih")
	}

	// let's access an unexported field, which should give an error
	f = s.Field("d")
	err = f.Set("large")
	if err != errNotExported {
		t.Error(err)
	}

	// let's set a pointer to struct
	b := &Bar{
		E: "gopher",
		F: 2,
	}

	f = s.Field("Bar")
	err = f.Set(b)
	if err != nil {
		t.Error(err)
	}

	baz := &Baz{
		A: "helloWorld",
		B: 42,
	}

	f = s.Field("E")
	err = f.Set(baz)
	if err != nil {
		t.Error(err)
	}

	ba := s.Field("E").Value().(*Baz)

	if ba.A != "helloWorld" {
		t.Errorf("could not set baz. Got: %s Want: helloWorld", ba.A)
	}
}

func TestField_NotSettable(t *testing.T) {
	a := map[int]Baz{
		4: {
			A: "value",
		},
	}

	s := New(a[4])

	if err := s.Field("A").Set("newValue"); err != errNotSettable {
		t.Errorf("Trying to set non-settable field should error with %q. Got %q instead.", errNotSettable, err)
	}
}

func TestField_Zero(t *testing.T) {
	s := newStruct()

	f := s.Field("A")
	err := f.Zero()
	if err != nil {
		t.Error(err)
	}

	if f.Value().(string) != "" {
		t.Errorf("Zeroed value is wrong: %s want: %s", f.Value().(string), "")
	}

	f = s.Field("Y")
	err = f.Zero()
	if err != nil {
		t.Error(err)
	}

	sliceLen := len(f.Value().([]string))
	if sliceLen != 0 {
		t.Errorf("Zeroed values slice length is wrong: %d, want: %d", sliceLen, 0)
	}

	f = s.Field("C")
	err = f.Zero()
	if err != nil {
		t.Error(err)
	}

	if f.Value().(bool) {
		t.Errorf("Zeroed value is wrong: %t want: %t", f.Value().(bool), false)
	}

	// let's access an unexported field, which should give an error
	f = s.Field("d")
	err = f.Zero()
	if err != errNotExported {
		t.Error(err)
	}

	f = s.Field("Bar")
	err = f.Zero()
	if err != nil {
		t.Error(err)
	}

	f = s.Field("E")
	err = f.Zero()
	if err != nil {
		t.Error(err)
	}

	v := s.Field("E").value
	if !v.IsNil() {
		t.Errorf("could not set baz. Got: %s Want: <nil>", v.Interface())
	}
}

func TestField(t *testing.T) {
	s := newStruct()

	defer func() {
		err := recover()
		if err == nil {
			t.Error("Retrieveing a non existing field from the struct should panic")
		}
	}()

	_ = s.Field("no-field")
}

func TestField_Kind(t *testing.T) {
	s := newStruct()

	f := s.Field("A")
	if f.Kind() != reflect.String {
		t.Errorf("Field A has wrong kind: %s want: %s", f.Kind(), reflect.String)
	}

	f = s.Field("B")
	if f.Kind() != reflect.Int {
		t.Errorf("Field B has wrong kind: %s want: %s", f.Kind(), reflect.Int)
	}

	// unexported
	f = s.Field("d")
	if f.Kind() != reflect.String {
		t.Errorf("Field d has wrong kind: %s want: %s", f.Kind(), reflect.String)
	}
}

func TestField_Tag(t *testing.T) {
	s := newStruct()

	v := s.Field("B").Tag("json")
	if v != "" {
		t.Errorf("Field's tag value of a non existing tag should return empty, got: %s", v)
	}

	v = s.Field("C").Tag("json")
	if v != "c" {
		t.Errorf("Field's tag value of the existing field C should return 'c', got: %s", v)
	}

	v = s.Field("d").Tag("json")
	if v != "" {
		t.Errorf("Field's tag value of a non exported field should return empty, got: %s", v)
	}

	v = s.Field("x").Tag("xml")
	if v != "x" {
		t.Errorf("Field's tag value of a non exported field with a tag should return 'x', got: %s", v)
	}

	v = s.Field("A").Tag("json")
	if v != "" {
		t.Errorf("Field's tag value of a existing field without a tag should return empty, got: %s", v)
	}
}

func TestField_Value(t *testing.T) {
	s := newStruct()

	v := s.Field("A").Value()
	val, ok := v.(string)
	if !ok {
		t.Errorf("Field's value of a A should be string")
	}

	if val != "gopher" {
		t.Errorf("Field's value of a existing tag should return 'gopher', got: %s", val)
	}

	defer func() {
		err := recover()
		if err == nil {
			t.Error("Value of a non exported field from the field should panic")
		}
	}()

	// should panic
	_ = s.Field("d").Value()
}

func TestField_IsEmbedded(t *testing.T) {
	s := newStruct()

	if !s.Field("Bar").IsEmbedded() {
		t.Errorf("Fields 'Bar' field is an embedded field")
	}

	if s.Field("d").IsEmbedded() {
		t.Errorf("Fields 'd' field is not an embedded field")
	}
}

func TestField_IsExported(t *testing.T) {
	s := newStruct()

	if !s.Field("Bar").IsExported() {
		t.Errorf("Fields 'Bar' field is an exported field")
	}

	if !s.Field("A").IsExported() {
		t.Errorf("Fields 'A' field is an exported field")
	}

	if s.Field("d").IsExported() {
		t.Errorf("Fields 'd' field is not an exported field")
	}
}

func TestField_IsZero(t *testing.T) {
	s := newStruct()

	if s.Field("A").IsZero() {
		t.Errorf("Fields 'A' field is an initialized field")
	}

	if !s.Field("B").IsZero() {
		t.Errorf("Fields 'B' field is not an initialized field")
	}
}

func TestField_Name(t *testing.T) {
	s := newStruct()

	if s.Field("A").Name() != "A" {
		t.Errorf("Fields 'A' field should have the name 'A'")
	}
}

func TestField_Field(t *testing.T) {
	s := newStruct()

	e := s.Field("Bar").Field("E")

	val, ok := e.Value().(string)
	if !ok {
		t.Error("The value of the field 'e' inside 'Bar' struct should be string")
	}

	if val != "example" {
		t.Errorf("The value of 'e' should be 'example, got: %s", val)
	}

	defer func() {
		err := recover()
		if err == nil {
			t.Error("Field of a non existing nested struct should panic")
		}
	}()

	_ = s.Field("Bar").Field("e")
}

func TestField_Fields(t *testing.T) {
	s := newStruct()
	fields := s.Field("Bar").Fields()

	if len(fields) != 3 {
		t.Errorf("We expect 3 fields in embedded struct, was: %d", len(fields))
	}
}

func TestField_FieldOk(t *testing.T) {
	s := newStruct()

	b, ok := s.FieldOk("Bar")
	if !ok {
		t.Error("The field 'Bar' should exists.")
	}

	e, ok := b.FieldOk("E")
	if !ok {
		t.Error("The field 'E' should exists.")
	}

	val, ok := e.Value().(string)
	if !ok {
		t.Error("The value of the field 'e' inside 'Bar' struct should be string")
	}

	if val != "example" {
		t.Errorf("The value of 'e' should be 'example, got: %s", val)
	}
}
