package gval

import (
	"context"
	"fmt"
	"testing"
	"text/scanner"
)

func TestNoParameter(t *testing.T) {
	testEvaluate(
		[]evaluationTest{
			{
				name:       "Number",
				expression: "100",
				want:       100.0,
			},
			{
				name:       "Single PLUS",
				expression: "51 + 49",
				want:       100.0,
			},
			{
				name:       "Single MINUS",
				expression: "100 - 51",
				want:       49.0,
			},
			{
				name:       "Single BITWISE AND",
				expression: "100 & 50",
				want:       32.0,
			},
			{
				name:       "Single BITWISE OR",
				expression: "100 | 50",
				want:       118.0,
			},
			{
				name:       "Single BITWISE XOR",
				expression: "100 ^ 50",
				want:       86.0,
			},
			{
				name:       "Single shift left",
				expression: "2 << 1",
				want:       4.0,
			},
			{
				name:       "Single shift right",
				expression: "2 >> 1",
				want:       1.0,
			},
			{
				name:       "Single BITWISE NOT",
				expression: "~10",
				want:       -11.0,
			},
			{

				name:       "Single MULTIPLY",
				expression: "5 * 20",
				want:       100.0,
			},
			{

				name:       "Single DIVIDE",
				expression: "100 / 20",
				want:       5.0,
			},
			{

				name:       "Single even MODULUS",
				expression: "100 % 2",
				want:       0.0,
			},
			{
				name:       "Single odd MODULUS",
				expression: "101 % 2",
				want:       1.0,
			},
			{

				name:       "Single EXPONENT",
				expression: "10 ** 2",
				want:       100.0,
			},
			{

				name:       "Compound PLUS",
				expression: "20 + 30 + 50",
				want:       100.0,
			},
			{

				name:       "Compound BITWISE AND",
				expression: "20 & 30 & 50",
				want:       16.0,
			},
			{
				name:       "Mutiple operators",
				expression: "20 * 5 - 49",
				want:       51.0,
			},
			{
				name:       "Parenthesis usage",
				expression: "100 - (5 * 10)",
				want:       50.0,
			},
			{

				name:       "Nested parentheses",
				expression: "50 + (5 * (15 - 5))",
				want:       100.0,
			},
			{

				name:       "Nested parentheses with bitwise",
				expression: "100 ^ (23 * (2 | 5))",
				want:       197.0,
			},
			{
				name:       "Logical OR operation of two clauses",
				expression: "(1 == 1) || (true == true)",
				want:       true,
			},
			{
				name:       "Logical AND operation of two clauses",
				expression: "(1 == 1) && (true == true)",
				want:       true,
			},
			{

				name:       "Implicit boolean",
				expression: "2 > 1",
				want:       true,
			},
			{
				name:       "Equal test minus numbers and no spaces",
				expression: "-1==-1",
				want:       true,
			},
			{

				name:       "Compound boolean",
				expression: "5 < 10 && 1 < 5",
				want:       true,
			},
			{
				name:       "Evaluated true && false operation (for issue #8)",
				expression: "1 > 10 && 11 > 10",
				want:       false,
			},
			{

				name:       "Evaluated true && false operation (for issue #8)",
				expression: "true == true && false == true",
				want:       false,
			},
			{

				name:       "Parenthesis boolean",
				expression: "10 < 50 && (1 != 2 && 1 > 0)",
				want:       true,
			},
			{
				name:       "Comparison of string constants",
				expression: `"foo" == "foo"`,
				want:       true,
			},
			{

				name:       "NEQ comparison of string constants",
				expression: `"foo" != "bar"`,
				want:       true,
			},
			{

				name:       "REQ comparison of string constants",
				expression: `"foobar" =~ "oba"`,
				want:       true,
			},
			{

				name:       "NREQ comparison of string constants",
				expression: `"foo" !~ "bar"`,
				want:       true,
			},
			{

				name:       "Multiplicative/additive order",
				expression: "5 + 10 * 2",
				want:       25.0,
			},
			{
				name:       "Multiple constant multiplications",
				expression: "10 * 10 * 10",
				want:       1000.0,
			},
			{

				name:       "Multiple adds/multiplications",
				expression: "10 * 10 * 10 + 1 * 10 * 10",
				want:       1100.0,
			},
			{

				name:       "Modulus operatorPrecedence",
				expression: "1 + 101 % 2 * 5",
				want:       6.0,
			},
			{
				name:       "Exponent operatorPrecedence",
				expression: "1 + 5 ** 3 % 2 * 5",
				want:       6.0,
			},
			{

				name:       "Bit shift operatorPrecedence",
				expression: "50 << 1 & 90",
				want:       64.0,
			},
			{

				name:       "Bit shift operatorPrecedence",
				expression: "90 & 50 << 1",
				want:       64.0,
			},
			{

				name:       "Bit shift operatorPrecedence amongst non-bitwise",
				expression: "90 + 50 << 1 * 5",
				want:       4480.0,
			},
			{
				name:       "Order of non-commutative same-operatorPrecedence operators (additive)",
				expression: "1 - 2 - 4 - 8",
				want:       -13.0,
			},
			{
				name:       "Order of non-commutative same-operatorPrecedence operators (multiplicative)",
				expression: "1 * 4 / 2 * 8",
				want:       16.0,
			},
			{
				name:       "Null coalesce operatorPrecedence",
				expression: "true ?? true ? 100 + 200 : 400",
				want:       300.0,
			},
			{
				name:       "Identical date equivalence",
				expression: `"2014-01-02 14:12:22" == "2014-01-02 14:12:22"`,
				want:       true,
			},
			{
				name:       "Positive date GT",
				expression: `"2014-01-02 14:12:22" > "2014-01-02 12:12:22"`,
				want:       true,
			},
			{
				name:       "Negative date GT",
				expression: `"2014-01-02 14:12:22" > "2014-01-02 16:12:22"`,
				want:       false,
			},
			{
				name:       "Positive date GTE",
				expression: `"2014-01-02 14:12:22" >= "2014-01-02 12:12:22"`,
				want:       true,
			},
			{
				name:       "Negative date GTE",
				expression: `"2014-01-02 14:12:22" >= "2014-01-02 16:12:22"`,
				want:       false,
			},
			{
				name:       "Positive date LT",
				expression: `"2014-01-02 14:12:22" < "2014-01-02 16:12:22"`,
				want:       true,
			},
			{

				name:       "Negative date LT",
				expression: `"2014-01-02 14:12:22" < "2014-01-02 11:12:22"`,
				want:       false,
			},
			{

				name:       "Positive date LTE",
				expression: `"2014-01-02 09:12:22" <= "2014-01-02 12:12:22"`,
				want:       true,
			},
			{
				name:       "Negative date LTE",
				expression: `"2014-01-02 14:12:22" <= "2014-01-02 11:12:22"`,
				want:       false,
			},
			{

				name:       "Sign prefix comparison",
				expression: "-1 < 0",
				want:       true,
			},
			{

				name:       "Lexicographic LT",
				expression: `"ab" < "abc"`,
				want:       true,
			},
			{
				name:       "Lexicographic LTE",
				expression: `"ab" <= "abc"`,
				want:       true,
			},
			{

				name:       "Lexicographic GT",
				expression: `"aba" > "abc"`,
				want:       false,
			},
			{

				name:       "Lexicographic GTE",
				expression: `"aba" >= "abc"`,
				want:       false,
			},
			{

				name:       "Boolean sign prefix comparison",
				expression: "!true == false",
				want:       true,
			},
			{
				name:       "Inversion of clause",
				expression: "!(10 < 0)",
				want:       true,
			},
			{

				name:       "Negation after modifier",
				expression: "10 * -10",
				want:       -100.0,
			},
			{

				name:       "Ternary with single boolean",
				expression: "true ? 10",
				want:       10.0,
			},
			{

				name:       "Ternary nil with single boolean",
				expression: "false ? 10",
				want:       nil,
			},
			{
				name:       "Ternary with comparator boolean",
				expression: "10 > 5 ? 35.50",
				want:       35.50,
			},
			{

				name:       "Ternary nil with comparator boolean",
				expression: "1 > 5 ? 35.50",
				want:       nil,
			},
			{

				name:       "Ternary with parentheses",
				expression: "(5 * (15 - 5)) > 5 ? 35.50",
				want:       35.50,
			},
			{

				name:       "Ternary operatorPrecedence",
				expression: "true ? 35.50 > 10",
				want:       true,
			},
			{
				name:       "Ternary-else",
				expression: "false ? 35.50 : 50",
				want:       50.0,
			},
			{

				name:       "Ternary-else inside clause",
				expression: "(false ? 5 : 35.50) > 10",
				want:       true,
			},
			{

				name:       "Ternary-else (true-case) inside clause",
				expression: "(true ? 1 : 5) < 10",
				want:       true,
			},
			{

				name:       "Ternary-else before comparator (negative case)",
				expression: "true ? 1 : 5 > 10",
				want:       1.0,
			},
			{
				name:       "Nested ternaries (#32)",
				expression: "(2 == 2) ? 1 : (true ? 2 : 3)",
				want:       1.0,
			},
			{

				name:       "Nested ternaries, right case (#32)",
				expression: "false ? 1 : (true ? 2 : 3)",
				want:       2.0,
			},
			{

				name:       "Doubly-nested ternaries (#32)",
				expression: "true ? (false ? 1 : (false ? 2 : 3)) : (false ? 4 : 5)",
				want:       3.0,
			},
			{

				name:       "String to string concat",
				expression: `"foo" + "bar" == "foobar"`,
				want:       true,
			},
			{
				name:       "String to float64 concat",
				expression: `"foo" + 123 == "foo123"`,
				want:       true,
			},
			{

				name:       "Float64 to string concat",
				expression: `123 + "bar" == "123bar"`,
				want:       true,
			},
			{

				name:       "String to date concat",
				expression: `"foo" + "02/05/1970" == "foobar"`,
				want:       false,
			},
			{

				name:       "String to bool concat",
				expression: `"foo" + true == "footrue"`,
				want:       true,
			},
			{
				name:       "Bool to string concat",
				expression: `true + "bar" == "truebar"`,
				want:       true,
			},
			{

				name:       "Null coalesce left",
				expression: "1 ?? 2",
				want:       1.0,
			},
			{

				name:       "Array membership literals",
				expression: "1 in [1, 2, 3]",
				want:       true,
			},
			{

				name:       "Array membership literal with inversion",
				expression: "!(1 in [1, 2, 3])",
				want:       false,
			},
			{
				name:       "Logical operator reordering (#30)",
				expression: "(true && true) || (true && false)",
				want:       true,
			},
			{

				name:       "Logical operator reordering without parens (#30)",
				expression: "true && true || true && false",
				want:       true,
			},
			{

				name:       "Logical operator reordering with multiple OR (#30)",
				expression: "false || true && true || false",
				want:       true,
			},
			{
				name:       "Left-side multiple consecutive (should be reordered) operators",
				expression: "(10 * 10 * 10) > 10",
				want:       true,
			},
			{

				name:       "Three-part non-paren logical op reordering (#44)",
				expression: "false && true || true",
				want:       true,
			},
			{

				name:       "Three-part non-paren logical op reordering (#44), second one",
				expression: "true || false && true",
				want:       true,
			},
			{
				name:       "Logical operator reordering without parens (#45)",
				expression: "true && true || false && false",
				want:       true,
			},
			{
				name:       "Single function",
				expression: "foo()",
				extension: Function("foo", func(arguments ...interface{}) (interface{}, error) {
					return true, nil
				}),

				want: true,
			},
			{
				name:       "Func with argument",
				expression: "passthrough(1)",
				extension: Function("passthrough", func(arguments ...interface{}) (interface{}, error) {
					return arguments[0], nil
				}),
				want: 1.0,
			},
			{
				name:       "Func with arguments",
				expression: "passthrough(1, 2)",
				extension: Function("passthrough", func(arguments ...interface{}) (interface{}, error) {
					return arguments[0].(float64) + arguments[1].(float64), nil
				}),
				want: 3.0,
			},
			{
				name:       "Nested function with operatorPrecedence",
				expression: "sum(1, sum(2, 3), 2 + 2, true ? 4 : 5)",
				extension: Function("sum", func(arguments ...interface{}) (interface{}, error) {
					sum := 0.0
					for _, v := range arguments {
						sum += v.(float64)
					}
					return sum, nil
				}),
				want: 14.0,
			},
			{
				name:       "Empty function and modifier, compared",
				expression: "numeric()-1 > 0",
				extension: Function("numeric", func(arguments ...interface{}) (interface{}, error) {
					return 2.0, nil
				}),
				want: true,
			},
			{
				name:       "Empty function comparator",
				expression: "numeric() > 0",
				extension: Function("numeric", func(arguments ...interface{}) (interface{}, error) {
					return 2.0, nil
				}),
				want: true,
			},
			{

				name:       "Empty function logical operator",
				expression: "success() && !false",
				extension: Function("success", func(arguments ...interface{}) (interface{}, error) {
					return true, nil
				}),
				want: true,
			},
			{
				name:       "Empty function ternary",
				expression: "nope() ? 1 : 2.0",
				extension: Function("nope", func(arguments ...interface{}) (interface{}, error) {
					return false, nil
				}),
				want: 2.0,
			},
			{

				name:       "Empty function null coalesce",
				expression: "null() ?? 2",
				extension: Function("null", func(arguments ...interface{}) (interface{}, error) {
					return nil, nil
				}),
				want: 2.0,
			},
			{
				name:       "Empty function with prefix",
				expression: "-ten()",
				extension: Function("ten", func(arguments ...interface{}) (interface{}, error) {
					return 10.0, nil
				}),
				want: -10.0,
			},
			{
				name:       "Empty function as part of chain",
				expression: "10 - numeric() - 2",
				extension: Function("numeric", func(arguments ...interface{}) (interface{}, error) {
					return 5.0, nil
				}),
				want: 3.0,
			},
			{
				name:       "Empty function near separator",
				expression: "10 in [1, 2, 3, ten(), 8]",
				extension: Function("ten", func(arguments ...interface{}) (interface{}, error) {
					return 10.0, nil
				}),
				want: true,
			},
			{
				name:       "Enclosed empty function with modifier and comparator (#28)",
				expression: "(ten() - 1) > 3",
				extension: Function("ten", func(arguments ...interface{}) (interface{}, error) {
					return 10.0, nil
				}),
				want: true,
			},
			{
				name:       "Array",
				expression: `[(ten() - 1) > 3, (ten() - 1),"hey"]`,
				extension: Function("ten", func(arguments ...interface{}) (interface{}, error) {
					return 10.0, nil
				}),
				want: []interface{}{true, 9., "hey"},
			},
			{
				name:       "Object",
				expression: `{1: (ten() - 1) > 3, 7 + ".X" : (ten() - 1),"hello" : "hey"}`,
				extension: Function("ten", func(arguments ...interface{}) (interface{}, error) {
					return 10.0, nil
				}),
				want: map[string]interface{}{"1": true, "7.X": 9., "hello": "hey"},
			},
			{
				name:       "Object negativ value",
				expression: `{1: -1,"hello" : "hey"}`,
				want:       map[string]interface{}{"1": -1., "hello": "hey"},
			},
			{
				name:       "Empty Array",
				expression: `[]`,
				want:       []interface{}{},
			},
			{
				name:       "Empty Object",
				expression: `{}`,
				want:       map[string]interface{}{},
			},
			{
				name:       "Variadic",
				expression: `sum(1,2,3,4)`,
				extension: Function("sum", func(arguments ...float64) (interface{}, error) {
					sum := 0.
					for _, a := range arguments {
						sum += a
					}
					return sum, nil
				}),
				want: 10.0,
			},
			{
				name:       "Ident Operator",
				expression: `1 plus 1`,
				extension: InfixNumberOperator("plus", func(a, b float64) (interface{}, error) {
					return a + b, nil
				}),
				want: 2.0,
			},
			{
				name:       "Postfix Operator",
				expression: `4§`,
				extension: PostfixOperator("§", func(_ context.Context, _ *Parser, eval Evaluable) (Evaluable, error) {
					return func(ctx context.Context, parameter interface{}) (interface{}, error) {
						i, err := eval.EvalInt(ctx, parameter)
						if err != nil {
							return nil, err
						}
						return fmt.Sprintf("§%d", i), nil
					}, nil
				}),
				want: "§4",
			},
			{
				name:       "Tabs as non-whitespace",
				expression: "4\t5\t6",
				extension: NewLanguage(
					Init(func(ctx context.Context, p *Parser) (Evaluable, error) {
						p.SetWhitespace('\n', '\r', ' ')
						return p.ParseExpression(ctx)
					}),
					InfixNumberOperator("\t", func(a, b float64) (interface{}, error) {
						return a * b, nil
					}),
				),
				want: 120.0,
			},
			{
				name:       "Handle all other prefixes",
				expression: "^foo + $bar + &baz",
				extension: DefaultExtension(func(ctx context.Context, p *Parser) (Evaluable, error) {
					var mul int
					switch p.TokenText() {
					case "^":
						mul = 1
					case "$":
						mul = 2
					case "&":
						mul = 3
					}

					switch p.Scan() {
					case scanner.Ident:
						return p.Const(mul * len(p.TokenText())), nil
					default:
						return nil, p.Expected("length multiplier", scanner.Ident)
					}
				}),
				want: 18.0,
			},
			{
				name:       "Embed languages",
				expression: "left { 5 + 5 } right",
				extension: func() Language {
					step := func(ctx context.Context, p *Parser, cur Evaluable) (Evaluable, error) {
						next, err := p.ParseExpression(ctx)
						if err != nil {
							return nil, err
						}

						return func(ctx context.Context, parameter interface{}) (interface{}, error) {
							us, err := cur.EvalString(ctx, parameter)
							if err != nil {
								return nil, err
							}

							them, err := next.EvalString(ctx, parameter)
							if err != nil {
								return nil, err
							}

							return us + them, nil
						}, nil
					}

					return NewLanguage(
						Init(func(ctx context.Context, p *Parser) (Evaluable, error) {
							p.SetWhitespace()
							p.SetMode(0)

							return p.ParseExpression(ctx)
						}),
						DefaultExtension(func(ctx context.Context, p *Parser) (Evaluable, error) {
							return step(ctx, p, p.Const(p.TokenText()))
						}),
						PrefixExtension(scanner.EOF, func(ctx context.Context, p *Parser) (Evaluable, error) {
							return p.Const(""), nil
						}),
						PrefixExtension('{', func(ctx context.Context, p *Parser) (Evaluable, error) {
							eval, err := p.ParseSublanguage(ctx, Full())
							if err != nil {
								return nil, err
							}

							switch p.Scan() {
							case '}':
							default:
								return nil, p.Expected("embedded", '}')
							}

							return step(ctx, p, eval)
						}),
					)
				}(),
				want: "left 10 right",
			},
			{
				name:       "Late binding",
				expression: "5 * [ 10 * { 20 / [ 10 ] } ]",
				extension: func() Language {
					var inner, outer Language

					parseCurly := func(ctx context.Context, p *Parser) (Evaluable, error) {
						eval, err := p.ParseSublanguage(ctx, outer)
						if err != nil {
							return nil, err
						}

						if p.Scan() != '}' {
							return nil, p.Expected("end", '}')
						}

						return eval, nil
					}

					parseSquare := func(ctx context.Context, p *Parser) (Evaluable, error) {
						eval, err := p.ParseSublanguage(ctx, inner)
						if err != nil {
							return nil, err
						}

						if p.Scan() != ']' {
							return nil, p.Expected("end", ']')
						}

						return eval, nil
					}

					inner = Full(PrefixExtension('{', parseCurly))
					outer = Full(PrefixExtension('[', parseSquare))
					return outer
				}(),
				want: 100.0,
			},
		},
		t,
	)
}
