package compiler_test

import (
	"bytes"
	"fmt"
	"math/big"
	"strings"
	"testing"

	"git.marketally.com/tutus-one/tutus-chain/pkg/compiler"
	"git.marketally.com/tutus-one/tutus-chain/pkg/vm"
	"github.com/stretchr/testify/require"
)

var switchTestCases = []testCase{
	{
		"simple switch success",
		`func F%d() int {
			a := 5
			switch a {
			case 5: return 2
			}
			return 1
		}
		`,
		big.NewInt(2),
	},
	{
		"switch with no tag",
		`func f() bool { return false }
		func F%d() int {
			switch {
			case f():
				return 1
			case true:
				return 2
			}
			return 3
		}
		`,
		big.NewInt(2),
	},
	{
		"type conversion in tag",
		`type state int
		func F%d() int {
			a := 1
			switch state(a) {
			case 1:
				return 42
			default:
				return 11
			}
		}
		`,
		big.NewInt(42),
	},
	{
		"simple switch fail",
		`func F%d() int {
			a := 6
			switch a {
			case 5:
				return 2
			}
			return 1
		}
		`,
		big.NewInt(1),
	},
	{
		"multiple cases success",
		`func F%d() int {
			a := 6
			switch a {
			case 5: return 2
			case 6: return 3
			}
			return 1
		}
		`,
		big.NewInt(3),
	},
	{
		"multiple cases fail",
		`func F%d() int {
			a := 7
			switch a {
			case 5: return 2
			case 6: return 3
			}
			return 1
		}
		`,
		big.NewInt(1),
	},
	{
		"default case",
		`func F%d() int {
			a := 7
			switch a {
			case 5: return 2
			case 6: return 3
			default: return 4
			}
			return 1
		}
		`,
		big.NewInt(4),
	},
	{
		"empty case before default",
		`func F%d() int {
			a := 6
			switch a {
			case 5: return 2
			case 6:
			default: return 4
			}
			return 1
		}
		`,
		big.NewInt(1),
	},
	{
		"expression in case clause",
		`func F%d() int {
			a := 6
			b := 3
			switch a {
			case 5: return 2
			case b*3-3: return 3
			}
			return 1
		}
		`,
		big.NewInt(3),
	},
	{
		"multiple expressions in case",
		`func F%d() int {
			a := 8
			b := 3
			switch a {
			case 5: return 2
			case b*3-3, 7, 8: return 3
			}
			return 1
		}
		`,
		big.NewInt(3),
	},
	{
		"string switch",
		`func F%d() int {
			name := "Valera"
			switch name {
			case "Misha": return 2
			case "Katya", "Dima": return 3
			case "Lera", "Valer" + "a": return 4
			}
			return 1
		}
		`,
		big.NewInt(4),
	},
	{
		"break from switch",
		`func F%d() int {
			i := 3
			switch i {
			case 2: return 2
			case 3:
				i = 1
				break
				return 3
			case 4: return 4
			}
			return i
		}
		`,
		big.NewInt(1),
	},
	{
		"break from outer for",
		`func F%d() int {
			i := 3
			loop:
			for i < 10 {
				i++
				switch i {
				case 5:
					i = 7
					break loop
					return 3
				case 6: return 4
				}
			}
			return i
		}
		`,
		big.NewInt(7),
	},
	{
		"continue outer for",
		`func F%d() int {
			i := 2
			for i < 10 {
				i++
				switch i {
				case 3:
					i = 7
					continue
				case 4, 5, 6, 7: return 5
				case 8: return 2
				}

				if i == 7 {
					return 6
				}
			}
			return i
		}
		`,
		big.NewInt(2),
	},
	{
		"simple fallthrough",
		`func F%d() int {
			n := 2
			switch n {
			case 1: return 5
			case 2: fallthrough
			case 3: return 6
			}
			return 7
		}
		`,
		big.NewInt(6),
	},
	{
		"double fallthrough",
		`func F%d() int {
			n := 2
			k := 5
			switch n {
			case 0: return k
			case 1: fallthrough
			case 2:
				k++
				fallthrough
			case 3:
			case 4:
				k++
				return k
			}
			return k
		}
		`,
		big.NewInt(6),
	},
	{
		"init assignment",
		`func F%d() int {
			var n int
			switch n = 1; {
			}
			return n
		}
		`,
		big.NewInt(1),
	},
	{
		"init short decl with shadowing",
		`func F%d() int {
			n := 10
			switch n := 1; {
			default:
				if n != 1 {
					return -1
				}
			}
			return n
		}
		`,
		big.NewInt(10),
	},
	{
		"init with multiple assignment",
		`func F%d() int {
			var a, b int
			switch a, b = 2, 3; {
			default:
				return a+b
			}
		}
		`,
		big.NewInt(5),
	},
	{
		"init with multiple assignment, without shadowing",
		`func F%d() int {
			switch a, b := 2, 3; {
			default:
				return a+b
			}
		}
		`,
		big.NewInt(5),
	},
	{
		"init with function call",
		`var g int
		func set(v int) int { g = v; return v }
		func F%d() int {
			g = 0
			switch x := set(7); x {
			case 7:
				return g
			default:
				return -1
			}
		}
		`,
		big.NewInt(7),
	},
	{
		"init evaluation order",
		`var order []int
		func add(v int) int { order = append(order, v); return v }
		func F%d() int {
			order = nil
			switch add(1); add(2) {
			case 2:
				if len(order) == 2 && order[0] == 1 && order[1] == 2 {
					return 42
				}
			}
			return -1
		}
		`,
		big.NewInt(42),
	},
	{
		"init without tag",
		`func F%d() int {
			var x int
			switch x = 7; {
			case x == 7:
				return x
			default:
				return -1
			}
		}
		`,
		big.NewInt(7),
	},
	{
		"init short decl with multi shadowing",
		`func F%d() int {
			a, b := 0, 0
			switch a, b := 3, 4; {
			default:
				return a+b
			}
			return a+b
		}
		`,
		big.NewInt(7),
	},
}

func TestSwitch(t *testing.T) {
	srcBuilder := bytes.NewBuffer([]byte("package testcase\n"))
	for i, tc := range switchTestCases {
		_, err := fmt.Fprintf(srcBuilder, tc.src, i)
		require.NoError(t, err)
	}

	ne, di, err := compiler.CompileWithOptions("file.go", strings.NewReader(srcBuilder.String()), nil)
	require.NoError(t, err)

	for i, tc := range switchTestCases {
		t.Run(tc.name, func(t *testing.T) {
			v := vm.New()
			invokeMethod(t, fmt.Sprintf("F%d", i), ne.Script, v, di)
			runAndCheck(t, v, tc.result)
		})
	}
}
