// Copyright 2021 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 cue

import (
	"fmt"
	"testing"

	"cuelang.org/go/cue/errors"
)

func TestAttributes(t *testing.T) {
	const config = `
	a: {
		a: 0 @foo(a,b,c=1)
		b: 1 @bar(a,b,c,d=1) @foo(a,,d=1)
	}
	b: {
		@embed(foo)
		3
	} @field(foo)

	c1: {} @step(1)
	if true {
		c2: { @step(2a) } @step(2b)
		@step(2c)
	}
	c3: {} @step(3)
	if false {
		c4: { @step(4a) } @step(4b)
		@step(4c)
	}
	`

	testCases := []struct {
		flags AttrKind
		path  string
		out   string
	}{{
		flags: FieldAttr,
		path:  "a.a",
		out:   "[@foo(a,b,c=1)]",
	}, {
		flags: FieldAttr,
		path:  "a.b",
		out:   "[@bar(a,b,c,d=1) @foo(a,,d=1)]",
	}, {
		flags: DeclAttr,
		path:  "b",
		out:   "[@embed(foo)]",
	}, {
		flags: FieldAttr,
		path:  "b",
		out:   "[@field(foo)]",
	}, {
		flags: ValueAttr,
		path:  "b",
		out:   "[@field(foo) @embed(foo)]",
	}, {
		flags: ValueAttr,
		path:  "c1",
		out:   "[@step(1)]",
	}, {
		flags: DeclAttr,
		path:  "c2",
		out:   "[@step(2a)]",
	}, {
		flags: FieldAttr,
		path:  "c2",
		out:   "[@step(2b)]",
	}, {
		flags: DeclAttr,
		path:  "",
		out:   "[@step(2c)]",
	}, {
		flags: ValueAttr | FieldAttr,
		path:  "c3",
		out:   "[@step(3)]",
	}, {
		flags: ValueAttr | FieldAttr,
		path:  "c4",
		out:   "[]",
	}}
	for _, tc := range testCases {
		t.Run(tc.path, func(t *testing.T) {
			v := getInstance(t, config).Value().LookupPath(ParsePath(tc.path))
			a := v.Attributes(tc.flags)
			got := fmt.Sprint(a)
			if got != tc.out {
				t.Errorf("got %v; want %v", got, tc.out)
			}

		})
	}
}

func TestAttributeErr(t *testing.T) {
	const config = `
	a: {
		a: 0 @foo(a,b,c=1)
		b: 1 @bar(a,b,c,d=1) @foo(a,,d=1)
	}
	`
	testCases := []struct {
		path string
		attr string
		err  error
	}{{
		path: "a",
		attr: "foo",
		err:  nil,
	}, {
		path: "a",
		attr: "bar",
		err:  errors.New(`attribute "bar" does not exist`),
	}, {
		path: "xx",
		attr: "bar",
		err:  errors.New(`attribute "bar" does not exist`),
	}, {
		path: "e",
		attr: "bar",
		err:  errors.New(`attribute "bar" does not exist`),
	}}
	for _, tc := range testCases {
		t.Run(tc.path+"-"+tc.attr, func(t *testing.T) {
			v := getInstance(t, config).Value().Lookup("a", tc.path)
			a := v.Attribute(tc.attr)
			err := a.Err()
			if !cmpError(err, tc.err) {
				t.Errorf("got %v; want %v", err, tc.err)
			}
		})
	}
}

func TestAttributeName(t *testing.T) {
	const config = `
	a: 0 @foo(a,b,c=1) @bar()
	`
	v := getInstance(t, config).Value().Lookup("a")
	a := v.Attribute("foo")
	if got, want := a.Name(), "foo"; got != want {
		t.Errorf("got %v; want %v", got, want)
	}
}

func TestAttributeString(t *testing.T) {
	const config = `
	a: {
		a: 0 @foo(a,b,c=1)
		b: 1 @bar(a,b,c,d=1) @foo(a,,d=1,e="x y","f g")
	}
	`
	testCases := []struct {
		path string
		attr string
		pos  int
		str  string
		err  error
	}{{
		path: "a",
		attr: "foo",
		pos:  0,
		str:  "a",
	}, {
		path: "a",
		attr: "foo",
		pos:  2,
		str:  "c=1",
	}, {
		path: "b",
		attr: "bar",
		pos:  3,
		str:  "d=1",
	}, {
		path: "b",
		attr: "foo",
		pos:  3,
		str:  `e="x y"`,
	}, {
		path: "b",
		attr: "foo",
		pos:  4,
		str:  `f g`,
	}, {
		path: "e",
		attr: "bar",
		err:  errors.New(`attribute "bar" does not exist`),
	}, {
		path: "b",
		attr: "foo",
		pos:  5,
		err:  errors.New("field does not exist"),
	}}
	for _, tc := range testCases {
		t.Run(fmt.Sprintf("%s.%s:%d", tc.path, tc.attr, tc.pos), func(t *testing.T) {
			v := getInstance(t, config).Value().Lookup("a", tc.path)
			a := v.Attribute(tc.attr)
			got, err := a.String(tc.pos)
			if !cmpError(err, tc.err) {
				t.Errorf("err: got %v; want %v", err, tc.err)
			}
			if got != tc.str {
				t.Errorf("str: got %v; want %v", got, tc.str)
			}
		})
	}
}

func TestAttributeArg(t *testing.T) {
	const config = `
	a: 1 @foo(a,,d=1,e="x y","f g", with spaces ,  s=  spaces in value  )
	`
	testCases := []struct {
		pos int
		key string
		val string
		raw string
	}{{
		pos: 0,
		key: "a",
		val: "",
		raw: "a",
	}, {
		pos: 1,
		key: "",
		val: "",
		raw: "",
	}, {
		pos: 2,
		key: "d",
		val: "1",
		raw: "d=1",
	}, {
		pos: 3,
		key: "e",
		val: "x y",
		raw: `e="x y"`,
	}, {
		pos: 4,
		key: "f g",
		val: "",
		raw: `"f g"`,
	}, {
		pos: 5,
		key: "with spaces",
		val: "",
		raw: " with spaces ",
	}, {
		pos: 6,
		key: "s",
		val: "spaces in value",
		raw: "  s=  spaces in value  ",
	}}
	for _, tc := range testCases {
		t.Run(fmt.Sprintf("%d", tc.pos), func(t *testing.T) {
			v := getInstance(t, config).Value().Lookup("a")
			a := v.Attribute("foo")
			key, val := a.Arg(tc.pos)
			raw := a.RawArg(tc.pos)
			if got, want := key, tc.key; got != want {
				t.Errorf("unexpected key; got %q want %q", got, want)
			}
			if got, want := val, tc.val; got != want {
				t.Errorf("unexpected value; got %q want %q", got, want)
			}
			if got, want := raw, tc.raw; got != want {
				t.Errorf("unexpected raw value; got %q want %q", got, want)
			}
		})
	}
}

func TestAttributeInt(t *testing.T) {
	const config = `
	a: {
		a: 0 @foo(1,3,c=1)
		b: 1 @bar(a,-4,c,d=1) @foo(a,,d=1)
	}
	`
	testCases := []struct {
		path string
		attr string
		pos  int
		val  int64
		err  error
	}{{
		path: "a",
		attr: "foo",
		pos:  0,
		val:  1,
	}, {
		path: "b",
		attr: "bar",
		pos:  1,
		val:  -4,
	}, {
		path: "e",
		attr: "bar",
		err:  errors.New(`attribute "bar" does not exist`),
	}, {
		path: "b",
		attr: "foo",
		pos:  4,
		err:  errors.New("field does not exist"),
	}, {
		path: "a",
		attr: "foo",
		pos:  2,
		err:  errors.New(`strconv.ParseInt: parsing "c=1": invalid syntax`),
	}}
	for _, tc := range testCases {
		t.Run(fmt.Sprintf("%s.%s:%d", tc.path, tc.attr, tc.pos), func(t *testing.T) {
			v := getInstance(t, config).Value().Lookup("a", tc.path)
			a := v.Attribute(tc.attr)
			got, err := a.Int(tc.pos)
			if !cmpError(err, tc.err) {
				t.Errorf("err: got %v; want %v", err, tc.err)
			}
			if got != tc.val {
				t.Errorf("val: got %v; want %v", got, tc.val)
			}
		})
	}
}

func TestAttributeFlag(t *testing.T) {
	const config = `
	a: {
		a: 0 @foo(a,b,c=1)
		b: 1 @bar(a,b,c,d=1) @foo(a,,d=1)
	}
	`
	testCases := []struct {
		path string
		attr string
		pos  int
		flag string
		val  bool
		err  error
	}{{
		path: "a",
		attr: "foo",
		pos:  0,
		flag: "a",
		val:  true,
	}, {
		path: "b",
		attr: "bar",
		pos:  1,
		flag: "a",
		val:  false,
	}, {
		path: "b",
		attr: "bar",
		pos:  0,
		flag: "c",
		val:  true,
	}, {
		path: "e",
		attr: "bar",
		err:  errors.New(`attribute "bar" does not exist`),
	}, {
		path: "b",
		attr: "foo",
		pos:  4,
		err:  errors.New("field does not exist"),
	}}
	for _, tc := range testCases {
		t.Run(fmt.Sprintf("%s.%s:%d", tc.path, tc.attr, tc.pos), func(t *testing.T) {
			v := getInstance(t, config).Value().Lookup("a", tc.path)
			a := v.Attribute(tc.attr)
			got, err := a.Flag(tc.pos, tc.flag)
			if !cmpError(err, tc.err) {
				t.Errorf("err: got %v; want %v", err, tc.err)
			}
			if got != tc.val {
				t.Errorf("val: got %v; want %v", got, tc.val)
			}
		})
	}
}

func TestAttributeLookup(t *testing.T) {
	const config = `
	a: {
		a: 0 @foo(a,b,c=1)
		b: 1 @bar(a,b,e =-5,d=1) @foo(a,,d=1)
	}
	`
	testCases := []struct {
		path string
		attr string
		pos  int
		key  string
		val  string
		err  error
	}{{
		path: "a",
		attr: "foo",
		pos:  0,
		key:  "c",
		val:  "1",
	}, {
		path: "b",
		attr: "bar",
		pos:  1,
		key:  "a",
		val:  "",
	}, {
		path: "b",
		attr: "bar",
		pos:  0,
		key:  "e",
		val:  "-5",
	}, {
		path: "b",
		attr: "bar",
		pos:  0,
		key:  "d",
		val:  "1",
	}, {
		path: "b",
		attr: "foo",
		pos:  2,
		key:  "d",
		val:  "1",
	}, {
		path: "b",
		attr: "foo",
		pos:  2,
		key:  "f",
		val:  "",
	}, {
		path: "e",
		attr: "bar",
		err:  errors.New(`attribute "bar" does not exist`),
	}, {
		path: "b",
		attr: "foo",
		pos:  4,
		err:  errors.New("field does not exist"),
	}}
	for _, tc := range testCases {
		t.Run(fmt.Sprintf("%s.%s:%d", tc.path, tc.attr, tc.pos), func(t *testing.T) {
			v := getInstance(t, config).Value().Lookup("a", tc.path)
			a := v.Attribute(tc.attr)
			got, _, err := a.Lookup(tc.pos, tc.key)
			if !cmpError(err, tc.err) {
				t.Errorf("err: got %v; want %v", err, tc.err)
			}
			if got != tc.val {
				t.Errorf("val: got %v; want %v", got, tc.val)
			}
		})
	}
}
