// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.

// parser_test.go
package expr

import (
	"errors"
	"reflect"
	"strings"
	"testing"
)

type inputType struct {
	source     string
	wantResult any
	wantErr    error
}

func TestGeneralParser(t *testing.T) {
	inputs := []inputType{
		/*   1 */ {`1+/*5*/2`, int64(3), nil},
		/*   2 */ {`3 == 4`, false, nil},
		/*   3 */ {`3 != 4`, true, nil},
		/*   4 */ {`4 == (3-1)*(10/5)`, true, nil},
		/*   5 */ {`3 <= 4`, true, nil},
		/*   6 */ {`3 < 4`, true, nil},
		/*   7 */ {`4 < 3`, false, nil},
		/*   8 */ {`1+5 < 4`, false, nil},
		/*   9 */ {`3 > 4`, false, nil},
		/*  10 */ {`4 >= 4`, true, nil},
		/*  11 */ {`4 > 3`, true, nil},
		/*  12 */ {`1+5 > 4`, true, nil},
		/*  13 */ {`true`, true, nil},
		/*  14 */ {`not true`, false, nil},
		/*  15 */ {`true and false`, false, nil},
		/*  16 */ {`true or false`, true, nil},
		/*  *17 */ {`"uno" + "due"`, `unodue`, nil},
		/*  *18 */ {`"uno" + 2`, `uno2`, nil},
		/*  *19 */ {`"uno" + (2+1)`, `uno3`, nil},
		/*  *20 */ {`"uno" * (2+1)`, `unounouno`, nil},
		/*  21 */ {"1", int64(1), nil},
		/*  22 */ {"1.5", float64(1.5), nil},
		/*  23 */ {"1.5*2", float64(3.0), nil},
		/*  24 */ {"+1", int64(1), nil},
		/*  25 */ {"-1", int64(-1), nil},
		/*  26 */ {"435 + 105 * 2 - 1", int64(644), nil},
		/*  27 */ {"200 / 2 - 1", int64(99), nil},
		/*  28 */ {"(1+1)", int64(2), nil},
		/*  29 */ {"-(1+1)", int64(-2), nil},
		/*  30 */ {"-(-2+1)", int64(1), nil},
		/*  31 */ {"(1+1)*5", int64(10), nil},
		/*  32 */ {"200 / (1+1) - 1", int64(99), nil},
		/*  33 */ {`1+(1+(1+(1)+1)+1)+1`, int64(7), nil},
		/*  34 */ {`(((1)))`, int64(1), nil},
		/*  35 */ {`var2="abc"; "uno_" + var2`, `uno_abc`, nil},
		/*  36 */ {`0 || 0.0 && "hello"`, false, nil},
		/*  37 */ {`"s" + true`, nil, errors.New(`[1:6] left operand 's' [string] and right operand 'true' [bool] are not compatible with operator "+"`)},
		/*  38 */ {`+false`, nil, errors.New(`[1:2] prefix/postfix operator "+" do not support operand 'false' [bool]`)},
		/*  39 */ {`false // very simple expression`, false, nil},
		/*  40 */ {`1 + // Missing right operator`, nil, errors.New(`[1:4] infix operator "+" requires two non-nil operands, got 1`)},
		/*  41 */ {"", nil, nil},
		/*  42 */ {"4!", int64(24), nil},
		/*  43 */ {"(-4)!", nil, errors.New(`factorial of a negative integer (-4) is not allowed`)},
		/*  44 */ {"-4!", int64(-24), nil},
		/*  45 */ {"1.5 < 7", true, nil},
		/*  46 */ {"1.5 > 7", false, nil},
		/*  47 */ {"1.5 <= 7", true, nil},
		/*  48 */ {"1.5 >= 7", false, nil},
		/*  49 */ {"1.5 != 7", true, nil},
		/*  50 */ {"1.5 == 7", false, nil},
		/*  51 */ {`"1.5" < "7"`, true, nil},
		/*  52 */ {`"1.5" > "7"`, false, nil},
		/*  53 */ {`"1.5" == "7"`, false, nil},
		/*  54 */ {`"1.5" != "7"`, true, nil},
		/*  55 */ {"1.5 < ", nil, errors.New(`[1:6] infix operator "<" requires two non-nil operands, got 1`)},
		/*  56 */ {"1.5 > ", nil, errors.New(`[1:6] infix operator ">" requires two non-nil operands, got 1`)},
		/*  57 */ {"1.5 <= ", nil, errors.New(`[1:6] infix operator "<=" requires two non-nil operands, got 1`)},
		/*  58 */ {"1.5 >= ", nil, errors.New(`[1:6] infix operator ">=" requires two non-nil operands, got 1`)},
		/*  59 */ {"1.5 != ", nil, errors.New(`[1:6] infix operator "!=" requires two non-nil operands, got 1`)},
		/*  60 */ {"1.5 == ", nil, errors.New(`[1:6] infix operator "==" requires two non-nil operands, got 1`)},
		/*  61 */ {`"1.5" < `, nil, errors.New(`[1:8] infix operator "<" requires two non-nil operands, got 1`)},
		/*  62 */ {`"1.5" > `, nil, errors.New(`[1:8] infix operator ">" requires two non-nil operands, got 1`)},
		/*  63 */ {`"1.5" == `, nil, errors.New(`[1:8] infix operator "==" requires two non-nil operands, got 1`)},
		/*  64 */ {`"1.5" != `, nil, errors.New(`[1:8] infix operator "!=" requires two non-nil operands, got 1`)},
		/*  65 */ {"+1.5", float64(1.5), nil},
		/*  66 */ {"+", nil, errors.New(`[1:2] prefix operator "+" requires one not nil operand`)},
		/*  67 */ {"4 / 0", nil, errors.New(`division by zero`)},
		/*  68 */ {"4.0 / 0", nil, errors.New(`division by zero`)},
		/*  69 */ {"4.0 / \n2", float64(2.0), nil},
		/*  70 */ {`123`, int64(123), nil},
		/*  71 */ {`1.`, float64(1.0), nil},
		/*  72 */ {`1.E-2`, float64(0.01), nil},
		/*  73 */ {`1E2`, float64(100), nil},
		/*  74 */ {`1 / 2`, int64(0), nil},
		/*  75 */ {`1.0 / 2`, float64(0.5), nil},
		/*  76 */ {`1 ./ 2`, float64(0.5), nil},
		/*  77 */ {`5 % 2`, int64(1), nil},
		/*  78 */ {`5 % (-2)`, int64(1), nil},
		/*  79 */ {`-5 % 2`, int64(-1), nil},
		/*  80 */ {`5 % 2.0`, nil, errors.New(`[1:4] left operand '5' [integer] and right operand '2' [float] are not compatible with operator "%"`)},
		/*  81 */ {`"a" < "b" AND NOT (2 < 1)`, true, nil},
		/*  82 */ {`"a" < "b" AND NOT (2 == 1)`, true, nil},
		/*  83 */ {`"a" < "b" AND ~ 2 == 1`, true, nil},
		/*  84 */ {`~ 2 > 1`, false, nil},
		/*  85 */ {`~ true && true`, false, nil},
		/*  86 */ {`~ false || true`, true, nil},
		/*  87 */ {`false but true`, true, nil},
		/*  88 */ {`2+3 but 5*2`, int64(10), nil},
		/*  89 */ {`x=2`, int64(2), nil},
		/*  90 */ {`x=2 but x*10`, int64(20), nil},
		/*  91 */ {`false and true`, false, nil},
		/*  92 */ {`false and (x==2)`, false, nil},
		/*  93 */ {`false and (x=2 but x==2) or x==2`, nil, errors.New(`undefined variable or function "x"`)},
		/*  94 */ {`false or true`, true, nil},
		/*  95 */ {`false or (x==2)`, nil, errors.New(`undefined variable or function "x"`)},
		/*  96 */ {`a=5; a`, int64(5), nil},
		/*  97 */ {`2=5`, nil, errors.New(`assign operator ("=") must be preceded by a variable`)},
		/*  98 */ {`2+a=5`, nil, errors.New(`[1:3] left operand of "=" must be a variable`)},
		/*  99 */ {`2+(a=5)`, int64(7), nil},
		/* 100 */ {`x ?? "default"`, "default", nil},
		/* 101 */ {`x="hello"; x ?? "default"`, "hello", nil},
		/* 102 */ {`y=x ?? func(){4}; y()`, int64(4), nil},
		/* 103 */ {`x ?= "default"; x`, "default", nil},
		/* 104 */ {`x="hello"; x ?= "default"; x`, "hello", nil},
		/* 105 */ {`1 ? {"a"} : {"b"}`, "b", nil},
		/* 106 */ {`10 ? {"a"} : {"b"} :: {"c"}`, "c", nil},
		/* 107 */ {`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}`, "b", nil},
		/* 108 */ {`10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"}`, nil, errors.New(`[1:34] case list in default clause`)},
		/* 109 */ {`10 ? {"a"} :[10] {x="b" but x} :: {"c"}`, "b", nil},
		/* 110 */ {`10 ? {"a"} :[10] {x="b"; x} :: {"c"}`, "b", nil},
		/* 111 */ {`10 ? {"a"} : {"b"}`, nil, errors.New(`[1:3] no case catches the value (10) of the selection expression`)},
		/* 112 */ {`10 ? {"a"} :: {"b"} : {"c"}`, nil, errors.New(`[1:22] selector-case outside of a selector context`)},
		/* 113 */ {`1 ? {"a"} : {"b"} ? ["a"] {"A"} :["b"] {"B"}`, "B", nil},
		/* 114 */ {`2 + 1 ? {"a"} : {"b"} * 3`, "2bbb", nil},
		/* 115 */ {`nil`, nil, nil},
		/* 116 */ {`null`, nil, errors.New(`undefined variable or function "null"`)},
		/* 117 */ {`{"key"}`, nil, errors.New(`[1:8] expected ":", got "}"`)},
		/* 118 */ {`{"key":}`, nil, errors.New(`[1:9] expected "dictionary-value", got "}"`)},
		/* 119 */ {`{}`, &DictType{}, nil},
		/* 120 */ {`1|2`, newFraction(1, 2), nil},
		/* 121 */ {`1|2 + 1`, newFraction(3, 2), nil},
		/* 122 */ {`1|2 - 1`, newFraction(-1, 2), nil},
		/* 123 */ {`1|2 * 1`, newFraction(1, 2), nil},
		/* 124 */ {`1|2 * 2|3`, newFraction(2, 6), nil},
		/* 125 */ {`1|2 / 2|3`, newFraction(3, 4), nil},
		/* 126 */ {`builtin "math.arith"; add(1,2,3)`, int64(6), nil},
		/* 127 */ {`builtin "math.arith"; mulX(1,2,3)`, nil, errors.New(`unknown function mulX()`)},
		/* 128 */ {`builtin "math.arith"; add(1+4,3+2,5*(3-2))`, int64(15), nil},
		/* 129 */ {`builtin "math.arith"; add(add(1+4),3+2,5*(3-2))`, int64(15), nil},
		/* 130 */ {`builtin "math.arith"; add(add(1,4),/*3+2,*/5*(3-2))`, int64(10), nil},
		/* 131 */ {`builtin "math.arith"; a=5; b=2; add(a, b*3)`, int64(11), nil},
		/* 132 */ {`builtin "math.arith"; var2="abc"; add(1,2) but var2`, "abc", nil},
		/* 133 */ {`builtin "math.arith"; add(1|2, 2|3)`, newFraction(7, 6), nil},
		/* 134 */ {`builtin "math.arith"; add(1|2, 1.0, 2)`, float64(3.5), nil},
		/* 135 */ {`builtin "math.arith"; mul(1|2, 2|3)`, newFraction(2, 6), nil},
		/* 136 */ {`builtin "math.arith"; mul(1|2, 1.0, 2)`, float64(1.0), nil},
		/* 137 */ {`builtin "os.file"`, int64(1), nil},
		/* 138 */ {`v=10; v++; v`, int64(11), nil},
		/* 139 */ {`1+1|2+0.5`, float64(2), nil},
		/* 140 */ {`1.2()`, newFraction(6, 5), nil},
		/* 141 */ {`1|(2-2)`, nil, errors.New(`division by zero`)},
	}

	// t.Setenv("EXPR_PATH", ".")
	parserTest(t, "General", inputs)
}

func parserTest(t *testing.T, section string, inputs []inputType) {

	succeeded := 0
	failed := 0

	for i, input := range inputs {
		var expr Expr
		var gotResult any
		var gotErr error

		ctx := NewSimpleFuncStore()
		parser := NewParser(ctx)

		logTest(t, i+1, section, input.source, input.wantResult, input.wantErr)

		r := strings.NewReader(input.source)
		scanner := NewScanner(r, DefaultTranslations())

		good := true
		if expr, gotErr = parser.Parse(scanner); gotErr == nil {
			gotResult, gotErr = expr.Eval(ctx)
		}

		eq := reflect.DeepEqual(gotResult, input.wantResult)

		if !eq /*gotResult != input.wantResult*/ {
			t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
			good = false
		}

		if gotErr != input.wantErr {
			if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
				t.Errorf("%d: %q -> err = <%v>, want <%v>", i+1, input.source, gotErr, input.wantErr)
				good = false
			}
		}

		if good {
			succeeded++
		} else {
			failed++
		}
	}
	t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
}

func logTest(t *testing.T, n int, section, source string, wantResult any, wantErr error) {
	if wantErr == nil {
		t.Logf("[+]%s nr %3d -- %q  --> %v", section, n, source, wantResult)
	} else {
		t.Logf("[-]%s nr %3d -- %q  --> %v", section, n, source, wantErr)
	}
}