package cty

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

	"github.com/zclconf/go-cty/cty/set"
)

// GoString is an implementation of fmt.GoStringer that produces concise
// source-like representations of values suitable for use in debug messages.
func (val Value) GoString() string {
	if val.IsMarked() {
		unVal, marks := val.Unmark()
		if len(marks) == 1 {
			var mark interface{}
			for m := range marks {
				mark = m
			}
			return fmt.Sprintf("%#v.Mark(%#v)", unVal, mark)
		}
		return fmt.Sprintf("%#v.WithMarks(%#v)", unVal, marks)
	}

	if val == NilVal {
		return "cty.NilVal"
	}

	if val.IsNull() {
		return fmt.Sprintf("cty.NullVal(%#v)", val.ty)
	}
	if val == DynamicVal { // is unknown, so must be before the IsKnown check below
		return "cty.DynamicVal"
	}
	if !val.IsKnown() {
		return fmt.Sprintf("cty.UnknownVal(%#v)", val.ty)
	}

	// By the time we reach here we've dealt with all of the exceptions around
	// unknowns and nulls, so we're guaranteed that the values are the
	// canonical internal representation of the given type.

	switch val.ty {
	case Bool:
		if val.v.(bool) {
			return "cty.True"
		}
		return "cty.False"
	case Number:
		fv := val.v.(*big.Float)
		// We'll try to use NumberIntVal or NumberFloatVal if we can, since
		// the fully-general initializer call is pretty ugly-looking.
		if fv.IsInt() {
			return fmt.Sprintf("cty.NumberIntVal(%#v)", fv)
		}
		if rfv, accuracy := fv.Float64(); accuracy == big.Exact {
			return fmt.Sprintf("cty.NumberFloatVal(%#v)", rfv)
		}
		return fmt.Sprintf("cty.MustParseNumberVal(%q)", fv.Text('f', -1))
	case String:
		return fmt.Sprintf("cty.StringVal(%#v)", val.v)
	}

	switch {
	case val.ty.IsSetType():
		vals := val.AsValueSlice()
		if len(vals) == 0 {
			return fmt.Sprintf("cty.SetValEmpty(%#v)", val.ty.ElementType())
		}
		return fmt.Sprintf("cty.SetVal(%#v)", vals)
	case val.ty.IsListType():
		vals := val.AsValueSlice()
		if len(vals) == 0 {
			return fmt.Sprintf("cty.ListValEmpty(%#v)", val.ty.ElementType())
		}
		return fmt.Sprintf("cty.ListVal(%#v)", vals)
	case val.ty.IsMapType():
		vals := val.AsValueMap()
		if len(vals) == 0 {
			return fmt.Sprintf("cty.MapValEmpty(%#v)", val.ty.ElementType())
		}
		return fmt.Sprintf("cty.MapVal(%#v)", vals)
	case val.ty.IsTupleType():
		if val.ty.Equals(EmptyTuple) {
			return "cty.EmptyTupleVal"
		}
		vals := val.AsValueSlice()
		return fmt.Sprintf("cty.TupleVal(%#v)", vals)
	case val.ty.IsObjectType():
		if val.ty.Equals(EmptyObject) {
			return "cty.EmptyObjectVal"
		}
		vals := val.AsValueMap()
		return fmt.Sprintf("cty.ObjectVal(%#v)", vals)
	case val.ty.IsCapsuleType():
		impl := val.ty.CapsuleOps().GoString
		if impl == nil {
			return fmt.Sprintf("cty.CapsuleVal(%#v, %#v)", val.ty, val.v)
		}
		return impl(val.EncapsulatedValue())
	}

	// Default exposes implementation details, so should actually cover
	// all of the cases above for good caller UX.
	return fmt.Sprintf("cty.Value{ty: %#v, v: %#v}", val.ty, val.v)
}

// Equals returns True if the receiver and the given other value have the
// same type and are exactly equal in value.
//
// As a special case, two null values are always equal regardless of type.
//
// The usual short-circuit rules apply, so the result will be unknown if
// either of the given values are.
//
// Use RawEquals to compare if two values are equal *ignoring* the
// short-circuit rules and the exception for null values.
func (val Value) Equals(other Value) Value {
	if val.IsMarked() || other.IsMarked() {
		val, valMarks := val.Unmark()
		other, otherMarks := other.Unmark()
		return val.Equals(other).WithMarks(valMarks, otherMarks)
	}

	// Start by handling Unknown values before considering types.
	// This needs to be done since Null values are always equal regardless of
	// type.
	switch {
	case !val.IsKnown() && !other.IsKnown():
		// both unknown
		return UnknownVal(Bool)
	case val.IsKnown() && !other.IsKnown():
		switch {
		case val.IsNull(), other.ty.HasDynamicTypes():
			// If known is Null, we need to wait for the unkown value since
			// nulls of any type are equal.
			// An unkown with a dynamic type compares as unknown, which we need
			// to check before the type comparison below.
			return UnknownVal(Bool)
		case !val.ty.Equals(other.ty):
			// There is no null comparison or dynamic types, so unequal types
			// will never be equal.
			return False
		default:
			return UnknownVal(Bool)
		}
	case other.IsKnown() && !val.IsKnown():
		switch {
		case other.IsNull(), val.ty.HasDynamicTypes():
			// If known is Null, we need to wait for the unkown value since
			// nulls of any type are equal.
			// An unkown with a dynamic type compares as unknown, which we need
			// to check before the type comparison below.
			return UnknownVal(Bool)
		case !other.ty.Equals(val.ty):
			// There's no null comparison or dynamic types, so unequal types
			// will never be equal.
			return False
		default:
			return UnknownVal(Bool)
		}
	}

	switch {
	case val.IsNull() && other.IsNull():
		// Nulls are always equal, regardless of type
		return BoolVal(true)
	case val.IsNull() || other.IsNull():
		// If only one is null then the result must be false
		return BoolVal(false)
	}

	if val.ty.HasDynamicTypes() || other.ty.HasDynamicTypes() {
		return UnknownVal(Bool)
	}

	if !val.ty.Equals(other.ty) {
		return BoolVal(false)
	}

	ty := val.ty
	result := false

	switch {
	case ty == Number:
		result = val.v.(*big.Float).Cmp(other.v.(*big.Float)) == 0
	case ty == Bool:
		result = val.v.(bool) == other.v.(bool)
	case ty == String:
		// Simple equality is safe because we NFC-normalize strings as they
		// enter our world from StringVal, and so we can assume strings are
		// always in normal form.
		result = val.v.(string) == other.v.(string)
	case ty.IsObjectType():
		oty := ty.typeImpl.(typeObject)
		result = true
		for attr, aty := range oty.AttrTypes {
			lhs := Value{
				ty: aty,
				v:  val.v.(map[string]interface{})[attr],
			}
			rhs := Value{
				ty: aty,
				v:  other.v.(map[string]interface{})[attr],
			}
			eq := lhs.Equals(rhs)
			if !eq.IsKnown() {
				return UnknownVal(Bool)
			}
			if eq.False() {
				result = false
				break
			}
		}
	case ty.IsTupleType():
		tty := ty.typeImpl.(typeTuple)
		result = true
		for i, ety := range tty.ElemTypes {
			lhs := Value{
				ty: ety,
				v:  val.v.([]interface{})[i],
			}
			rhs := Value{
				ty: ety,
				v:  other.v.([]interface{})[i],
			}
			eq := lhs.Equals(rhs)
			if !eq.IsKnown() {
				return UnknownVal(Bool)
			}
			if eq.False() {
				result = false
				break
			}
		}
	case ty.IsListType():
		ety := ty.typeImpl.(typeList).ElementTypeT
		if len(val.v.([]interface{})) == len(other.v.([]interface{})) {
			result = true
			for i := range val.v.([]interface{}) {
				lhs := Value{
					ty: ety,
					v:  val.v.([]interface{})[i],
				}
				rhs := Value{
					ty: ety,
					v:  other.v.([]interface{})[i],
				}
				eq := lhs.Equals(rhs)
				if !eq.IsKnown() {
					return UnknownVal(Bool)
				}
				if eq.False() {
					result = false
					break
				}
			}
		}
	case ty.IsSetType():
		s1 := val.v.(set.Set)
		s2 := other.v.(set.Set)
		equal := true

		// Note that by our definition of sets it's never possible for two
		// sets that contain unknown values (directly or indicrectly) to
		// ever be equal, even if they are otherwise identical.

		// FIXME: iterating both lists and checking each item is not the
		// ideal implementation here, but it works with the primitives we
		// have in the set implementation. Perhaps the set implementation
		// can provide its own equality test later.
		s1.EachValue(func(v interface{}) {
			if !s2.Has(v) {
				equal = false
			}
		})
		s2.EachValue(func(v interface{}) {
			if !s1.Has(v) {
				equal = false
			}
		})

		result = equal
	case ty.IsMapType():
		ety := ty.typeImpl.(typeMap).ElementTypeT
		if len(val.v.(map[string]interface{})) == len(other.v.(map[string]interface{})) {
			result = true
			for k := range val.v.(map[string]interface{}) {
				if _, ok := other.v.(map[string]interface{})[k]; !ok {
					result = false
					break
				}
				lhs := Value{
					ty: ety,
					v:  val.v.(map[string]interface{})[k],
				}
				rhs := Value{
					ty: ety,
					v:  other.v.(map[string]interface{})[k],
				}
				eq := lhs.Equals(rhs)
				if !eq.IsKnown() {
					return UnknownVal(Bool)
				}
				if eq.False() {
					result = false
					break
				}
			}
		}
	case ty.IsCapsuleType():
		impl := val.ty.CapsuleOps().Equals
		if impl == nil {
			impl := val.ty.CapsuleOps().RawEquals
			if impl == nil {
				// A capsule type's encapsulated value is a pointer to a value of its
				// native type, so we can just compare these to get the identity test
				// we need.
				return BoolVal(val.v == other.v)
			}
			return BoolVal(impl(val.v, other.v))
		}
		ret := impl(val.v, other.v)
		if !ret.Type().Equals(Bool) {
			panic(fmt.Sprintf("Equals for %#v returned %#v, not cty.Bool", ty, ret.Type()))
		}
		return ret

	default:
		// should never happen
		panic(fmt.Errorf("unsupported value type %#v in Equals", ty))
	}

	return BoolVal(result)
}

// NotEqual is a shorthand for Equals followed by Not.
func (val Value) NotEqual(other Value) Value {
	return val.Equals(other).Not()
}

// True returns true if the receiver is True, false if False, and panics if
// the receiver is not of type Bool.
//
// This is a helper function to help write application logic that works with
// values, rather than a first-class operation. It does not work with unknown
// or null values. For more robust handling with unknown value
// short-circuiting, use val.Equals(cty.True).
func (val Value) True() bool {
	val.assertUnmarked()
	if val.ty != Bool {
		panic("not bool")
	}
	return val.Equals(True).v.(bool)
}

// False is the opposite of True.
func (val Value) False() bool {
	return !val.True()
}

// RawEquals returns true if and only if the two given values have the same
// type and equal value, ignoring the usual short-circuit rules about
// unknowns and dynamic types.
//
// This method is more appropriate for testing than for real use, since it
// skips over usual semantics around unknowns but as a consequence allows
// testing the result of another operation that is expected to return unknown.
// It returns a primitive Go bool rather than a Value to remind us that it
// is not a first-class value operation.
func (val Value) RawEquals(other Value) bool {
	if !val.ty.Equals(other.ty) {
		return false
	}
	if !val.HasSameMarks(other) {
		return false
	}
	// Since we've now checked the marks, we'll unmark for the rest of this...
	val = val.unmarkForce()
	other = other.unmarkForce()

	if (!val.IsKnown()) && (!other.IsKnown()) {
		return true
	}
	if (val.IsKnown() && !other.IsKnown()) || (other.IsKnown() && !val.IsKnown()) {
		return false
	}
	if val.IsNull() && other.IsNull() {
		return true
	}
	if (val.IsNull() && !other.IsNull()) || (other.IsNull() && !val.IsNull()) {
		return false
	}
	if val.ty == DynamicPseudoType && other.ty == DynamicPseudoType {
		return true
	}

	ty := val.ty
	switch {
	case ty == Number || ty == Bool || ty == String || ty == DynamicPseudoType:
		return val.Equals(other).True()
	case ty.IsObjectType():
		oty := ty.typeImpl.(typeObject)
		for attr, aty := range oty.AttrTypes {
			lhs := Value{
				ty: aty,
				v:  val.v.(map[string]interface{})[attr],
			}
			rhs := Value{
				ty: aty,
				v:  other.v.(map[string]interface{})[attr],
			}
			eq := lhs.RawEquals(rhs)
			if !eq {
				return false
			}
		}
		return true
	case ty.IsTupleType():
		tty := ty.typeImpl.(typeTuple)
		for i, ety := range tty.ElemTypes {
			lhs := Value{
				ty: ety,
				v:  val.v.([]interface{})[i],
			}
			rhs := Value{
				ty: ety,
				v:  other.v.([]interface{})[i],
			}
			eq := lhs.RawEquals(rhs)
			if !eq {
				return false
			}
		}
		return true
	case ty.IsListType():
		ety := ty.typeImpl.(typeList).ElementTypeT
		if len(val.v.([]interface{})) == len(other.v.([]interface{})) {
			for i := range val.v.([]interface{}) {
				lhs := Value{
					ty: ety,
					v:  val.v.([]interface{})[i],
				}
				rhs := Value{
					ty: ety,
					v:  other.v.([]interface{})[i],
				}
				eq := lhs.RawEquals(rhs)
				if !eq {
					return false
				}
			}
			return true
		}
		return false
	case ty.IsSetType():
		s1 := val.v.(set.Set)
		s2 := other.v.(set.Set)

		// Since we're intentionally ignoring our rule that two unknowns
		// are never equal, we can cheat here.
		// (This isn't 100% right since e.g. it will fail if the set contains
		// numbers that are infinite, which DeepEqual can't compare properly.
		// We're accepting that limitation for simplicity here, since this
		// function is here primarily for testing.)
		return reflect.DeepEqual(s1, s2)

	case ty.IsMapType():
		ety := ty.typeImpl.(typeMap).ElementTypeT
		if len(val.v.(map[string]interface{})) == len(other.v.(map[string]interface{})) {
			for k := range val.v.(map[string]interface{}) {
				if _, ok := other.v.(map[string]interface{})[k]; !ok {
					return false
				}
				lhs := Value{
					ty: ety,
					v:  val.v.(map[string]interface{})[k],
				}
				rhs := Value{
					ty: ety,
					v:  other.v.(map[string]interface{})[k],
				}
				eq := lhs.RawEquals(rhs)
				if !eq {
					return false
				}
			}
			return true
		}
		return false
	case ty.IsCapsuleType():
		impl := val.ty.CapsuleOps().RawEquals
		if impl == nil {
			// A capsule type's encapsulated value is a pointer to a value of its
			// native type, so we can just compare these to get the identity test
			// we need.
			return val.v == other.v
		}
		return impl(val.v, other.v)

	default:
		// should never happen
		panic(fmt.Errorf("unsupported value type %#v in RawEquals", ty))
	}
}

// Add returns the sum of the receiver and the given other value. Both values
// must be numbers; this method will panic if not.
func (val Value) Add(other Value) Value {
	if val.IsMarked() || other.IsMarked() {
		val, valMarks := val.Unmark()
		other, otherMarks := other.Unmark()
		return val.Add(other).WithMarks(valMarks, otherMarks)
	}

	if shortCircuit := mustTypeCheck(Number, Number, val, other); shortCircuit != nil {
		shortCircuit = forceShortCircuitType(shortCircuit, Number)
		return *shortCircuit
	}

	ret := new(big.Float)
	ret.Add(val.v.(*big.Float), other.v.(*big.Float))
	return NumberVal(ret)
}

// Subtract returns receiver minus the given other value. Both values must be
// numbers; this method will panic if not.
func (val Value) Subtract(other Value) Value {
	if val.IsMarked() || other.IsMarked() {
		val, valMarks := val.Unmark()
		other, otherMarks := other.Unmark()
		return val.Subtract(other).WithMarks(valMarks, otherMarks)
	}

	if shortCircuit := mustTypeCheck(Number, Number, val, other); shortCircuit != nil {
		shortCircuit = forceShortCircuitType(shortCircuit, Number)
		return *shortCircuit
	}

	return val.Add(other.Negate())
}

// Negate returns the numeric negative of the receiver, which must be a number.
// This method will panic when given a value of any other type.
func (val Value) Negate() Value {
	if val.IsMarked() {
		val, valMarks := val.Unmark()
		return val.Negate().WithMarks(valMarks)
	}

	if shortCircuit := mustTypeCheck(Number, Number, val); shortCircuit != nil {
		shortCircuit = forceShortCircuitType(shortCircuit, Number)
		return *shortCircuit
	}

	ret := new(big.Float).Neg(val.v.(*big.Float))
	return NumberVal(ret)
}

// Multiply returns the product of the receiver and the given other value.
// Both values must be numbers; this method will panic if not.
func (val Value) Multiply(other Value) Value {
	if val.IsMarked() || other.IsMarked() {
		val, valMarks := val.Unmark()
		other, otherMarks := other.Unmark()
		return val.Multiply(other).WithMarks(valMarks, otherMarks)
	}

	if shortCircuit := mustTypeCheck(Number, Number, val, other); shortCircuit != nil {
		shortCircuit = forceShortCircuitType(shortCircuit, Number)
		return *shortCircuit
	}

	ret := new(big.Float)
	ret.Mul(val.v.(*big.Float), other.v.(*big.Float))
	return NumberVal(ret)
}

// Divide returns the quotient of the receiver and the given other value.
// Both values must be numbers; this method will panic if not.
//
// If the "other" value is exactly zero, this operation will return either
// PositiveInfinity or NegativeInfinity, depending on the sign of the
// receiver value. For some use-cases the presence of infinities may be
// undesirable, in which case the caller should check whether the
// other value equals zero before calling and raise an error instead.
//
// If both values are zero or infinity, this function will panic with
// an instance of big.ErrNaN.
func (val Value) Divide(other Value) Value {
	if val.IsMarked() || other.IsMarked() {
		val, valMarks := val.Unmark()
		other, otherMarks := other.Unmark()
		return val.Divide(other).WithMarks(valMarks, otherMarks)
	}

	if shortCircuit := mustTypeCheck(Number, Number, val, other); shortCircuit != nil {
		shortCircuit = forceShortCircuitType(shortCircuit, Number)
		return *shortCircuit
	}

	ret := new(big.Float)
	ret.Quo(val.v.(*big.Float), other.v.(*big.Float))
	return NumberVal(ret)
}

// Modulo returns the remainder of an integer division of the receiver and
// the given other value. Both values must be numbers; this method will panic
// if not.
//
// If the "other" value is exactly zero, this operation will return either
// PositiveInfinity or NegativeInfinity, depending on the sign of the
// receiver value. For some use-cases the presence of infinities may be
// undesirable, in which case the caller should check whether the
// other value equals zero before calling and raise an error instead.
//
// This operation is primarily here for use with nonzero natural numbers.
// Modulo with "other" as a non-natural number gets somewhat philosophical,
// and this function takes a position on what that should mean, but callers
// may wish to disallow such things outright or implement their own modulo
// if they disagree with the interpretation used here.
func (val Value) Modulo(other Value) Value {
	if val.IsMarked() || other.IsMarked() {
		val, valMarks := val.Unmark()
		other, otherMarks := other.Unmark()
		return val.Modulo(other).WithMarks(valMarks, otherMarks)
	}

	if shortCircuit := mustTypeCheck(Number, Number, val, other); shortCircuit != nil {
		shortCircuit = forceShortCircuitType(shortCircuit, Number)
		return *shortCircuit
	}

	// We cheat a bit here with infinities, just abusing the Multiply operation
	// to get an infinite result of the correct sign.
	if val == PositiveInfinity || val == NegativeInfinity || other == PositiveInfinity || other == NegativeInfinity {
		return val.Multiply(other)
	}

	if other.RawEquals(Zero) {
		return val
	}

	// FIXME: This is a bit clumsy. Should come back later and see if there's a
	// more straightforward way to do this.
	rat := val.Divide(other)
	ratFloorInt := &big.Int{}
	rat.v.(*big.Float).Int(ratFloorInt)
	work := (&big.Float{}).SetInt(ratFloorInt)
	work.Mul(other.v.(*big.Float), work)
	work.Sub(val.v.(*big.Float), work)

	return NumberVal(work)
}

// Absolute returns the absolute (signless) value of the receiver, which must
// be a number or this method will panic.
func (val Value) Absolute() Value {
	if val.IsMarked() {
		val, valMarks := val.Unmark()
		return val.Absolute().WithMarks(valMarks)
	}

	if shortCircuit := mustTypeCheck(Number, Number, val); shortCircuit != nil {
		shortCircuit = forceShortCircuitType(shortCircuit, Number)
		return *shortCircuit
	}

	ret := (&big.Float{}).Abs(val.v.(*big.Float))
	return NumberVal(ret)
}

// GetAttr returns the value of the given attribute of the receiver, which
// must be of an object type that has an attribute of the given name.
// This method will panic if the receiver type is not compatible.
//
// The method will also panic if the given attribute name is not defined
// for the value's type. Use the attribute-related methods on Type to
// check for the validity of an attribute before trying to use it.
//
// This method may be called on a value whose type is DynamicPseudoType,
// in which case the result will also be DynamicVal.
func (val Value) GetAttr(name string) Value {
	if val.IsMarked() {
		val, valMarks := val.Unmark()
		return val.GetAttr(name).WithMarks(valMarks)
	}

	if val.ty == DynamicPseudoType {
		return DynamicVal
	}

	if !val.ty.IsObjectType() {
		panic("value is not an object")
	}

	name = NormalizeString(name)
	if !val.ty.HasAttribute(name) {
		panic("value has no attribute of that name")
	}

	attrType := val.ty.AttributeType(name)

	if !val.IsKnown() {
		return UnknownVal(attrType)
	}

	return Value{
		ty: attrType,
		v:  val.v.(map[string]interface{})[name],
	}
}

// Index returns the value of an element of the receiver, which must have
// either a list, map or tuple type. This method will panic if the receiver
// type is not compatible.
//
// The key value must be the correct type for the receving collection: a
// number if the collection is a list or tuple, or a string if it is a map.
// In the case of a list or tuple, the given number must be convertable to int
// or this method will panic. The key may alternatively be of
// DynamicPseudoType, in which case the result itself is an unknown of the
// collection's element type.
//
// The result is of the receiver collection's element type, or in the case
// of a tuple the type of the specific element index requested.
//
// This method may be called on a value whose type is DynamicPseudoType,
// in which case the result will also be the DynamicValue.
func (val Value) Index(key Value) Value {
	if val.IsMarked() || key.IsMarked() {
		val, valMarks := val.Unmark()
		key, keyMarks := key.Unmark()
		return val.Index(key).WithMarks(valMarks, keyMarks)
	}

	if val.ty == DynamicPseudoType {
		return DynamicVal
	}

	switch {
	case val.Type().IsListType():
		elty := val.Type().ElementType()
		if key.Type() == DynamicPseudoType {
			return UnknownVal(elty)
		}

		if key.Type() != Number {
			panic("element key for list must be number")
		}
		if !key.IsKnown() {
			return UnknownVal(elty)
		}

		if !val.IsKnown() {
			return UnknownVal(elty)
		}

		index, accuracy := key.v.(*big.Float).Int64()
		if accuracy != big.Exact || index < 0 {
			panic("element key for list must be non-negative integer")
		}

		return Value{
			ty: elty,
			v:  val.v.([]interface{})[index],
		}
	case val.Type().IsMapType():
		elty := val.Type().ElementType()
		if key.Type() == DynamicPseudoType {
			return UnknownVal(elty)
		}

		if key.Type() != String {
			panic("element key for map must be string")
		}
		if !key.IsKnown() {
			return UnknownVal(elty)
		}

		if !val.IsKnown() {
			return UnknownVal(elty)
		}

		keyStr := key.v.(string)

		return Value{
			ty: elty,
			v:  val.v.(map[string]interface{})[keyStr],
		}
	case val.Type().IsTupleType():
		if key.Type() == DynamicPseudoType {
			return DynamicVal
		}

		if key.Type() != Number {
			panic("element key for tuple must be number")
		}
		if !key.IsKnown() {
			return DynamicVal
		}

		index, accuracy := key.v.(*big.Float).Int64()
		if accuracy != big.Exact || index < 0 {
			panic("element key for list must be non-negative integer")
		}

		eltys := val.Type().TupleElementTypes()

		if !val.IsKnown() {
			return UnknownVal(eltys[index])
		}

		return Value{
			ty: eltys[index],
			v:  val.v.([]interface{})[index],
		}
	default:
		panic("not a list, map, or tuple type")
	}
}

// HasIndex returns True if the receiver (which must be supported for Index)
// has an element with the given index key, or False if it does not.
//
// The result will be UnknownVal(Bool) if either the collection or the
// key value are unknown.
//
// This method will panic if the receiver is not indexable, but does not
// impose any panic-causing type constraints on the key.
func (val Value) HasIndex(key Value) Value {
	if val.IsMarked() || key.IsMarked() {
		val, valMarks := val.Unmark()
		key, keyMarks := key.Unmark()
		return val.HasIndex(key).WithMarks(valMarks, keyMarks)
	}

	if val.ty == DynamicPseudoType {
		return UnknownVal(Bool)
	}

	switch {
	case val.Type().IsListType():
		if key.Type() == DynamicPseudoType {
			return UnknownVal(Bool)
		}

		if key.Type() != Number {
			return False
		}
		if !key.IsKnown() {
			return UnknownVal(Bool)
		}
		if !val.IsKnown() {
			return UnknownVal(Bool)
		}

		index, accuracy := key.v.(*big.Float).Int64()
		if accuracy != big.Exact || index < 0 {
			return False
		}

		return BoolVal(int(index) < len(val.v.([]interface{})) && index >= 0)
	case val.Type().IsMapType():
		if key.Type() == DynamicPseudoType {
			return UnknownVal(Bool)
		}

		if key.Type() != String {
			return False
		}
		if !key.IsKnown() {
			return UnknownVal(Bool)
		}
		if !val.IsKnown() {
			return UnknownVal(Bool)
		}

		keyStr := key.v.(string)
		_, exists := val.v.(map[string]interface{})[keyStr]

		return BoolVal(exists)
	case val.Type().IsTupleType():
		if key.Type() == DynamicPseudoType {
			return UnknownVal(Bool)
		}

		if key.Type() != Number {
			return False
		}
		if !key.IsKnown() {
			return UnknownVal(Bool)
		}

		index, accuracy := key.v.(*big.Float).Int64()
		if accuracy != big.Exact || index < 0 {
			return False
		}

		length := val.Type().Length()
		return BoolVal(int(index) < length && index >= 0)
	default:
		panic("not a list, map, or tuple type")
	}
}

// HasElement returns True if the receiver (which must be of a set type)
// has the given value as an element, or False if it does not.
//
// The result will be UnknownVal(Bool) if either the set or the
// given value are unknown.
//
// This method will panic if the receiver is not a set, or if it is a null set.
func (val Value) HasElement(elem Value) Value {
	if val.IsMarked() || elem.IsMarked() {
		val, valMarks := val.Unmark()
		elem, elemMarks := elem.Unmark()
		return val.HasElement(elem).WithMarks(valMarks, elemMarks)
	}

	ty := val.Type()

	if !ty.IsSetType() {
		panic("not a set type")
	}
	if !val.IsKnown() || !elem.IsKnown() {
		return UnknownVal(Bool)
	}
	if val.IsNull() {
		panic("can't call HasElement on a nil value")
	}
	if !ty.ElementType().Equals(elem.Type()) {
		return False
	}

	s := val.v.(set.Set)
	return BoolVal(s.Has(elem.v))
}

// Length returns the length of the receiver, which must be a collection type
// or tuple type, as a number value. If the receiver is not a compatible type
// then this method will panic.
//
// If the receiver is unknown then the result is also unknown.
//
// If the receiver is null then this function will panic.
//
// Note that Length is not supported for strings. To determine the length
// of a string, call AsString and take the length of the native Go string
// that is returned.
func (val Value) Length() Value {
	if val.IsMarked() {
		val, valMarks := val.Unmark()
		return val.Length().WithMarks(valMarks)
	}

	if val.Type().IsTupleType() {
		// For tuples, we can return the length even if the value is not known.
		return NumberIntVal(int64(val.Type().Length()))
	}

	if !val.IsKnown() {
		return UnknownVal(Number)
	}

	return NumberIntVal(int64(val.LengthInt()))
}

// LengthInt is like Length except it returns an int. It has the same behavior
// as Length except that it will panic if the receiver is unknown.
//
// This is an integration method provided for the convenience of code bridging
// into Go's type system.
func (val Value) LengthInt() int {
	val.assertUnmarked()
	if val.Type().IsTupleType() {
		// For tuples, we can return the length even if the value is not known.
		return val.Type().Length()
	}
	if val.Type().IsObjectType() {
		// For objects, the length is the number of attributes associated with the type.
		return len(val.Type().AttributeTypes())
	}
	if !val.IsKnown() {
		panic("value is not known")
	}
	if val.IsNull() {
		panic("value is null")
	}

	switch {

	case val.ty.IsListType():
		return len(val.v.([]interface{}))

	case val.ty.IsSetType():
		return val.v.(set.Set).Length()

	case val.ty.IsMapType():
		return len(val.v.(map[string]interface{}))

	default:
		panic("value is not a collection")
	}
}

// ElementIterator returns an ElementIterator for iterating the elements
// of the receiver, which must be a collection type, a tuple type, or an object
// type. If called on a method of any other type, this method will panic.
//
// The value must be Known and non-Null, or this method will panic.
//
// If the receiver is of a list type, the returned keys will be of type Number
// and the values will be of the list's element type.
//
// If the receiver is of a map type, the returned keys will be of type String
// and the value will be of the map's element type. Elements are passed in
// ascending lexicographical order by key.
//
// If the receiver is of a set type, each element is returned as both the
// key and the value, since set members are their own identity.
//
// If the receiver is of a tuple type, the returned keys will be of type Number
// and the value will be of the corresponding element's type.
//
// If the receiver is of an object type, the returned keys will be of type
// String and the value will be of the corresponding attributes's type.
//
// ElementIterator is an integration method, so it cannot handle Unknown
// values. This method will panic if the receiver is Unknown.
func (val Value) ElementIterator() ElementIterator {
	val.assertUnmarked()
	if !val.IsKnown() {
		panic("can't use ElementIterator on unknown value")
	}
	if val.IsNull() {
		panic("can't use ElementIterator on null value")
	}
	return elementIterator(val)
}

// CanIterateElements returns true if the receiver can support the
// ElementIterator method (and by extension, ForEachElement) without panic.
func (val Value) CanIterateElements() bool {
	return canElementIterator(val)
}

// ForEachElement executes a given callback function for each element of
// the receiver, which must be a collection type or tuple type, or this method
// will panic.
//
// ForEachElement uses ElementIterator internally, and so the values passed
// to the callback are as described for ElementIterator.
//
// Returns true if the iteration exited early due to the callback function
// returning true, or false if the loop ran to completion.
//
// ForEachElement is an integration method, so it cannot handle Unknown
// values. This method will panic if the receiver is Unknown.
func (val Value) ForEachElement(cb ElementCallback) bool {
	val.assertUnmarked()
	it := val.ElementIterator()
	for it.Next() {
		key, val := it.Element()
		stop := cb(key, val)
		if stop {
			return true
		}
	}
	return false
}

// Not returns the logical inverse of the receiver, which must be of type
// Bool or this method will panic.
func (val Value) Not() Value {
	if val.IsMarked() {
		val, valMarks := val.Unmark()
		return val.Not().WithMarks(valMarks)
	}

	if shortCircuit := mustTypeCheck(Bool, Bool, val); shortCircuit != nil {
		shortCircuit = forceShortCircuitType(shortCircuit, Bool)
		return *shortCircuit
	}

	return BoolVal(!val.v.(bool))
}

// And returns the result of logical AND with the receiver and the other given
// value, which must both be of type Bool or this method will panic.
func (val Value) And(other Value) Value {
	if val.IsMarked() || other.IsMarked() {
		val, valMarks := val.Unmark()
		other, otherMarks := other.Unmark()
		return val.And(other).WithMarks(valMarks, otherMarks)
	}

	if shortCircuit := mustTypeCheck(Bool, Bool, val, other); shortCircuit != nil {
		shortCircuit = forceShortCircuitType(shortCircuit, Bool)
		return *shortCircuit
	}

	return BoolVal(val.v.(bool) && other.v.(bool))
}

// Or returns the result of logical OR with the receiver and the other given
// value, which must both be of type Bool or this method will panic.
func (val Value) Or(other Value) Value {
	if val.IsMarked() || other.IsMarked() {
		val, valMarks := val.Unmark()
		other, otherMarks := other.Unmark()
		return val.Or(other).WithMarks(valMarks, otherMarks)
	}

	if shortCircuit := mustTypeCheck(Bool, Bool, val, other); shortCircuit != nil {
		shortCircuit = forceShortCircuitType(shortCircuit, Bool)
		return *shortCircuit
	}

	return BoolVal(val.v.(bool) || other.v.(bool))
}

// LessThan returns True if the receiver is less than the other given value,
// which must both be numbers or this method will panic.
func (val Value) LessThan(other Value) Value {
	if val.IsMarked() || other.IsMarked() {
		val, valMarks := val.Unmark()
		other, otherMarks := other.Unmark()
		return val.LessThan(other).WithMarks(valMarks, otherMarks)
	}

	if shortCircuit := mustTypeCheck(Number, Bool, val, other); shortCircuit != nil {
		shortCircuit = forceShortCircuitType(shortCircuit, Bool)
		return *shortCircuit
	}

	return BoolVal(val.v.(*big.Float).Cmp(other.v.(*big.Float)) < 0)
}

// GreaterThan returns True if the receiver is greater than the other given
// value, which must both be numbers or this method will panic.
func (val Value) GreaterThan(other Value) Value {
	if val.IsMarked() || other.IsMarked() {
		val, valMarks := val.Unmark()
		other, otherMarks := other.Unmark()
		return val.GreaterThan(other).WithMarks(valMarks, otherMarks)
	}

	if shortCircuit := mustTypeCheck(Number, Bool, val, other); shortCircuit != nil {
		shortCircuit = forceShortCircuitType(shortCircuit, Bool)
		return *shortCircuit
	}

	return BoolVal(val.v.(*big.Float).Cmp(other.v.(*big.Float)) > 0)
}

// LessThanOrEqualTo is equivalent to LessThan and Equal combined with Or.
func (val Value) LessThanOrEqualTo(other Value) Value {
	return val.LessThan(other).Or(val.Equals(other))
}

// GreaterThanOrEqualTo is equivalent to GreaterThan and Equal combined with Or.
func (val Value) GreaterThanOrEqualTo(other Value) Value {
	return val.GreaterThan(other).Or(val.Equals(other))
}

// AsString returns the native string from a non-null, non-unknown cty.String
// value, or panics if called on any other value.
func (val Value) AsString() string {
	val.assertUnmarked()
	if val.ty != String {
		panic("not a string")
	}
	if val.IsNull() {
		panic("value is null")
	}
	if !val.IsKnown() {
		panic("value is unknown")
	}

	return val.v.(string)
}

// AsBigFloat returns a big.Float representation of a non-null, non-unknown
// cty.Number value, or panics if called on any other value.
//
// For more convenient conversions to other native numeric types, use the
// "gocty" package.
func (val Value) AsBigFloat() *big.Float {
	val.assertUnmarked()
	if val.ty != Number {
		panic("not a number")
	}
	if val.IsNull() {
		panic("value is null")
	}
	if !val.IsKnown() {
		panic("value is unknown")
	}

	// Copy the float so that callers can't mutate our internal state
	ret := *(val.v.(*big.Float))

	return &ret
}

// AsValueSlice returns a []cty.Value representation of a non-null, non-unknown
// value of any type that CanIterateElements, or panics if called on
// any other value.
//
// For more convenient conversions to slices of more specific types, use
// the "gocty" package.
func (val Value) AsValueSlice() []Value {
	val.assertUnmarked()
	l := val.LengthInt()
	if l == 0 {
		return nil
	}

	ret := make([]Value, 0, l)
	for it := val.ElementIterator(); it.Next(); {
		_, v := it.Element()
		ret = append(ret, v)
	}
	return ret
}

// AsValueMap returns a map[string]cty.Value representation of a non-null,
// non-unknown value of any type that CanIterateElements, or panics if called
// on any other value.
//
// For more convenient conversions to maps of more specific types, use
// the "gocty" package.
func (val Value) AsValueMap() map[string]Value {
	val.assertUnmarked()
	l := val.LengthInt()
	if l == 0 {
		return nil
	}

	ret := make(map[string]Value, l)
	for it := val.ElementIterator(); it.Next(); {
		k, v := it.Element()
		ret[k.AsString()] = v
	}
	return ret
}

// AsValueSet returns a ValueSet representation of a non-null,
// non-unknown value of any collection type, or panics if called
// on any other value.
//
// Unlike AsValueSlice and AsValueMap, this method requires specifically a
// collection type (list, set or map) and does not allow structural types
// (tuple or object), because the ValueSet type requires homogenous
// element types.
//
// The returned ValueSet can store only values of the receiver's element type.
func (val Value) AsValueSet() ValueSet {
	val.assertUnmarked()
	if !val.Type().IsCollectionType() {
		panic("not a collection type")
	}

	// We don't give the caller our own set.Set (assuming we're a cty.Set value)
	// because then the caller could mutate our internals, which is forbidden.
	// Instead, we will construct a new set and append our elements into it.
	ret := NewValueSet(val.Type().ElementType())
	for it := val.ElementIterator(); it.Next(); {
		_, v := it.Element()
		ret.Add(v)
	}
	return ret
}

// EncapsulatedValue returns the native value encapsulated in a non-null,
// non-unknown capsule-typed value, or panics if called on any other value.
//
// The result is the same pointer that was passed to CapsuleVal to create
// the value. Since cty considers values to be immutable, it is strongly
// recommended to treat the encapsulated value itself as immutable too.
func (val Value) EncapsulatedValue() interface{} {
	val.assertUnmarked()
	if !val.Type().IsCapsuleType() {
		panic("not a capsule-typed value")
	}

	return val.v
}
