func TestEvalFunctionNode_FailedToEvaluateArgumentNodes(t *testing.T) {
	// bool("value"), where in our case "value" won't exist
	evaluator, err := stateful.NewEvalFunctionNode(&ast.FunctionNode{
		Func: "bool",
		Args: []ast.Node{
			&ast.ReferenceNode{Reference: "value"},
		},
	})

	if err != nil {
		t.Fatalf("Failed to create node evaluator: %v", err)
	}

	result, err := evaluator.EvalBool(stateful.NewScope(), stateful.CreateExecutionState())

	expectedError := errors.New("Failed to handle 1 argument: name \"value\" is undefined. Names in scope:")
	if err == nil {
		t.Errorf("Expected an error, but got nil error and result (%t)", result)
		return
	}

	if strings.TrimSpace(err.Error()) != expectedError.Error() {
		t.Errorf("Got unexpected error:\ngot: %v\nexpected: %v\n", err, expectedError)
	}
}
func TestEvalFunctionNode_EvalFloat64_Sanity(t *testing.T) {
	evaluator, err := stateful.NewEvalFunctionNode(&ast.FunctionNode{
		Func: "abs",
		Args: []ast.Node{
			&ast.NumberNode{
				IsFloat: true,
				Float64: float64(-1),
			},
		},
	})

	if err != nil {
		t.Fatalf("Failed to create node evaluator: %v", err)
	}

	result, err := evaluator.EvalFloat(stateful.NewScope(), stateful.CreateExecutionState())
	if err != nil {
		t.Errorf("Expected a result, but got error - %v", err)
		return
	}

	if result != float64(1) {
		t.Errorf("unexpected result: got: %T(%v), expected: float64(1)", result, result)
	}
}
func TestEvalFunctionNode_EvalInt64_KeepConsistentState(t *testing.T) {
	evaluator, err := stateful.NewEvalFunctionNode(&ast.FunctionNode{
		Func: "count",
		Args: []ast.Node{},
	})

	if err != nil {
		t.Fatalf("Failed to create node evaluator: %v", err)
	}

	executionState := stateful.CreateExecutionState()

	// first evaluation
	result, err := evaluator.EvalInt(stateful.NewScope(), executionState)
	if err != nil {
		t.Errorf("first evaluation: Expected a result, but got error - %v", err)
		return
	}

	if result != int64(1) {
		t.Errorf("first evaluation: unexpected result: got: %T(%v), expected: int64(1)", result, result)
	}

	// second evaluation
	result, err = evaluator.EvalInt(stateful.NewScope(), executionState)
	if err != nil {
		t.Errorf("second evaluation: Expected a result, but got error - %v", err)
		return
	}

	if result != int64(2) {
		t.Errorf("second evaluation: unexpected result: got: %T(%v), expected: int64(2)", result, result)
	}
}
func TestEvalUnaryNode_EvalFloat64_FailedToEvaluateNode(t *testing.T) {
	evaluator, err := stateful.NewEvalUnaryNode(&ast.UnaryNode{
		Operator: ast.TokenMinus,
		Node: &ast.ReferenceNode{
			Reference: "value",
		},
	})

	if err != nil {
		t.Fatalf("Failed to compile unary node: %v", err)
	}

	result, err := evaluator.EvalFloat(stateful.NewScope(), stateful.CreateExecutionState())

	expectedError := errors.New("name \"value\" is undefined. Names in scope:")

	if err == nil && result != float64(0) {
		t.Errorf("Expected an error, but got nil error and result: %v", result)
		return
	}

	if strings.TrimSpace(err.Error()) != expectedError.Error() {
		t.Errorf("Got unexpected error:\ngot: %v\nexpected: %v\n", err, expectedError)
	}
}
func TestEvalUnaryNode_EvalInt64_FailedToEvaluateNode(t *testing.T) {
	evaluator, err := stateful.NewEvalUnaryNode(&ast.UnaryNode{
		Operator: ast.TokenMinus,
		Node: &ast.BoolNode{
			Bool: false,
		},
	})

	if err != nil {
		t.Fatalf("Failed to compile unary node: %v", err)
	}

	result, err := evaluator.EvalInt(stateful.NewScope(), stateful.CreateExecutionState())

	expectedError := errors.New("TypeGuard: expression returned unexpected type boolean, expected int")

	if err == nil && result != int64(0) {
		t.Errorf("Expected an error, but got nil error and result: %v", result)
		return
	}

	if err.Error() != expectedError.Error() {
		t.Errorf("Got unexpected error:\ngot: %v\nexpected: %v\n", err, expectedError)
	}
}
func TestEvalFunctionNode_EvalBool_Sanity(t *testing.T) {
	evaluator, err := stateful.NewEvalFunctionNode(&ast.FunctionNode{
		Func: "bool",
		Args: []ast.Node{&ast.StringNode{Literal: "true"}},
	})

	if err != nil {
		t.Fatalf("Failed to create node evaluator: %v", err)
	}

	result, err := evaluator.EvalBool(stateful.NewScope(), stateful.CreateExecutionState())
	if err != nil {
		t.Errorf("Expected a result, but got error - %v", err)
		return
	}

	if !result {
		t.Errorf("unexpected result: got: %v, expected: true", result)
	}
}
func TestEvalLambdaNode_EvalRegex_Sanity(t *testing.T) {
	evaluator, err := stateful.NewEvalLambdaNode(&ast.LambdaNode{
		Expression: &ast.RegexNode{
			Regex: regexp.MustCompile("^abc.*"),
		},
	})

	if err != nil {
		t.Fatalf("Failed to compile lambda node: %v", err)
	}

	result, err := evaluator.EvalRegex(stateful.NewScope(), stateful.CreateExecutionState())
	if err != nil {
		t.Errorf("Got unexpected error: %v", err)
	}

	if got, exp := result.String(), "^abc.*"; exp != got {
		t.Errorf("unexpected result: got: %T(%v), expected: %s", got, got, exp)
	}
}
func TestEvalLambdaNode_EvalString_Sanity(t *testing.T) {
	evaluator, err := stateful.NewEvalLambdaNode(&ast.LambdaNode{
		Expression: &ast.StringNode{
			Literal: "string",
		},
	})

	if err != nil {
		t.Fatalf("Failed to compile lambda node: %v", err)
	}

	result, err := evaluator.EvalString(stateful.NewScope(), stateful.CreateExecutionState())
	if err != nil {
		t.Errorf("Got unexpected error: %v", err)
	}

	if got, exp := result, "string"; got != exp {
		t.Errorf("unexpected result: got: %T(%v), expected: %s", got, got, exp)
	}
}
func TestEvalUnaryNode_EvalBool_Sanity(t *testing.T) {
	evaluator, err := stateful.NewEvalUnaryNode(&ast.UnaryNode{
		Operator: ast.TokenNot,
		Node: &ast.BoolNode{
			Bool: false,
		},
	})

	if err != nil {
		t.Fatalf("Failed to compile unary node: %v", err)
	}

	result, err := evaluator.EvalBool(stateful.NewScope(), stateful.CreateExecutionState())
	if err != nil {
		t.Errorf("Got unexpected error: %v", err)
	}

	if !result {
		t.Errorf("unexpected result: got: %t, expected: true", result)
	}
}
func TestEvalUnaryNode_EvalDuration_Sanity(t *testing.T) {
	evaluator, err := stateful.NewEvalUnaryNode(&ast.UnaryNode{
		Operator: ast.TokenMinus,
		Node: &ast.DurationNode{
			Dur: 12 * time.Hour,
		},
	})

	if err != nil {
		t.Fatalf("Failed to compile unary node: %v", err)
	}

	result, err := evaluator.EvalDuration(stateful.NewScope(), stateful.CreateExecutionState())
	if err != nil {
		t.Errorf("Got unexpected error: %v", err)
	}

	if got, exp := result, -12*time.Hour; got != exp {
		t.Errorf("unexpected result: got: %T(%v), expected: %s", got, got, exp)
	}
}
func TestEvalFunctionNode_AryMismatch(t *testing.T) {
	evaluator, err := stateful.NewEvalFunctionNode(&ast.FunctionNode{
		Func: "sigma",
		Args: []ast.Node{&ast.BoolNode{Bool: true}},
	})

	if err != nil {
		t.Fatalf("Failed to create node evaluator: %v", err)
	}

	result, err := evaluator.EvalFloat(stateful.NewScope(), stateful.CreateExecutionState())

	expectedError := errors.New("error calling \"sigma\": value is not a float")
	if err == nil {
		t.Errorf("Expected an error, but got nil error and result (%v)", result)
		return
	}

	if strings.TrimSpace(err.Error()) != expectedError.Error() {
		t.Errorf("Got unexpected error:\ngot: %v\nexpected: %v\n", err, expectedError)
	}
}
func TestEvalFunctionNode_UndefinedFunction(t *testing.T) {
	evaluator, err := stateful.NewEvalFunctionNode(&ast.FunctionNode{
		Func: "yosi_the_king",
		Args: []ast.Node{},
	})

	if err != nil {
		t.Fatalf("Failed to create node evaluator: %v", err)
	}

	result, err := evaluator.EvalBool(stateful.NewScope(), stateful.CreateExecutionState())

	expectedError := errors.New("undefined function: \"yosi_the_king\"")
	if err == nil {
		t.Errorf("Expected an error, but got nil error and result (%t)", result)
		return
	}

	if strings.TrimSpace(err.Error()) != expectedError.Error() {
		t.Errorf("Got unexpected error:\ngot: %v\nexpected: %v\n", err, expectedError)
	}
}
func TestEvalFunctionNode_EvalInt64_Reset(t *testing.T) {
	evaluator, err := stateful.NewEvalFunctionNode(&ast.FunctionNode{
		Func: "count",
		Args: []ast.Node{},
	})

	if err != nil {
		t.Fatalf("Failed to create node evaluator: %v", err)
	}

	executionState := stateful.CreateExecutionState()

	// first evaluation
	result, err := evaluator.EvalInt(stateful.NewScope(), executionState)
	if err != nil {
		t.Errorf("first evaluation: Expected a result, but got error - %v", err)
		return
	}

	if result != int64(1) {
		t.Errorf("first evaluation: unexpected result: got: %T(%v), expected: int64(1)", result, result)
	}

	// reset (we don't call ResetAll on ExecutionState in order to isolate the scope of this test)
	for _, fnc := range executionState.Funcs {
		fnc.Reset()
	}

	// second evaluation
	result, err = evaluator.EvalInt(stateful.NewScope(), executionState)
	if err != nil {
		t.Errorf("second evaluation (after reset): Expected a result, but got error - %v", err)
		return
	}

	if result != int64(1) {
		t.Errorf("second evaluation (after reset): unexpected result: got: %T(%v), expected: int64(1)", result, result)
	}
}
func TestEvalUnaryNode_EvalInt64_Sanity(t *testing.T) {
	evaluator, err := stateful.NewEvalUnaryNode(&ast.UnaryNode{
		Operator: ast.TokenMinus,
		Node: &ast.NumberNode{
			IsInt: true,
			Int64: int64(12),
		},
	})

	if err != nil {
		t.Fatalf("Failed to compile unary node: %v", err)
	}

	result, err := evaluator.EvalInt(stateful.NewScope(), stateful.CreateExecutionState())
	if err != nil {
		t.Errorf("Got unexpected error: %v", err)
	}

	if result != int64(-12) {
		t.Errorf("unexpected result: got: %T(%v), expected: int64(-12)", result, result)
	}
}
func TestEvalFunctionNode_ComplexNodes(t *testing.T) {
	// pow("x" * 2, -"y") => pow(4, -1)
	evaluator, err := stateful.NewEvalFunctionNode(&ast.FunctionNode{
		Func: "pow",
		Args: []ast.Node{
			// "x" * 2
			&ast.BinaryNode{
				Operator: ast.TokenMult,
				Left:     &ast.ReferenceNode{Reference: "x"},
				Right:    &ast.NumberNode{IsFloat: true, Float64: float64(2)},
			},

			// -"y"
			&ast.UnaryNode{
				Operator: ast.TokenMinus,
				Node:     &ast.ReferenceNode{Reference: "y"},
			},
		},
	})

	if err != nil {
		t.Fatalf("Failed to create node evaluator: %v", err)
	}

	scope := stateful.NewScope()
	scope.Set("x", float64(2))
	scope.Set("y", float64(1))

	result, err := evaluator.EvalFloat(scope, stateful.CreateExecutionState())
	if err != nil {
		t.Errorf("Expected a result, but got error - %v", err)
		return
	}

	if result != float64(0.25) {
		t.Errorf("unexpected result: got: %T(%v), expected: float64(0.25)", result, result)
	}
}
func TestEvalLambdaNode_EvalBool_SeparateState(t *testing.T) {
	// l1 = lambda: count() > 5
	// l2 = lambda: count() > 10
	// Because of short-circuiting it should take 15 calls before,
	// 'l1 AND l2' evaluates to true.

	l1 := &ast.LambdaNode{
		Expression: &ast.BinaryNode{
			Operator: ast.TokenGreater,
			Left: &ast.FunctionNode{
				Func: "count",
			},
			Right: &ast.NumberNode{
				IsInt: true,
				Int64: 5,
			},
		},
	}

	l2 := &ast.LambdaNode{
		Expression: &ast.BinaryNode{
			Operator: ast.TokenGreater,
			Left: &ast.FunctionNode{
				Func: "count",
			},
			Right: &ast.NumberNode{
				IsInt: true,
				Int64: 10,
			},
		},
	}

	evaluator, err := stateful.NewEvalBinaryNode(&ast.BinaryNode{
		Operator: ast.TokenAnd,
		Left:     l1,
		Right:    l2,
	})

	if err != nil {
		t.Fatalf("Failed to compile lambda node: %v", err)
	}

	count := 15
	for i := 0; i < count; i++ {
		result, err := evaluator.EvalBool(stateful.NewScope(), stateful.CreateExecutionState())
		if err != nil {
			t.Fatalf("Got unexpected error: %v", err)
		}

		if got, exp := result, false; got != exp {
			t.Fatalf("unexpected result: got: %T(%v), expected: %t", got, got, exp)
		}
	}
	// Final time it should be true
	result, err := evaluator.EvalBool(stateful.NewScope(), stateful.CreateExecutionState())
	if err != nil {
		t.Fatalf("Got unexpected error: %v", err)
	}

	if got, exp := result, true; got != exp {
		t.Fatalf("unexpected result: got: %T(%v), expected: %t", got, got, exp)
	}
}