package cty

import (
	"fmt"
)

// anyUnknown is a helper to easily check if a set of values contains any
// unknowns, for operations that short-circuit to return unknown in that case.
func anyUnknown(values ...Value) bool {
	for _, val := range values {
		if val.v == unknown {
			return true
		}
	}
	return false
}

// typeCheck tests whether all of the given values belong to the given type.
// If the given types are a mixture of the given type and the dynamic
// pseudo-type then a short-circuit dynamic value is returned. If the given
// values are all of the correct type but at least one is unknown then
// a short-circuit unknown value is returned. If any other types appear then
// an error is returned. Otherwise (finally!) the result is nil, nil.
func typeCheck(required Type, ret Type, values ...Value) (shortCircuit *Value, err error) {
	hasDynamic := false
	hasUnknown := false

	for i, val := range values {
		if val.ty == DynamicPseudoType {
			hasDynamic = true
			continue
		}

		if !val.Type().Equals(required) {
			return nil, fmt.Errorf(
				"type mismatch: want %s but value %d is %s",
				required.FriendlyName(),
				i, val.ty.FriendlyName(),
			)
		}

		if val.v == unknown {
			hasUnknown = true
		}
	}

	if hasDynamic {
		return &DynamicVal, nil
	}

	if hasUnknown {
		ret := UnknownVal(ret)
		return &ret, nil
	}

	return nil, nil
}

// mustTypeCheck is a wrapper around typeCheck that immediately panics if
// any error is returned.
func mustTypeCheck(required Type, ret Type, values ...Value) *Value {
	shortCircuit, err := typeCheck(required, ret, values...)
	if err != nil {
		panic(err)
	}
	return shortCircuit
}

// shortCircuitForceType takes the return value from mustTypeCheck and
// replaces it with an unknown of the given type if the original value was
// DynamicVal.
//
// This is useful for operations that are specified to always return a
// particular type, since then a dynamic result can safely be "upgrade" to
// a strongly-typed unknown, which then allows subsequent operations to
// be actually type-checked.
//
// It is safe to use this only if the operation in question is defined as
// returning either a value of the given type or panicking, since we know
// then that subsequent operations won't run if the operation panics.
//
// If the given short-circuit value is *not* DynamicVal then it must be
// of the given type, or this function will panic.
func forceShortCircuitType(shortCircuit *Value, ty Type) *Value {
	if shortCircuit == nil {
		return nil
	}

	if shortCircuit.ty == DynamicPseudoType {
		ret := UnknownVal(ty)
		return &ret
	}

	if !shortCircuit.ty.Equals(ty) {
		panic("forceShortCircuitType got value of wrong type")
	}

	return shortCircuit
}
