// Copyright 2019 CUE Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package diff

import (
	"bytes"
	"testing"

	"cuelang.org/go/cue"
	"cuelang.org/go/cue/cuecontext"
	"cuelang.org/go/internal/cuetdtest"
)

func TestDiff(t *testing.T) {
	testCases := []struct {
		name    string
		x, y    string
		kind    Kind
		diff    string
		profile *Profile
	}{{
		name: "identity struct",
		x: `{
			a: {
				b: 1
				c: 2
			}
			l: {
				d: 1
			}
		}`,
		y: `{
			a: {
				c: 2
				b: 1
			}
			l: {
				d: 1
			}
		}`,
	}, {
		name: "identity list",
		x:    `[1, 2, 3]`,
		y:    `[1, 2, 3]`,
	}, {
		name: "identity value",
		x:    `"foo"`,
		y:    `"foo"`,
	}, {
		name: "modified value",
		x:    `"foo"`,
		y:    `"bar"`,
		kind: Modified,
		diff: `- "foo",
+ "bar",
`,
	}, {
		name: "basics",
		x: `{
			a: int
			b: 2
			s: 4
			d: 1
			e: null
		}
		`,
		y: `
		{
			a: string
			c: 3
			s: 4
			d: int
			e: null
		}
		`,
		kind: Modified,
		diff: `  {
-     a: int
+     a: string
-     b: 2
+     c: 3
      s: 4
-     d: 1
+     d: int
      e: null
  }
`,
	}, {
		name: "basics 2",
		x: `{
			ls: [2, 3, 4]
			"foo-bar": 2
			s: 4
			lm1: [2, 3, 5]
			lm2: [6]
		}
		`,
		y: `
		{
			ls: [2, 3, 4]
			"foo-bar": 3
			s: 4
			lm1: [2, 3, 4, 6]
			lm2: []
			la: [2, 3, 4]
		}
		`,
		kind: Modified,
		diff: `  {
      ls: [2, 3, 4]
-     "foo-bar": 2
+     "foo-bar": 3
      s: 4
      lm1: [
          2,
          3,
-         5,
+         4,
+         6,
      ]
      lm2: [
-         6,
      ]
+     la: [2, 3, 4]
  }
`,
	}, {
		name: "interupted run 1",
		x: `{
	a: 1
	b: 2
	c: 3
	d: 4
	e: 10
	f: 6
	g: 7
	h: 8
	i: 9
	j: 10
}
`,
		y: `
{
	a: 1
	b: 2
	c: 3
	d: 4
	e: 5
	f: 6
	g: 7
	h: 8
	i: 9
	j: 10
}
`,
		kind: Modified,
		diff: `  {
      ... // 2 identical elements
      c: 3
      d: 4
-     e: 10
+     e: 5
      f: 6
      g: 7
      ... // 3 identical elements
  }
`,
	}, {
		name: "interupted run 2",
		x: `{
			a: -1
			b: 2
			c: 3
			d: 4
			e: 5
			f: 6
			g: 7
			h: 8
			i: 9
			j: -10
		}`,
		y: `{
			a: 1
			b: 2
			c: 3
			d: 4
			e: 5
			f: 6
			g: 7
			h: 8
			i: 9
			j: 10
		}
		`,
		kind: Modified,
		diff: `  {
-     a: -1
+     a: 1
      b: 2
      c: 3
      ... // 4 identical elements
      h: 8
      i: 9
-     j: -10
+     j: 10
  }
`,
	}, {
		name: "recursion",
		x: `{
		s: {
			a: 1
			b: 3
			d: 4
		}
		l: [
			[3, 4]
		]
	}`,
		y: `{
		s: {
			a: 2
			b: 3
			c: 4
		}
		l: [
			[3, 5, 6]
		]
	}
	`,
		kind: Modified,
		diff: `  {
      s: {
-         a: 1
+         a: 2
          b: 3
-         d: 4
+         c: 4
      }
      l: [
          [
              3,
-             4,
+             5,
+             6,
          ]
      ]
  }
`,
	}, {
		name: "optional and definitions",
		x: `{
	#s: {
		#a: 1
		b: 2
	}
	o?: 3
	#od?: 1
	oc?: 5
}`,
		y: `{
	#s: {
		a: 2
		#b: 2
	}
	o?: 4
	#od: 1
	#oc?: 5
}
`,
		kind: Modified,
		diff: `  {
      #s: {
-         #a: 1
-         b: 2
+         a: 2
+         #b: 2
      }
-     o?: 3
+     o?: 4
-     #od?: 1
-     oc?: 5
+     #od: 1
+     #oc?: 5
  }
`,
	}, {
		name: "bulk optional",
		x: `{[_]: x: "hello"}

a: x: "hello"
		`,
		y: `[_]: x: "hello"

		`,
		kind: Modified,
		diff: `  {
-     a: {
-     	x: "hello"
-     }
  }
`,
	}, {
		x: `
		#Directory: {
			{
					// Directory from another directory (e.g. subdirectory)
					from: #Directory
			} | {
					// Reference to remote directory
					ref: string
			} | {
					// Use a local directory
					local: string
			}
			path: string | *"/"
	}
		`,
		y: `
	#Directory: {
        {
                // Directory from another directory (e.g. subdirectory)
                from: #Directory
        } | {
                // Reference to remote directory
                ref: string
        } | {
                // Use a local directory
                local: string
        }
        path: string | *"/"
	}
	`,
		profile: Final,
	}, {
		x: `
		#Directory: {
			{
					// Directory from another directory (e.g. subdirectory)
					from: #Directory
			} | {
					// Reference to remote directory
					ref: string
			} | {
					// Use a local directory
					local: string
			}
			path: string | *"/"
	}
		`,
		y: `
	#Directory: {
        {
                // Directory from another directory (e.g. subdirectory)
                from: #Directory
        } | {
                // Reference to remote directory
                ref: string
        } | {
                // Use a local directory
                local: string
        }
        path: string | *"/"
	}
`,
	}, {
		name: "hidden fields",
		x:    `{a: 1, _hidden1: 1, _hidden: 1}`,
		y:    `{a: 1, _hidden2: 1, _hidden: 2}`,
		diff: `  {
      a: 1
-     _hidden1: 1
+     _hidden2: 1
-     _hidden: 1
+     _hidden: 2
  }
`,
		kind: Modified,
	}, {
		name:    "ignore hidden fields in schema",
		x:       `{a: 1, _hidden1: 1, _hidden: 1}`,
		y:       `{a: 1, _hidden2: 1, _hidden: 2}`,
		profile: &Profile{SkipHidden: true},
	}, {
		name:    "ignore hidden fields in data",
		x:       `{a: 1, _hidden1: 1, _hidden: 1}`,
		y:       `{a: 1, _hidden2: 1, _hidden: 2}`,
		profile: &Profile{SkipHidden: true, Concrete: true},
	}, {
		name: "all errors are equal",
		x:    `1 & 3`,
		y:    `1 & 4`,
	}}
	for _, tc := range testCases {
		cuetdtest.FullMatrix.Run(t, tc.name, func(t *testing.T, m *cuetdtest.M) {
			ctx := m.CueContext()
			// it is not fatal if x or y contain errors: some test cases
			// rely on interacting with such errors.
			x := ctx.CompileString(tc.x, cue.Filename("x"))
			y := ctx.CompileString(tc.y, cue.Filename("y"))
			p := tc.profile
			if p == nil {
				p = Schema
			}
			kind, script := p.Diff(x, y)
			if kind != tc.kind {
				t.Fatalf("got %d; want %d", kind, tc.kind)
			}
			if script != nil {
				w := &bytes.Buffer{}
				err := Print(w, script)
				if err != nil {
					t.Fatal(err)
				}
				if got := w.String(); got != tc.diff {
					t.Errorf("\ngot\n%s;\nwant\n%s", got, tc.diff)
				}
			}
		})
	}
}

func TestX(t *testing.T) {
	t.Skip()

	tc := struct {
		x, y string
		kind Kind
		diff string
	}{
		x: `{
		}
		`,
		y: `
		{
		}
		`,
		kind: Modified,
		diff: ``,
	}
	ctx := cuecontext.New()
	// it is not fatal if x or y contain errors: some test cases
	// rely on interacting with such errors.
	x := ctx.CompileString(tc.x, cue.Filename("x"))
	y := ctx.CompileString(tc.y, cue.Filename("y"))

	kind, script := Diff(x, y)
	if kind != tc.kind {
		t.Fatalf("got %d; want %d", kind, tc.kind)
	}
	w := &bytes.Buffer{}
	err := Print(w, script)
	if err != nil {
		t.Fatal(err)
	}
	if got := w.String(); got != tc.diff {
		t.Errorf("got\n%s;\nwant\n%s", got, tc.diff)
	}
}
