// 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 trim

import (
	"testing"

	"cuelang.org/go/cue"
	"cuelang.org/go/cue/ast"
	"cuelang.org/go/cue/cuecontext"
	"cuelang.org/go/cue/errors"
	"cuelang.org/go/cue/format"
	"cuelang.org/go/cue/parser"
	"cuelang.org/go/internal/cuetxtar"
	"golang.org/x/tools/txtar"
)

func TestFiles(t *testing.T) {
	testCases := []struct {
		name string
		in   string
		out  string
	}{{
		name: "optional does not remove required",
		in: `
		a: ["aFoo"]: 3
		a: aFoo:     _

		a: {
			{["aFoo"]: 3}
			aFoo:     _
		}

		["aFoo"]: 3
		aFoo:     _
		`,
		out: `a: ["aFoo"]: 3
a: aFoo:     _

a: {
	{["aFoo"]: 3}
	aFoo: _
}

["aFoo"]: 3
aFoo:     _
`,
	}, {
		// TODO: make this optional
		name: "defaults can remove non-defaults",
		in: `
		foo: [string]: a: *1 | int
		foo: b: a: 1
		`,
		out: `foo: [string]: a: *1 | int
foo: b: {}
`,
	}, {
		name: "remove top-level struct",
		in: `
		a: b: 3
		for k, v in a {
			c: "\(k)": v
		}
		c: b: 3

		z: {

			a: b: 3
			for k, v in a {
				c: "\(k)": v
			}
			c: b: 3
		}
		`,
		out: `a: b: 3
for k, v in a {
	c: "\(k)": v
}

z: {

	a: b: 3
	for k, v in a {
		c: "\(k)": v
	}
}
`,
	}, {
		name: "do not remove field",
		in: `
		{[_]: x: "hello"}
		a: x: "hello"
		`,
		out: `
{[_]: x: "hello"}
a: {}
`,
	}, {
		name: "issue303",
		in: `
		foo: c: true
		foo: #M
		#M: c?: bool
		`,
		out: `foo: c: true
foo: #M
#M: c?: bool
`,
	}, {
		name: "remove due to simplification",
		in: `
foo: [string]: {
	t: [string]: {
		x: >=0 & <=5
	}
}

foo: multipath: {
	t: [string]: {
		// Combined with the other constraints, we know the value must be 5 and
		// thus the entry below can be eliminated.
		x: >=5 & <=8 & int
	}

	t: u: { x: 5 }
}

group: {
	for k, v in foo {
		comp: "\(k)": v
	}
}

		`,
		out: `foo: [string]: {
	t: [string]: {
		x: >=0 & <=5
	}
}

foo: multipath: {
	t: [string]: {

		x: >=5 & <=8 & int
	}

	t: u: {}
}

group: {
	for k, v in foo {
		comp: "\(k)": v
	}
}
`,
	}, {
		name: "list removal",
		in: `
		service: [string]: {
			ports: [{a: 1}, {a: 1}, ...{ extra: 3 }]
		}
		service: a: {
			ports: [{a: 1}, {a: 1, extra: 3}, {}, { extra: 3 }]
		}
`,
		out: `service: [string]: {
	ports: [{a: 1}, {a: 1}, ...{extra: 3}]
}
service: a: {
	ports: [{}, {extra: 3}, {}, {}]
}
`,
	}, {
		name: "list removal",
		in: `
		service: [string]: {
			ports: [{a: 1}, {a: 1}, ...{ extra: 3 }]
		}
		service: a: {
			ports: [{a: 1}, {a: 1,}]
		}
		`,
		out: `service: [string]: {
	ports: [{a: 1}, {a: 1}, ...{extra: 3}]
}
service: a: {
}
`,
	}, {
		name: "do not overmark comprehension",
		in: `
foo: multipath: {
	t: [string]: { x: 5 }

	// Don't remove u!
	t: u: { x: 5 }
}

group: {
	for k, v in foo {
		comp: "\(k)": v
	}
}

	`,
		out: `foo: multipath: {
	t: [string]: {x: 5}

	t: u: {}
}

group: {
	for k, v in foo {
		comp: "\(k)": v
	}
}
`,
	}, {
		name: "remove implied interpolations",
		in: `
				foo: [string]: {
					a: string
					b: "--\(a)--"
				}
				foo: entry: {
					a: "insert"
					b: "--insert--"
				}
				`,
		out: `foo: [string]: {
	a: string
	b: "--\(a)--"
}
foo: entry: {
	a: "insert"
}
`,
	}}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			f, err := parser.ParseFile("test", tc.in)
			if err != nil {
				t.Fatal(err)
			}
			r := cuecontext.New()
			v := r.BuildFile(f)
			if err := v.Err(); err != nil {
				t.Fatal(err)
			}
			err = Files([]*ast.File{f}, v, &Config{Trace: false})
			if err != nil {
				t.Fatal(err)
			}

			out := formatNode(t, f)
			if got := string(out); got != tc.out {
				t.Errorf("\ngot:\n%s\nwant:\n%s", got, tc.out)
			}
		})
	}
}

const trace = false

func TestData(t *testing.T) {
	test := cuetxtar.TxTarTest{
		Root: "./testdata",
		Name: "trim",
	}

	test.Run(t, func(t *cuetxtar.Test) {

		a := t.Instance()
		val := cuecontext.New().BuildInstance(a)
		// Note: don't check val.Err because there are deliberate
		// errors in some tests.

		files := a.Files

		err := Files(files, val, &Config{Trace: trace})
		if err != nil {
			t.WriteErrors(errors.Promote(err, ""))
		}

		for _, f := range files {
			t.WriteFile(f)
		}
	})
}

func formatNode(t *testing.T, n ast.Node) []byte {
	t.Helper()

	b, err := format.Node(n)
	if err != nil {
		t.Fatal(err)
	}
	return b
}

// For debugging, do not remove.
func TestX(t *testing.T) {
	in := `
-- in.cue --
`

	t.Skip()

	a := txtar.Parse([]byte(in))
	instances := cuetxtar.Load(a, t.TempDir())

	inst := cue.Build(instances)[0]
	if inst.Err != nil {
		t.Fatal(inst.Err)
	}

	files := instances[0].Files

	Debug = true

	err := Files(files, inst, &Config{
		Trace: true,
	})
	if err != nil {
		t.Fatal(err)
	}

	for _, f := range files {
		b := formatNode(t, f)
		t.Error(string(b))
	}
}
