package cty

import (
	"fmt"
	"reflect"
	"testing"
)

func TestValueEquals(t *testing.T) {
	capsuleA := CapsuleVal(capsuleTestType1, &capsuleTestType1Native{"capsuleA"})
	capsuleB := CapsuleVal(capsuleTestType1, &capsuleTestType1Native{"capsuleB"})
	capsuleC := CapsuleVal(capsuleTestType2, &capsuleTestType2Native{"capsuleC"})

	tests := []struct {
		LHS      Value
		RHS      Value
		Expected Value
	}{
		// Booleans
		{
			BoolVal(true),
			BoolVal(true),
			BoolVal(true),
		},
		{
			BoolVal(false),
			BoolVal(false),
			BoolVal(true),
		},
		{
			BoolVal(true),
			BoolVal(false),
			BoolVal(false),
		},

		// Numbers
		{
			NumberIntVal(1),
			NumberIntVal(2),
			BoolVal(false),
		},
		{
			NumberIntVal(2),
			NumberIntVal(2),
			BoolVal(true),
		},

		// Strings
		{
			StringVal(""),
			StringVal(""),
			BoolVal(true),
		},
		{
			StringVal("hello"),
			StringVal("hello"),
			BoolVal(true),
		},
		{
			StringVal("hello"),
			StringVal("world"),
			BoolVal(false),
		},
		{
			StringVal("0"),
			StringVal(""),
			BoolVal(false),
		},
		{
			StringVal("años"),
			StringVal("años"),
			BoolVal(true),
		},
		{
			// Combining marks are normalized by StringVal
			StringVal("años"),  // (precomposed tilde-n)
			StringVal("años"), // (combining tilde followed by bare n)
			BoolVal(true),
		},
		{
			// tilde-n does not normalize with bare n
			StringVal("años"),
			StringVal("anos"),
			BoolVal(false),
		},

		// Objects
		{
			ObjectVal(map[string]Value{}),
			ObjectVal(map[string]Value{}),
			BoolVal(true),
		},
		{
			ObjectVal(map[string]Value{
				"num": NumberIntVal(1),
			}),
			ObjectVal(map[string]Value{
				"num": NumberIntVal(1),
			}),
			BoolVal(true),
		},
		{
			ObjectVal(map[string]Value{
				"num": NumberIntVal(1),
			}),
			ObjectVal(map[string]Value{}),
			BoolVal(false),
		},
		{
			ObjectVal(map[string]Value{
				"num":  NumberIntVal(1),
				"flag": BoolVal(true),
			}),
			ObjectVal(map[string]Value{
				"num":  NumberIntVal(1),
				"flag": BoolVal(true),
			}),
			BoolVal(true),
		},
		{
			ObjectVal(map[string]Value{
				"num": NumberIntVal(1),
			}),
			ObjectVal(map[string]Value{
				"num": NumberIntVal(2),
			}),
			BoolVal(false),
		},
		{
			ObjectVal(map[string]Value{
				"num": NumberIntVal(1),
			}),
			ObjectVal(map[string]Value{
				"othernum": NumberIntVal(1),
			}),
			BoolVal(false),
		},
		{
			ObjectVal(map[string]Value{
				"num":  NumberIntVal(1),
				"flag": BoolVal(true),
			}),
			ObjectVal(map[string]Value{
				"num": NumberIntVal(1),
			}),
			BoolVal(false),
		},
		{
			ObjectVal(map[string]Value{
				"num":  NumberIntVal(1),
				"flag": BoolVal(true),
			}),
			ObjectVal(map[string]Value{
				"num":  NumberIntVal(1),
				"flag": BoolVal(false),
			}),
			BoolVal(false),
		},

		// Tuples
		{
			EmptyTupleVal,
			EmptyTupleVal,
			BoolVal(true),
		},
		{
			TupleVal([]Value{NumberIntVal(1)}),
			TupleVal([]Value{NumberIntVal(1)}),
			BoolVal(true),
		},
		{
			TupleVal([]Value{NumberIntVal(1)}),
			TupleVal([]Value{NumberIntVal(2)}),
			BoolVal(false),
		},
		{
			TupleVal([]Value{StringVal("hi")}),
			TupleVal([]Value{NumberIntVal(1)}),
			BoolVal(false),
		},
		{
			TupleVal([]Value{NumberIntVal(1)}),
			TupleVal([]Value{NumberIntVal(1), NumberIntVal(2)}),
			BoolVal(false),
		},
		{
			TupleVal([]Value{NumberIntVal(1), NumberIntVal(2)}),
			TupleVal([]Value{NumberIntVal(1)}),
			BoolVal(false),
		},
		{
			TupleVal([]Value{NumberIntVal(1), NumberIntVal(2)}),
			TupleVal([]Value{NumberIntVal(1), NumberIntVal(2)}),
			BoolVal(true),
		},
		{
			TupleVal([]Value{UnknownVal(Number)}),
			TupleVal([]Value{NumberIntVal(1)}),
			UnknownVal(Bool),
		},
		{
			TupleVal([]Value{UnknownVal(Number)}),
			TupleVal([]Value{UnknownVal(Number)}),
			UnknownVal(Bool),
		},
		{
			TupleVal([]Value{NumberIntVal(1)}),
			TupleVal([]Value{UnknownVal(Number)}),
			UnknownVal(Bool),
		},
		{
			TupleVal([]Value{NumberIntVal(1)}),
			TupleVal([]Value{DynamicVal}),
			UnknownVal(Bool),
		},
		{
			TupleVal([]Value{DynamicVal}),
			TupleVal([]Value{NumberIntVal(1)}),
			UnknownVal(Bool),
		},
		{
			TupleVal([]Value{NumberIntVal(1)}),
			UnknownVal(Tuple([]Type{Number})),
			UnknownVal(Bool),
		},
		{
			UnknownVal(Tuple([]Type{Number})),
			TupleVal([]Value{NumberIntVal(1)}),
			UnknownVal(Bool),
		},
		{
			DynamicVal,
			TupleVal([]Value{NumberIntVal(1)}),
			UnknownVal(Bool),
		},
		{
			TupleVal([]Value{NumberIntVal(1)}),
			DynamicVal,
			UnknownVal(Bool),
		},

		// Lists
		{
			ListValEmpty(Number),
			ListValEmpty(Number),
			BoolVal(true),
		},
		{
			ListValEmpty(Number),
			ListValEmpty(Bool),
			BoolVal(false),
		},
		{
			ListVal([]Value{
				NumberIntVal(1),
			}),
			ListVal([]Value{
				NumberIntVal(1),
			}),
			BoolVal(true),
		},
		{
			ListVal([]Value{
				NumberIntVal(1),
			}),
			ListValEmpty(String),
			BoolVal(false),
		},
		{
			ListVal([]Value{
				NumberIntVal(1),
				NumberIntVal(2),
			}),
			ListVal([]Value{
				NumberIntVal(1),
				NumberIntVal(2),
			}),
			BoolVal(true),
		},
		{
			ListVal([]Value{
				NumberIntVal(1),
			}),
			ListVal([]Value{
				NumberIntVal(2),
			}),
			BoolVal(false),
		},
		{
			ListVal([]Value{
				NumberIntVal(1),
				NumberIntVal(2),
			}),
			ListVal([]Value{
				NumberIntVal(1),
			}),
			BoolVal(false),
		},
		{
			ListVal([]Value{
				NumberIntVal(1),
			}),
			ListVal([]Value{
				NumberIntVal(1),
				NumberIntVal(2),
			}),
			BoolVal(false),
		},

		// Maps
		{
			MapValEmpty(Number),
			MapValEmpty(Number),
			BoolVal(true),
		},
		{
			MapValEmpty(Number),
			MapValEmpty(Bool),
			BoolVal(false),
		},
		{
			MapVal(map[string]Value{
				"num": NumberIntVal(1),
			}),
			MapVal(map[string]Value{
				"num": NumberIntVal(1),
			}),
			BoolVal(true),
		},
		{
			MapVal(map[string]Value{
				"num": NumberIntVal(1),
			}),
			MapValEmpty(String),
			BoolVal(false),
		},
		{
			MapVal(map[string]Value{
				"num1": NumberIntVal(1),
				"num2": NumberIntVal(2),
			}),
			MapVal(map[string]Value{
				"num1": NumberIntVal(1),
				"num2": NumberIntVal(2),
			}),
			BoolVal(true),
		},
		{
			MapVal(map[string]Value{
				"num": NumberIntVal(1),
			}),
			MapVal(map[string]Value{
				"num": NumberIntVal(2),
			}),
			BoolVal(false),
		},
		{
			MapVal(map[string]Value{
				"num": NumberIntVal(1),
			}),
			MapVal(map[string]Value{
				"othernum": NumberIntVal(1),
			}),
			BoolVal(false),
		},
		{
			MapVal(map[string]Value{
				"num1": NumberIntVal(1),
				"num2": NumberIntVal(2),
			}),
			MapVal(map[string]Value{
				"num1": NumberIntVal(1),
			}),
			BoolVal(false),
		},
		{
			MapVal(map[string]Value{
				"num1": NumberIntVal(1),
			}),
			MapVal(map[string]Value{
				"num1": NumberIntVal(1),
				"num2": NumberIntVal(2),
			}),
			BoolVal(false),
		},
		{
			MapVal(map[string]Value{
				"num1": NumberIntVal(1),
				"num2": NumberIntVal(2),
			}),
			MapVal(map[string]Value{
				"num1": NumberIntVal(1),
				"num2": NumberIntVal(3),
			}),
			BoolVal(false),
		},

		// Sets
		{
			SetValEmpty(Number),
			SetValEmpty(Number),
			BoolVal(true),
		},
		{
			SetValEmpty(Number),
			SetValEmpty(Bool),
			BoolVal(false),
		},
		{
			SetVal([]Value{
				NumberIntVal(1),
			}),
			SetVal([]Value{
				NumberIntVal(1),
			}),
			BoolVal(true),
		},
		{
			SetVal([]Value{
				NumberIntVal(1),
			}),
			SetValEmpty(String),
			BoolVal(false),
		},
		{
			SetVal([]Value{
				NumberIntVal(1),
				NumberIntVal(2),
			}),
			SetVal([]Value{
				NumberIntVal(2),
				NumberIntVal(1),
			}),
			BoolVal(true),
		},
		{
			SetVal([]Value{
				NumberIntVal(1),
			}),
			SetVal([]Value{
				NumberIntVal(2),
			}),
			BoolVal(false),
		},
		{
			SetVal([]Value{
				NumberIntVal(1),
				NumberIntVal(2),
			}),
			SetVal([]Value{
				NumberIntVal(1),
			}),
			BoolVal(false),
		},
		{
			SetVal([]Value{
				NumberIntVal(1),
			}),
			SetVal([]Value{
				NumberIntVal(1),
				NumberIntVal(2),
			}),
			BoolVal(false),
		},

		// Capsules
		{
			capsuleA,
			capsuleA,
			True,
		},
		{
			capsuleA,
			capsuleB,
			False,
		},
		{
			capsuleA,
			capsuleC,
			False,
		},
		{
			capsuleA,
			UnknownVal(capsuleTestType1), // same type
			UnknownVal(Bool),
		},
		{
			capsuleA,
			UnknownVal(capsuleTestType2), // different type
			False,
		},

		// Unknowns and Dynamics
		{
			NumberIntVal(2),
			UnknownVal(Number),
			UnknownVal(Bool),
		},
		{
			NumberIntVal(1),
			DynamicVal,
			UnknownVal(Bool),
		},
		{
			DynamicVal,
			BoolVal(true),
			UnknownVal(Bool),
		},
		{
			DynamicVal,
			DynamicVal,
			UnknownVal(Bool),
		},
		{
			ListVal([]Value{
				StringVal("hi"),
				DynamicVal,
			}),
			ListVal([]Value{
				StringVal("hi"),
				DynamicVal,
			}),
			UnknownVal(Bool),
		},
		{
			ListVal([]Value{
				StringVal("hi"),
				UnknownVal(String),
			}),
			ListVal([]Value{
				StringVal("hi"),
				UnknownVal(String),
			}),
			UnknownVal(Bool),
		},
		{
			MapVal(map[string]Value{
				"static":  StringVal("hi"),
				"dynamic": DynamicVal,
			}),
			MapVal(map[string]Value{
				"static":  StringVal("hi"),
				"dynamic": DynamicVal,
			}),
			UnknownVal(Bool),
		},
		{
			MapVal(map[string]Value{
				"static":  StringVal("hi"),
				"dynamic": UnknownVal(String),
			}),
			MapVal(map[string]Value{
				"static":  StringVal("hi"),
				"dynamic": UnknownVal(String),
			}),
			UnknownVal(Bool),
		},
	}

	for _, test := range tests {
		t.Run(fmt.Sprintf("%#v.Equals(%#v)", test.LHS, test.RHS), func(t *testing.T) {
			got := test.LHS.Equals(test.RHS)
			if !got.RawEquals(test.Expected) {
				t.Fatalf("Equals returned %#v; want %#v", got, test.Expected)
			}
		})
	}
}

func TestValueAdd(t *testing.T) {
	tests := []struct {
		LHS      Value
		RHS      Value
		Expected Value
	}{
		{
			NumberIntVal(1),
			NumberIntVal(2),
			NumberIntVal(3),
		},
		{
			NumberIntVal(1),
			NumberIntVal(-2),
			NumberIntVal(-1),
		},
		{
			NumberIntVal(1),
			NumberFloatVal(0.5),
			NumberFloatVal(1.5),
		},
		{
			NumberIntVal(1),
			UnknownVal(Number),
			UnknownVal(Number),
		},
		{
			UnknownVal(Number),
			UnknownVal(Number),
			UnknownVal(Number),
		},
		{
			NumberIntVal(1),
			DynamicVal,
			UnknownVal(Number),
		},
		{
			DynamicVal,
			DynamicVal,
			UnknownVal(Number),
		},
	}

	for _, test := range tests {
		t.Run(fmt.Sprintf("%#v.Add(%#v)", test.LHS, test.RHS), func(t *testing.T) {
			got := test.LHS.Add(test.RHS)
			if !got.RawEquals(test.Expected) {
				t.Fatalf("Add returned %#v; want %#v", got, test.Expected)
			}
		})
	}
}

func TestValueSubtract(t *testing.T) {
	tests := []struct {
		LHS      Value
		RHS      Value
		Expected Value
	}{
		{
			NumberIntVal(1),
			NumberIntVal(2),
			NumberIntVal(-1),
		},
		{
			NumberIntVal(1),
			NumberIntVal(-2),
			NumberIntVal(3),
		},
		{
			NumberIntVal(1),
			NumberFloatVal(0.5),
			NumberFloatVal(0.5),
		},
		{
			NumberIntVal(1),
			UnknownVal(Number),
			UnknownVal(Number),
		},
		{
			UnknownVal(Number),
			UnknownVal(Number),
			UnknownVal(Number),
		},
		{
			NumberIntVal(1),
			DynamicVal,
			UnknownVal(Number),
		},
		{
			DynamicVal,
			DynamicVal,
			UnknownVal(Number),
		},
	}

	for _, test := range tests {
		t.Run(fmt.Sprintf("%#v.Subtract(%#v)", test.LHS, test.RHS), func(t *testing.T) {
			got := test.LHS.Subtract(test.RHS)
			if !got.RawEquals(test.Expected) {
				t.Fatalf("Subtract returned %#v; want %#v", got, test.Expected)
			}
		})
	}
}

func TestValueNegate(t *testing.T) {
	tests := []struct {
		Receiver Value
		Expected Value
	}{
		{
			NumberIntVal(1),
			NumberIntVal(-1),
		},
		{
			NumberFloatVal(0.5),
			NumberFloatVal(-0.5),
		},
		{
			UnknownVal(Number),
			UnknownVal(Number),
		},
		{
			DynamicVal,
			UnknownVal(Number),
		},
	}

	for _, test := range tests {
		t.Run(fmt.Sprintf("%#v.Negate()", test.Receiver), func(t *testing.T) {
			got := test.Receiver.Negate()
			if !got.RawEquals(test.Expected) {
				t.Fatalf("Negate returned %#v; want %#v", got, test.Expected)
			}
		})
	}
}

func TestValueMultiply(t *testing.T) {
	tests := []struct {
		LHS      Value
		RHS      Value
		Expected Value
	}{
		{
			NumberIntVal(4),
			NumberIntVal(2),
			NumberIntVal(8),
		},
		{
			NumberIntVal(1),
			NumberIntVal(-2),
			NumberIntVal(-2),
		},
		{
			NumberIntVal(5),
			NumberFloatVal(0.5),
			NumberFloatVal(2.5),
		},
		{
			NumberIntVal(1),
			UnknownVal(Number),
			UnknownVal(Number),
		},
		{
			UnknownVal(Number),
			UnknownVal(Number),
			UnknownVal(Number),
		},
		{
			NumberIntVal(1),
			DynamicVal,
			UnknownVal(Number),
		},
		{
			DynamicVal,
			DynamicVal,
			UnknownVal(Number),
		},
	}

	for _, test := range tests {
		t.Run(fmt.Sprintf("%#v.Multiply(%#v)", test.LHS, test.RHS), func(t *testing.T) {
			got := test.LHS.Multiply(test.RHS)
			if !got.RawEquals(test.Expected) {
				t.Fatalf("Multiply returned %#v; want %#v", got, test.Expected)
			}
		})
	}
}

func TestValueDivide(t *testing.T) {
	tests := []struct {
		LHS      Value
		RHS      Value
		Expected Value
	}{
		{
			NumberIntVal(10),
			NumberIntVal(2),
			NumberIntVal(5),
		},
		{
			NumberIntVal(1),
			NumberIntVal(-2),
			NumberFloatVal(-0.5),
		},
		{
			NumberIntVal(5),
			NumberFloatVal(0.5),
			NumberIntVal(10),
		},
		{
			NumberIntVal(5),
			NumberIntVal(0),
			PositiveInfinity,
		},
		{
			NumberIntVal(-5),
			NumberIntVal(0),
			NegativeInfinity,
		},
		{
			NumberIntVal(1),
			UnknownVal(Number),
			UnknownVal(Number),
		},
		{
			UnknownVal(Number),
			UnknownVal(Number),
			UnknownVal(Number),
		},
		{
			NumberIntVal(1),
			DynamicVal,
			UnknownVal(Number),
		},
		{
			DynamicVal,
			DynamicVal,
			UnknownVal(Number),
		},
	}

	for _, test := range tests {
		t.Run(fmt.Sprintf("%#v.Divide(%#v)", test.LHS, test.RHS), func(t *testing.T) {
			got := test.LHS.Divide(test.RHS)
			if !got.RawEquals(test.Expected) {
				t.Fatalf("Divide returned %#v; want %#v", got, test.Expected)
			}
		})
	}
}

func TestValueModulo(t *testing.T) {
	tests := []struct {
		LHS      Value
		RHS      Value
		Expected Value
	}{
		{
			NumberIntVal(10),
			NumberIntVal(2),
			NumberIntVal(0),
		},
		{
			NumberIntVal(-10),
			NumberIntVal(2),
			NumberIntVal(0),
		},
		{
			NumberIntVal(11),
			NumberIntVal(2),
			NumberIntVal(1),
		},
		{
			NumberIntVal(-11),
			NumberIntVal(2),
			NumberIntVal(-1),
		},
		{
			NumberIntVal(1),
			NumberIntVal(-2),
			NumberFloatVal(1),
		},
		{
			NumberIntVal(5),
			NumberFloatVal(0.5),
			NumberIntVal(0),
		},
		{
			NumberIntVal(5),
			NumberFloatVal(1.5),
			NumberFloatVal(0.5),
		},
		{
			NumberIntVal(5),
			NumberIntVal(0),
			NumberIntVal(5),
		},
		{
			NumberIntVal(-5),
			NumberIntVal(0),
			NumberIntVal(-5),
		},
		{
			NumberIntVal(1),
			UnknownVal(Number),
			UnknownVal(Number),
		},
		{
			UnknownVal(Number),
			UnknownVal(Number),
			UnknownVal(Number),
		},
		{
			NumberIntVal(1),
			DynamicVal,
			UnknownVal(Number),
		},
		{
			DynamicVal,
			DynamicVal,
			UnknownVal(Number),
		},
	}

	for _, test := range tests {
		t.Run(fmt.Sprintf("%#v.Modulo(%#v)", test.LHS, test.RHS), func(t *testing.T) {
			got := test.LHS.Modulo(test.RHS)
			if !got.RawEquals(test.Expected) {
				t.Fatalf("Modulo returned %#v; want %#v", got, test.Expected)
			}
		})
	}
}

func TestValueAbsolute(t *testing.T) {
	tests := []struct {
		Receiver Value
		Expected Value
	}{
		{
			NumberIntVal(1),
			NumberIntVal(1),
		},
		{
			NumberIntVal(-1),
			NumberIntVal(1),
		},
		{
			NumberFloatVal(0.5),
			NumberFloatVal(0.5),
		},
		{
			NumberFloatVal(-0.5),
			NumberFloatVal(0.5),
		},
		{
			PositiveInfinity,
			PositiveInfinity,
		},
		{
			NegativeInfinity,
			PositiveInfinity,
		},
		{
			UnknownVal(Number),
			UnknownVal(Number),
		},
		{
			DynamicVal,
			UnknownVal(Number),
		},
	}

	for _, test := range tests {
		t.Run(fmt.Sprintf("%#v.Absolute()", test.Receiver), func(t *testing.T) {
			got := test.Receiver.Absolute()
			if !got.RawEquals(test.Expected) {
				t.Fatalf("Absolute returned %#v; want %#v", got, test.Expected)
			}
		})
	}
}

func TestValueGetAttr(t *testing.T) {
	tests := []struct {
		Object   Value
		AttrName string
		Expected Value
	}{
		{
			ObjectVal(map[string]Value{
				"greeting": StringVal("hello"),
			}),
			"greeting",
			StringVal("hello"),
		},
		{
			UnknownVal(Object(map[string]Type{
				"greeting": String,
			})),
			"greeting",
			UnknownVal(String),
		},
		{
			DynamicVal,
			"hello",
			DynamicVal,
		},
	}

	for _, test := range tests {
		t.Run(fmt.Sprintf("%#v.GetAttr(%q)", test.Object, test.AttrName), func(t *testing.T) {
			got := test.Object.GetAttr(test.AttrName)
			if !got.RawEquals(test.Expected) {
				t.Fatalf("GetAttr returned %#v; want %#v", got, test.Expected)
			}
		})
	}
}

func TestValueIndex(t *testing.T) {
	tests := []struct {
		Collection Value
		Key        Value
		Expected   Value
	}{
		{
			ListVal([]Value{StringVal("hello")}),
			NumberIntVal(0),
			StringVal("hello"),
		},
		{
			ListVal([]Value{StringVal("hello"), StringVal("world")}),
			NumberIntVal(1),
			StringVal("world"),
		},
		{
			ListVal([]Value{StringVal("hello")}),
			UnknownVal(Number),
			UnknownVal(String),
		},
		{
			ListVal([]Value{StringVal("hello")}),
			DynamicVal,
			UnknownVal(String),
		},
		{
			UnknownVal(List(String)),
			NumberIntVal(0),
			UnknownVal(String),
		},
		{
			MapVal(map[string]Value{"greeting": StringVal("hello")}),
			StringVal("greeting"),
			StringVal("hello"),
		},
		{
			MapVal(map[string]Value{"greeting": True}),
			UnknownVal(String),
			UnknownVal(Bool),
		},
		{
			MapVal(map[string]Value{"greeting": True}),
			DynamicVal,
			UnknownVal(Bool),
		},
		{
			UnknownVal(Map(String)),
			StringVal("greeting"),
			UnknownVal(String),
		},
		{
			DynamicVal,
			StringVal("hello"),
			DynamicVal,
		},
		{
			DynamicVal,
			NumberIntVal(0),
			DynamicVal,
		},
		{
			TupleVal([]Value{StringVal("hello")}),
			NumberIntVal(0),
			StringVal("hello"),
		},
		{
			TupleVal([]Value{StringVal("hello"), NumberIntVal(5)}),
			NumberIntVal(0),
			StringVal("hello"),
		},
		{
			TupleVal([]Value{StringVal("hello"), NumberIntVal(5)}),
			NumberIntVal(1),
			NumberIntVal(5),
		},
		{
			TupleVal([]Value{StringVal("hello"), DynamicVal}),
			NumberIntVal(0),
			StringVal("hello"),
		},
		{
			TupleVal([]Value{StringVal("hello"), DynamicVal}),
			NumberIntVal(1),
			DynamicVal,
		},
		{
			TupleVal([]Value{StringVal("hello"), UnknownVal(Number)}),
			NumberIntVal(0),
			StringVal("hello"),
		},
		{
			TupleVal([]Value{StringVal("hello"), UnknownVal(Number)}),
			NumberIntVal(1),
			UnknownVal(Number),
		},
		{
			TupleVal([]Value{StringVal("hello"), UnknownVal(Number)}),
			UnknownVal(Number),
			DynamicVal,
		},
		{
			UnknownVal(Tuple([]Type{String})),
			NumberIntVal(0),
			UnknownVal(String),
		},
	}

	for _, test := range tests {
		t.Run(fmt.Sprintf("%#v.Index(%q)", test.Collection, test.Key), func(t *testing.T) {
			got := test.Collection.Index(test.Key)
			if !got.RawEquals(test.Expected) {
				t.Fatalf("Index returned %#v; want %#v", got, test.Expected)
			}
		})
	}
}

func TestValueHasIndex(t *testing.T) {
	tests := []struct {
		Collection Value
		Key        Value
		Expected   Value
	}{
		{
			ListVal([]Value{StringVal("hello")}),
			NumberIntVal(0),
			True,
		},
		{
			ListVal([]Value{StringVal("hello"), StringVal("world")}),
			NumberIntVal(1),
			True,
		},
		{
			ListVal([]Value{StringVal("hello"), StringVal("world")}),
			NumberIntVal(2),
			False,
		},
		{
			ListVal([]Value{StringVal("hello"), StringVal("world")}),
			NumberIntVal(-1),
			False,
		},
		{
			ListVal([]Value{StringVal("hello"), StringVal("world")}),
			NumberFloatVal(0.5),
			False,
		},
		{
			ListVal([]Value{StringVal("hello"), StringVal("world")}),
			StringVal("greeting"),
			False,
		},
		{
			ListVal([]Value{StringVal("hello"), StringVal("world")}),
			True,
			False,
		},
		{
			ListVal([]Value{StringVal("hello")}),
			UnknownVal(Number),
			UnknownVal(Bool),
		},
		{
			ListVal([]Value{StringVal("hello")}),
			DynamicVal,
			UnknownVal(Bool),
		},
		{
			UnknownVal(List(String)),
			NumberIntVal(0),
			UnknownVal(Bool),
		},
		{
			UnknownVal(List(String)),
			StringVal("hello"),
			False,
		},
		{
			MapVal(map[string]Value{"greeting": StringVal("hello")}),
			StringVal("greeting"),
			True,
		},
		{
			MapVal(map[string]Value{"greeting": StringVal("hello")}),
			StringVal("grouting"),
			False,
		},
		{
			MapVal(map[string]Value{"greeting": StringVal("hello")}),
			StringVal(""),
			False,
		},
		{
			MapVal(map[string]Value{"greeting": StringVal("hello")}),
			Zero,
			False,
		},
		{
			MapVal(map[string]Value{"greeting": StringVal("hello")}),
			True,
			False,
		},
		{
			MapVal(map[string]Value{"greeting": StringVal("hello")}),
			UnknownVal(String),
			UnknownVal(Bool),
		},
		{
			MapVal(map[string]Value{"greeting": StringVal("hello")}),
			DynamicVal,
			UnknownVal(Bool),
		},
		{
			UnknownVal(Map(String)),
			StringVal("hello"),
			UnknownVal(Bool),
		},
		{
			UnknownVal(Map(String)),
			NumberIntVal(0),
			False,
		},
		{
			TupleVal([]Value{StringVal("hello")}),
			NumberIntVal(0),
			True,
		},
		{
			TupleVal([]Value{StringVal("hello"), StringVal("world")}),
			NumberIntVal(1),
			True,
		},
		{
			TupleVal([]Value{StringVal("hello"), StringVal("world")}),
			NumberIntVal(2),
			False,
		},
		{
			TupleVal([]Value{StringVal("hello"), StringVal("world")}),
			NumberIntVal(-1),
			False,
		},
		{
			TupleVal([]Value{StringVal("hello"), StringVal("world")}),
			NumberFloatVal(0.5),
			False,
		},
		{
			TupleVal([]Value{StringVal("hello"), StringVal("world")}),
			StringVal("greeting"),
			False,
		},
		{
			TupleVal([]Value{StringVal("hello"), StringVal("world")}),
			True,
			False,
		},
		{
			TupleVal([]Value{StringVal("hello")}),
			UnknownVal(Number),
			UnknownVal(Bool),
		},
		{
			UnknownVal(Tuple([]Type{String})),
			NumberIntVal(0),
			True,
		},
		{
			TupleVal([]Value{StringVal("hello")}),
			DynamicVal,
			UnknownVal(Bool),
		},
		{
			DynamicVal,
			StringVal("hello"),
			UnknownVal(Bool),
		},
		{
			DynamicVal,
			NumberIntVal(0),
			UnknownVal(Bool),
		},
	}

	for _, test := range tests {
		t.Run(fmt.Sprintf("%#v.HasIndex(%q)", test.Collection, test.Key), func(t *testing.T) {
			got := test.Collection.HasIndex(test.Key)
			if !got.RawEquals(test.Expected) {
				t.Fatalf("HasIndex returned %#v; want %#v", got, test.Expected)
			}
		})
	}
}

func TestValueForEachElement(t *testing.T) {
	type call struct {
		Key     Value
		Element Value
	}
	tests := []struct {
		Receiver Value
		Expected []call
		Stopped  bool
	}{
		{
			ListValEmpty(String),
			[]call{},
			false,
		},
		{
			ListVal([]Value{
				NumberIntVal(1),
				NumberIntVal(2),
			}),
			[]call{
				{NumberIntVal(0), NumberIntVal(1)},
				{NumberIntVal(1), NumberIntVal(2)},
			},
			false,
		},
		{
			ListVal([]Value{
				StringVal("hey"),
				StringVal("stop"),
				StringVal("hey"),
			}),
			[]call{
				{NumberIntVal(0), StringVal("hey")},
				{NumberIntVal(1), StringVal("stop")},
			},
			true,
		},
		{
			SetValEmpty(String),
			[]call{},
			false,
		},
		{
			SetVal([]Value{
				NumberIntVal(1),
				NumberIntVal(2),
			}),
			[]call{
				// Ordering is arbitrary but consistent, so future changes
				// to the set implementation may reorder these.
				{NumberIntVal(2), NumberIntVal(2)},
				{NumberIntVal(1), NumberIntVal(1)},
			},
			false,
		},
		{
			SetVal([]Value{
				StringVal("hi"),
				StringVal("stop"),
				StringVal("hey"),
			}),
			[]call{
				// Ordering is arbitrary but consistent, so future changes
				// to the set implementation may reorder these.
				{StringVal("hi"), StringVal("hi")},
				{StringVal("stop"), StringVal("stop")},
			},
			true,
		},
		{
			MapVal(map[string]Value{
				"second": NumberIntVal(2),
				"first":  NumberIntVal(1),
			}),
			[]call{
				{StringVal("first"), NumberIntVal(1)},
				{StringVal("second"), NumberIntVal(2)},
			},
			false,
		},
		{
			MapVal(map[string]Value{
				"item2": StringVal("value2"),
				"item1": StringVal("stop"),
				"item0": StringVal("value0"),
			}),
			[]call{
				{StringVal("item0"), StringVal("value0")},
				{StringVal("item1"), StringVal("stop")},
			},
			true,
		},
		{
			EmptyTupleVal,
			[]call{},
			false,
		},
		{
			TupleVal([]Value{
				StringVal("hello"),
				NumberIntVal(2),
			}),
			[]call{
				{NumberIntVal(0), StringVal("hello")},
				{NumberIntVal(1), NumberIntVal(2)},
			},
			false,
		},
		{
			TupleVal([]Value{
				NumberIntVal(5),
				StringVal("stop"),
				True,
			}),
			[]call{
				{NumberIntVal(0), NumberIntVal(5)},
				{NumberIntVal(1), StringVal("stop")},
			},
			true,
		},
		{
			EmptyObjectVal,
			[]call{},
			false,
		},
		{
			ObjectVal(map[string]Value{
				"bool":   True,
				"string": StringVal("hello"),
			}),
			[]call{
				{StringVal("bool"), True},
				{StringVal("string"), StringVal("hello")},
			},
			false,
		},
	}

	for _, test := range tests {
		t.Run(fmt.Sprintf("%#v.ForEachElement()", test.Receiver), func(t *testing.T) {
			calls := make([]call, 0)
			stopped := test.Receiver.ForEachElement(
				func(key Value, elem Value) (stop bool) {
					calls = append(calls, call{
						Key:     key,
						Element: elem,
					})
					if elem.v == "stop" {
						stop = true
					}
					return
				},
			)
			if !reflect.DeepEqual(calls, test.Expected) {
				t.Errorf(
					"wrong calls from ForEachElement\ngot:  %#v\nwant: %#v",
					calls, test.Expected,
				)
			}
			if stopped != test.Stopped {
				t.Errorf(
					"ForEachElement returned %#v; want %#v",
					stopped, test.Stopped,
				)
			}
		})
	}
}

func TestValueNot(t *testing.T) {
	tests := []struct {
		Receiver Value
		Expected Value
	}{
		{
			True,
			False,
		},
		{
			False,
			True,
		},
		{
			UnknownVal(Bool),
			UnknownVal(Bool),
		},
		{
			DynamicVal,
			UnknownVal(Bool),
		},
	}

	for _, test := range tests {
		t.Run(fmt.Sprintf("%#v.Not()", test.Receiver), func(t *testing.T) {
			got := test.Receiver.Not()
			if !got.RawEquals(test.Expected) {
				t.Fatalf("Not returned %#v; want %#v", got, test.Expected)
			}
		})
	}
}

func TestValueAnd(t *testing.T) {
	tests := []struct {
		Receiver Value
		Other    Value
		Expected Value
	}{
		{
			False,
			False,
			False,
		},
		{
			False,
			True,
			False,
		},
		{
			True,
			False,
			False,
		},
		{
			True,
			True,
			True,
		},
		{
			UnknownVal(Bool),
			UnknownVal(Bool),
			UnknownVal(Bool),
		},
		{
			True,
			UnknownVal(Bool),
			UnknownVal(Bool),
		},
		{
			UnknownVal(Bool),
			True,
			UnknownVal(Bool),
		},
		{
			DynamicVal,
			DynamicVal,
			UnknownVal(Bool),
		},
		{
			True,
			DynamicVal,
			UnknownVal(Bool),
		},
		{
			DynamicVal,
			True,
			UnknownVal(Bool),
		},
	}

	for _, test := range tests {
		t.Run(fmt.Sprintf("%#v.And(%#v)", test.Receiver, test.Other), func(t *testing.T) {
			got := test.Receiver.And(test.Other)
			if !got.RawEquals(test.Expected) {
				t.Fatalf("And returned %#v; want %#v", got, test.Expected)
			}
		})
	}
}

func TestValueOr(t *testing.T) {
	tests := []struct {
		Receiver Value
		Other    Value
		Expected Value
	}{
		{
			False,
			False,
			False,
		},
		{
			False,
			True,
			True,
		},
		{
			True,
			False,
			True,
		},
		{
			True,
			True,
			True,
		},
		{
			UnknownVal(Bool),
			UnknownVal(Bool),
			UnknownVal(Bool),
		},
		{
			True,
			UnknownVal(Bool),
			UnknownVal(Bool),
		},
		{
			UnknownVal(Bool),
			True,
			UnknownVal(Bool),
		},
		{
			DynamicVal,
			DynamicVal,
			UnknownVal(Bool),
		},
		{
			True,
			DynamicVal,
			UnknownVal(Bool),
		},
		{
			DynamicVal,
			True,
			UnknownVal(Bool),
		},
	}

	for _, test := range tests {
		t.Run(fmt.Sprintf("%#v.Or(%#v)", test.Receiver, test.Other), func(t *testing.T) {
			got := test.Receiver.Or(test.Other)
			if !got.RawEquals(test.Expected) {
				t.Fatalf("Or returned %#v; want %#v", got, test.Expected)
			}
		})
	}
}

func TestLessThan(t *testing.T) {
	tests := []struct {
		Receiver Value
		Other    Value
		Expected Value
	}{
		{
			NumberIntVal(0),
			NumberIntVal(1),
			True,
		},
		{
			NumberIntVal(1),
			NumberIntVal(0),
			False,
		},
		{
			NumberIntVal(0),
			NumberIntVal(0),
			False,
		},
		{
			NumberFloatVal(0.1),
			NumberFloatVal(0.2),
			True,
		},
		{
			NumberFloatVal(0.2),
			NumberFloatVal(0.1),
			False,
		},
		{
			NumberIntVal(0),
			NumberFloatVal(0.2),
			True,
		},
		{
			NumberFloatVal(0.2),
			NumberIntVal(0),
			False,
		},
		{
			NumberFloatVal(0.2),
			NumberFloatVal(0.2),
			False,
		},
		{
			UnknownVal(Number),
			UnknownVal(Number),
			UnknownVal(Bool),
		},
		{
			NumberIntVal(1),
			UnknownVal(Number),
			UnknownVal(Bool),
		},
		{
			UnknownVal(Number),
			NumberIntVal(1),
			UnknownVal(Bool),
		},
		{
			DynamicVal,
			DynamicVal,
			UnknownVal(Bool),
		},
		{
			NumberIntVal(1),
			DynamicVal,
			UnknownVal(Bool),
		},
		{
			DynamicVal,
			NumberIntVal(1),
			UnknownVal(Bool),
		},
	}

	for _, test := range tests {
		t.Run(fmt.Sprintf("%#v.LessThan(%#v)", test.Receiver, test.Other), func(t *testing.T) {
			got := test.Receiver.LessThan(test.Other)
			if !got.RawEquals(test.Expected) {
				t.Fatalf("LessThan returned %#v; want %#v", got, test.Expected)
			}
		})
	}
}

func TestGreaterThan(t *testing.T) {
	tests := []struct {
		Receiver Value
		Other    Value
		Expected Value
	}{
		{
			NumberIntVal(0),
			NumberIntVal(1),
			False,
		},
		{
			NumberIntVal(1),
			NumberIntVal(0),
			True,
		},
		{
			NumberIntVal(0),
			NumberIntVal(0),
			False,
		},
		{
			NumberFloatVal(0.1),
			NumberFloatVal(0.2),
			False,
		},
		{
			NumberFloatVal(0.2),
			NumberFloatVal(0.1),
			True,
		},
		{
			NumberIntVal(0),
			NumberFloatVal(0.2),
			False,
		},
		{
			NumberFloatVal(0.2),
			NumberIntVal(0),
			True,
		},
		{
			NumberFloatVal(0.2),
			NumberFloatVal(0.2),
			False,
		},
		{
			UnknownVal(Number),
			UnknownVal(Number),
			UnknownVal(Bool),
		},
		{
			NumberIntVal(1),
			UnknownVal(Number),
			UnknownVal(Bool),
		},
		{
			UnknownVal(Number),
			NumberIntVal(1),
			UnknownVal(Bool),
		},
		{
			DynamicVal,
			DynamicVal,
			UnknownVal(Bool),
		},
		{
			NumberIntVal(1),
			DynamicVal,
			UnknownVal(Bool),
		},
		{
			DynamicVal,
			NumberIntVal(1),
			UnknownVal(Bool),
		},
	}

	for _, test := range tests {
		t.Run(fmt.Sprintf("%#v.GreaterThan(%#v)", test.Receiver, test.Other), func(t *testing.T) {
			got := test.Receiver.GreaterThan(test.Other)
			if !got.RawEquals(test.Expected) {
				t.Fatalf("GreaterThan returned %#v; want %#v", got, test.Expected)
			}
		})
	}
}

func TestLessThanOrEqualTo(t *testing.T) {
	tests := []struct {
		Receiver Value
		Other    Value
		Expected Value
	}{
		{
			NumberIntVal(0),
			NumberIntVal(1),
			True,
		},
		{
			NumberIntVal(1),
			NumberIntVal(0),
			False,
		},
		{
			NumberIntVal(0),
			NumberIntVal(0),
			True,
		},
		{
			NumberFloatVal(0.1),
			NumberFloatVal(0.2),
			True,
		},
		{
			NumberFloatVal(0.2),
			NumberFloatVal(0.1),
			False,
		},
		{
			NumberIntVal(0),
			NumberFloatVal(0.2),
			True,
		},
		{
			NumberFloatVal(0.2),
			NumberIntVal(0),
			False,
		},
		{
			NumberFloatVal(0.2),
			NumberFloatVal(0.2),
			True,
		},
		{
			UnknownVal(Number),
			UnknownVal(Number),
			UnknownVal(Bool),
		},
		{
			NumberIntVal(1),
			UnknownVal(Number),
			UnknownVal(Bool),
		},
		{
			UnknownVal(Number),
			NumberIntVal(1),
			UnknownVal(Bool),
		},
		{
			DynamicVal,
			DynamicVal,
			UnknownVal(Bool),
		},
		{
			NumberIntVal(1),
			DynamicVal,
			UnknownVal(Bool),
		},
		{
			DynamicVal,
			NumberIntVal(1),
			UnknownVal(Bool),
		},
	}

	for _, test := range tests {
		t.Run(fmt.Sprintf("%#v.LessThanOrEqualTo(%#v)", test.Receiver, test.Other), func(t *testing.T) {
			got := test.Receiver.LessThanOrEqualTo(test.Other)
			if !got.RawEquals(test.Expected) {
				t.Fatalf("LessThanOrEqualTo returned %#v; want %#v", got, test.Expected)
			}
		})
	}
}

func TestGreaterThanOrEqualTo(t *testing.T) {
	tests := []struct {
		Receiver Value
		Other    Value
		Expected Value
	}{
		{
			NumberIntVal(0),
			NumberIntVal(1),
			False,
		},
		{
			NumberIntVal(1),
			NumberIntVal(0),
			True,
		},
		{
			NumberIntVal(0),
			NumberIntVal(0),
			True,
		},
		{
			NumberFloatVal(0.1),
			NumberFloatVal(0.2),
			False,
		},
		{
			NumberFloatVal(0.2),
			NumberFloatVal(0.1),
			True,
		},
		{
			NumberIntVal(0),
			NumberFloatVal(0.2),
			False,
		},
		{
			NumberFloatVal(0.2),
			NumberIntVal(0),
			True,
		},
		{
			NumberFloatVal(0.2),
			NumberFloatVal(0.2),
			True,
		},
		{
			UnknownVal(Number),
			UnknownVal(Number),
			UnknownVal(Bool),
		},
		{
			NumberIntVal(1),
			UnknownVal(Number),
			UnknownVal(Bool),
		},
		{
			UnknownVal(Number),
			NumberIntVal(1),
			UnknownVal(Bool),
		},
		{
			DynamicVal,
			DynamicVal,
			UnknownVal(Bool),
		},
		{
			NumberIntVal(1),
			DynamicVal,
			UnknownVal(Bool),
		},
		{
			DynamicVal,
			NumberIntVal(1),
			UnknownVal(Bool),
		},
	}

	for _, test := range tests {
		t.Run(fmt.Sprintf("%#v.GreaterThanOrEqualTo(%#v)", test.Receiver, test.Other), func(t *testing.T) {
			got := test.Receiver.GreaterThanOrEqualTo(test.Other)
			if !got.RawEquals(test.Expected) {
				t.Fatalf("GreaterThanOrEqualTo returned %#v; want %#v", got, test.Expected)
			}
		})
	}
}
