package diff_test

import (
	"fmt"
	"math/big"
	"reflect"

	"github.com/r3labs/diff/v3"
)

//Try to do a bunch of stuff that will result in some or all failures
//when trying to apply either a valid or invalid changelog
func ExamplePatchWithErrors() {

	type Fruit struct {
		ID        int            `diff:"ID" json:"Identifier"`
		Name      string         `diff:"name"`
		Healthy   bool           `diff:"-"`
		Nutrients []string       `diff:"nutrients"`
		Labels    map[string]int `diff:"labs"`
	}

	type Bat struct {
		ID   string `diff:"ID"`
		Name string `diff:"-"`
	}

	a := Fruit{
		ID:      1,
		Name:    "Green Apple",
		Healthy: true,
		Nutrients: []string{
			"vitamin a",
			"vitamin b",
			"vitamin c",
			"vitamin d",
		},
		Labels: make(map[string]int),
	}
	a.Labels["likes"] = 10
	a.Labels["colors"] = 2

	b := Fruit{
		ID:      2,
		Name:    "Red Apple",
		Healthy: true,
		Nutrients: []string{
			"vitamin c",
			"vitamin d",
			"vitamin e",
		},
		Labels: make(map[string]int),
	}
	b.Labels["forests"] = 1223
	b.Labels["colors"] = 1222

	c := Fruit{
		Labels: make(map[string]int),
		Nutrients: []string{
			"vitamin c",
			"vitamin d",
			"vitamin a",
		},
	}
	c.Labels["likes"] = 21
	c.Labels["colors"] = 42

	d := Bat{
		ID:   "first",
		Name: "second",
	}

	changelog, err := diff.Diff(a, b)
	if err != nil {
		panic(err)
	}

	//This fails in total because c is not assignable (passed by Value)
	patchLog := diff.Patch(changelog, c)

	//this also demonstrated the nested errors with 'next'

	errors := patchLog[0].Errors.(*diff.DiffError)

	//we can also continue to nest errors if we like
	message := errors.WithCause(diff.NewError("This is a custom message")).
		WithCause(fmt.Errorf("this is an error from somewhere else but still compatible")).
		Error()

	//invoke a few failures, i.e. bad changelog
	changelog[2].Path[1] = "bad index"
	changelog[3].Path[0] = "bad struct field"

	patchLog = diff.Patch(changelog, &c)

	patchLog, _ = diff.Merge(a, nil, &c)

	patchLog, _ = diff.Merge(a, d, &c)

	//try patching a string
	patchLog = diff.Patch(changelog, message)

	//test an invalid change Value
	var bad *diff.ChangeValue
	if bad.IsValid() {
		fmt.Print("this should never happen")
	}

	//Output:
}

//ExampleMerge demonstrates how to use the Merge function
func ExampleMerge() {
	type Fruit struct {
		ID        int            `diff:"ID" json:"Identifier"`
		Name      string         `diff:"name"`
		Healthy   bool           `diff:"healthy"`
		Nutrients []string       `diff:"nutrients,create,omitunequal"`
		Labels    map[string]int `diff:"labs,create"`
	}

	a := Fruit{
		ID:      1,
		Name:    "Green Apple",
		Healthy: true,
		Nutrients: []string{
			"vitamin a",
			"vitamin b",
			"vitamin c",
			"vitamin d",
		},
		Labels: make(map[string]int),
	}
	a.Labels["likes"] = 10
	a.Labels["colors"] = 2

	b := Fruit{
		ID:      2,
		Name:    "Red Apple",
		Healthy: true,
		Nutrients: []string{
			"vitamin c",
			"vitamin d",
			"vitamin e",
		},
		Labels: make(map[string]int),
	}
	b.Labels["forests"] = 1223
	b.Labels["colors"] = 1222

	c := Fruit{
		Labels: make(map[string]int),
		Nutrients: []string{
			"vitamin a",
			"vitamin c",
			"vitamin d",
		},
	}
	c.Labels["likes"] = 21
	c.Labels["colors"] = 42

	//the only error that can happen here comes from the diff step
	patchLog, _ := diff.Merge(a, b, &c)

	//Note that unlike our patch version we've not included 'create' in the
	//tag for nutrients. This will omit "vitamin e" from ending up in c
	fmt.Printf("%#v", len(patchLog))

	//Output: 8
}

//ExamplePrimitiveSlice demonstrates working with arrays and primitive values
func ExamplePrimitiveSlice() {
	sla := []string{
		"this",
		"is",
		"a",
		"simple",
	}

	slb := []string{
		"slice",
		"That",
		"can",
		"be",
		"diff'ed",
	}

	slc := []string{
		"ok",
	}

	patch, err := diff.Diff(sla, slb, diff.StructMapKeySupport())
	if err != nil {
		fmt.Print("failed to diff sla and slb")
	}
	cl := diff.Patch(patch, &slc)

	//now the other way, round
	sla = []string{
		"slice",
		"That",
		"can",
		"be",
		"diff'ed",
	}
	slb = []string{
		"this",
		"is",
		"a",
		"simple",
	}

	patch, err = diff.Diff(sla, slb)
	if err != nil {
		fmt.Print("failed to diff sla and slb")
	}
	cl = diff.Patch(patch, &slc)

	//and finally a clean view
	sla = []string{
		"slice",
		"That",
		"can",
		"be",
		"diff'ed",
	}
	slb = []string{}

	patch, err = diff.Diff(sla, slb)
	if err != nil {
		fmt.Print("failed to diff sla and slb")
	}
	cl = diff.Patch(patch, &slc)

	fmt.Printf("%d changes made to string array; %v", len(cl), slc)

	//Output: 5 changes made to string array; [simple a]
}

//ExampleComplexMapPatch demonstrates how to use the Patch function for complex slices
//NOTE: There is a potential pitfall here, take a close look at b[2]. If patching the
//      original, the operation will work intuitively however, in a merge situation we
//      may not get everything we expect because it's a true diff between a and b and
//      the diff log will not contain enough information to fully recreate b from an
//      empty slice. This is exemplified in that the test "colors" is dropped in element
//      3 of c. Change "colors" to "color" and see what happens. Keep in mind this only
//      happens when we need to allocate a new complex element. In normal operations we
//      fix for this by keeping a copy of said element in the diff log (as parent) and
//      allocate such an element as a whole copy prior to applying any updates?
//
//      The new default is to carry this information forward, we invoke this pitfall
//      by creating such a situation and explicitly telling diff to discard the parent
//      In memory constrained environments if the developer is careful, they can use
//      the discard feature but unless you REALLY understand what's happening here, use
//      the default.
func ExampleComplexSlicePatch() {

	type Content struct {
		Text   string `diff:",create"`
		Number int    `diff:",create"`
	}
	type Attributes struct {
		Labels []Content `diff:",create"`
	}

	a := Attributes{
		Labels: []Content{
			{
				Text:   "likes",
				Number: 10,
			},
			{
				Text:   "forests",
				Number: 10,
			},
			{
				Text:   "colors",
				Number: 2,
			},
		},
	}

	b := Attributes{
		Labels: []Content{
			{
				Text:   "forests",
				Number: 14,
			},
			{
				Text:   "location",
				Number: 0x32,
			},
			{
				Text:   "colors",
				Number: 1222,
			},
			{
				Text:   "trees",
				Number: 34,
			},
		},
	}
	c := Attributes{}

	changelog, err := diff.Diff(a, b, diff.DiscardComplexOrigin(), diff.StructMapKeySupport())
	if err != nil {
		panic(err)
	}

	patchLog := diff.Patch(changelog, &c)

	fmt.Printf("Patched %d entries and encountered %d errors", len(patchLog), patchLog.ErrorCount())

	//Output: Patched 7 entries and encountered 3 errors
}

//ExampleComplexMapPatch demonstrates how to use the Patch function for complex slices.
func ExampleComplexMapPatch() {

	type Key struct {
		Value  string
		weight int
	}
	type Content struct {
		Text        string
		Number      float64
		WholeNumber int
	}
	type Attributes struct {
		Labels map[Key]Content
	}

	a := Attributes{
		Labels: make(map[Key]Content),
	}
	a.Labels[Key{Value: "likes"}] = Content{
		WholeNumber: 10,
		Number:      23.4,
	}

	a.Labels[Key{Value: "colors"}] = Content{
		WholeNumber: 2,
	}

	b := Attributes{
		Labels: make(map[Key]Content),
	}
	b.Labels[Key{Value: "forests"}] = Content{
		Text: "Sherwood",
	}
	b.Labels[Key{Value: "colors"}] = Content{
		Number: 1222,
	}
	b.Labels[Key{Value: "latitude"}] = Content{
		Number: 38.978797,
	}
	b.Labels[Key{Value: "longitude"}] = Content{
		Number: -76.490986,
	}

	//c := Attributes{}
	c := Attributes{
		Labels: make(map[Key]Content),
	}
	c.Labels[Key{Value: "likes"}] = Content{
		WholeNumber: 210,
		Number:      23.4453,
	}

	changelog, err := diff.Diff(a, b)
	if err != nil {
		panic(err)
	}

	patchLog := diff.Patch(changelog, &c)

	fmt.Printf("%#v", len(patchLog))

	//Output: 7
}

//ExamplePatch demonstrates how to use the Patch function
func ExamplePatch() {

	type Key struct {
		value  string
		weight int
	}
	type Cycle struct {
		Name  string `diff:"name,create"`
		Count int    `diff:"count,create"`
	}
	type Fruit struct {
		ID        int           `diff:"ID" json:"Identifier"`
		Name      string        `diff:"name"`
		Healthy   bool          `diff:"healthy"`
		Nutrients []string      `diff:"nutrients,create,omitunequal"`
		Labels    map[Key]Cycle `diff:"labs,create"`
		Cycles    []Cycle       `diff:"cycles,immutable"`
		Weights   []int
	}

	a := Fruit{
		ID:      1,
		Name:    "Green Apple",
		Healthy: true,
		Nutrients: []string{
			"vitamin a",
			"vitamin b",
			"vitamin c",
			"vitamin d",
		},
		Labels: make(map[Key]Cycle),
	}
	a.Labels[Key{value: "likes"}] = Cycle{
		Count: 10,
	}
	a.Labels[Key{value: "colors"}] = Cycle{
		Count: 2,
	}

	b := Fruit{
		ID:      2,
		Name:    "Red Apple",
		Healthy: true,
		Nutrients: []string{
			"vitamin c",
			"vitamin d",
			"vitamin e",
		},
		Labels: make(map[Key]Cycle),
		Weights: []int{
			1,
			2,
			3,
			4,
		},
	}
	b.Labels[Key{value: "forests"}] = Cycle{
		Count: 1223,
	}
	b.Labels[Key{value: "colors"}] = Cycle{
		Count: 1222,
	}

	c := Fruit{
		//Labels: make(map[string]int),
		Nutrients: []string{
			"vitamin a",
			"vitamin c",
			"vitamin d",
		},
	}
	//c.Labels["likes"] = 21

	d := a
	d.Cycles = []Cycle{
		Cycle{
			Name:  "First",
			Count: 45,
		},
		Cycle{
			Name:  "Third",
			Count: 4,
		},
	}
	d.Nutrients = append(d.Nutrients, "minerals")

	changelog, err := diff.Diff(a, b)
	if err != nil {
		panic(err)
	}

	patchLog := diff.Patch(changelog, &c)

	changelog, _ = diff.Diff(a, d)
	patchLog = diff.Patch(changelog, &c)

	fmt.Printf("%#v", len(patchLog))

	//Output: 1
}

func ExampleDiff() {
	type Tag struct {
		Name  string `diff:"name,identifier"`
		Value string `diff:"value"`
	}

	type Fruit struct {
		ID        int      `diff:"id"`
		Name      string   `diff:"name"`
		Healthy   bool     `diff:"healthy"`
		Nutrients []string `diff:"nutrients"`
		Tags      []Tag    `diff:"tags"`
	}

	a := Fruit{
		ID:      1,
		Name:    "Green Apple",
		Healthy: true,
		Nutrients: []string{
			"vitamin c",
			"vitamin d",
		},
		Tags: []Tag{
			{
				Name:  "kind",
				Value: "fruit",
			},
		},
	}

	b := Fruit{
		ID:      2,
		Name:    "Red Apple",
		Healthy: true,
		Nutrients: []string{
			"vitamin c",
			"vitamin d",
			"vitamin e",
		},
		Tags: []Tag{
			{
				Name:  "popularity",
				Value: "high",
			},
			{
				Name:  "kind",
				Value: "fruit",
			},
		},
	}

	changelog, err := diff.Diff(a, b)
	if err != nil {
		panic(err)
	}

	fmt.Printf("%#v", changelog)
	// Produces: diff.Changelog{diff.Change{Type:"update", Path:[]string{"id"}, From:1, To:2}, diff.Change{Type:"update", Path:[]string{"name"}, From:"Green Apple", To:"Red Apple"}, diff.Change{Type:"create", Path:[]string{"nutrients", "2"}, From:interface {}(nil), To:"vitamin e"}, diff.Change{Type:"create", Path:[]string{"tags", "popularity"}, From:interface {}(nil), To:main.Tag{Name:"popularity", Value:"high"}}}
}

func ExampleFilter() {
	type Tag struct {
		Name  string `diff:"name,identifier"`
		Value string `diff:"value"`
	}

	type Fruit struct {
		ID        int      `diff:"id"`
		Name      string   `diff:"name"`
		Healthy   bool     `diff:"healthy"`
		Nutrients []string `diff:"nutrients"`
		Tags      []Tag    `diff:"tags"`
	}

	a := Fruit{
		ID:      1,
		Name:    "Green Apple",
		Healthy: true,
		Nutrients: []string{
			"vitamin c",
			"vitamin d",
		},
	}

	b := Fruit{
		ID:      2,
		Name:    "Red Apple",
		Healthy: true,
		Nutrients: []string{
			"vitamin c",
			"vitamin d",
			"vitamin e",
		},
	}

	d, err := diff.NewDiffer(diff.Filter(func(path []string, parent reflect.Type, field reflect.StructField) bool {
		return field.Name != "Name"
	}))
	if err != nil {
		panic(err)
	}

	changelog, err := d.Diff(a, b)
	if err != nil {
		panic(err)
	}

	fmt.Printf("%#v", changelog)
	// Output: diff.Changelog{diff.Change{Type:"update", Path:[]string{"id"}, From:1, To:2, parent:diff_test.Fruit{ID:1, Name:"Green Apple", Healthy:true, Nutrients:[]string{"vitamin c", "vitamin d"}, Tags:[]diff_test.Tag(nil)}}, diff.Change{Type:"create", Path:[]string{"nutrients", "2"}, From:interface {}(nil), To:"vitamin e", parent:interface {}(nil)}}
}

func ExamplePrivatePtr() {
	type number struct {
		value *big.Int
		exp   int32
	}
	a := number{}
	b := number{value: big.NewInt(111)}

	changelog, err := diff.Diff(a, b)
	if err != nil {
		panic(err)
	}

	fmt.Printf("%#v", changelog)
	// Output: diff.Changelog{diff.Change{Type:"update", Path:[]string{"value"}, From:interface {}(nil), To:111, parent:diff_test.number{value:(*big.Int)(nil), exp:0}}}
}
