type Expr = IfExpr | IntegerLiteral
type Stmt = FnDecl | StructDecl
type ExprStmt = Expr | Stmt

struct FnDecl {
	pos int
}

struct StructDecl {
	pos int
}

struct IfExpr {
	pos int
}

struct IntegerLiteral {
	val string
}

fn test_sum_type_match() {
	// TODO: Remove these casts
	assert is_gt_simple('3', int(2))
	assert !is_gt_simple('3', int(5))
	assert is_gt_simple('3', f64(1.2))
	assert !is_gt_simple('3', f64(3.5))
	assert is_gt_nested('3', int(2))
	assert !is_gt_nested('3', int(5))
	assert is_gt_nested('3', f64(1.2))
	assert !is_gt_nested('3', f64(3.5))
	assert concat('3', int(2)) == '3(int)2'
	assert concat('3', int(5)) == '3(int)5'
	assert concat('3', f64(1.2)) == '3(float)1.2'
	assert concat('3', f64(3.5)) == '3(float)3.5'
	assert get_sum('3', int(2)) == 5.0
	assert get_sum('3', int(5)) == 8.0
	assert get_sum('3', f64(1.2)) == 4.2
	assert get_sum('3', f64(3.5)) == 6.5
	assert verify_complex_expr(int(1), false)
	assert !verify_complex_expr(int(2), false)
	assert verify_complex_expr(f64(2.5), false)
	assert !verify_complex_expr(f64(1.5), false)
	assert verify_complex_expr(int(0), true)
	assert as_string(int(1)) == 'This is the string representation of "1"'
	assert as_string(f64(3.14)) == 'This is the string representation of "3.14"'
	assert as_string('String') == 'This is the string representation of "String"'
	assert as_string(IntAndStr{
		foo: 2
		bar: 'hi'
		baz: &IntAndStr{
			foo: 3
			bar: 'hello'
			baz: 0
		}
	}) == 'This is the string representation of "5_hi_hello"'
	assert as_string(true) == 'This is the string representation of "true"'
	assert as_string(CommonType(Color.red)) == 'This is the string representation of "enum1_red"'
	assert as_string(CommonType(Color.green)) == 'This is the string representation of "enum2_green"'
	assert as_string(CommonType(Color.blue)) == 'This is the string representation of "enum3_blue"'
	assert sumtype_match_with_string_interpolation(1) == "it's an int: 5"
	assert sumtype_match_with_string_interpolation(2) == "it's a string: hello"
	assert sumtype_match_with_string_interpolation(3) == 'green_green'
	assert sumtype_match_with_string_interpolation(4) == "it's a f64: 1.5"
	assert sumtype_match_with_string_interpolation(5) == "it's a bool: false"
	assert sumtype_match_with_string_interpolation(6) == "it's an IntAndStr: 2_hi_3_hello"
}

// Test moving structs between master/sub arrays
type Master = Sub1 | Sub2

struct Sub1 {
mut:
	val  int
	name string
}

struct Sub2 {
	name string
	val  int
}

fn test_converting_down() {
	mut out := []Master{}
	out << Sub1{
		val: 1
		name: 'one'
	}
	out << Sub2{
		val: 2
		name: 'two'
	}
	out << Sub2{
		val: 3
		name: 'three'
	}
	mut res := []Sub2{cap: out.len}
	for d in out {
		match d {
			Sub2 { res << d }
			else {}
		}
	}
	assert res[0].val == 2
	assert res[0].name == 'two'
	assert res[1].val == 3
	assert res[1].name == 'three'
}

fn test_assignment_and_push() {
	mut expr1 := Expr(IfExpr{})
	mut arr1 := []Expr{}
	expr := IntegerLiteral{
		val: '111'
	}
	arr1 << expr
	match arr1[0] {
		IntegerLiteral {
			arr1 << arr1[0]
			expr1 = arr1[0]
		}
		else {}
	}
}

fn test_expr() {
	expr := IntegerLiteral{
		val: '12'
	}
	assert handle(expr) == 'int'
	// assert expr is IntegerLiteral // TODO
}

struct Outer2 {
	e Expr
}

fn test_zero_value_init() {
	// no c compiler error then it's successful
	_ := Outer2{}
}

type Bar = Test | string
type Xyz = int | string

struct Test {
	x   string
	xyz Xyz
}

fn test_assignment() {
	y := 5
	mut x := Xyz(y)
	x = 'test'

	if x is string {
		x2 := x as string
		assert x2 == 'test'
	}
}

struct Foo {
	y Bar
}

fn test_as_cast() {
	f := Foo{
		y: Bar('test')
	}
	y := f.y as string
	assert y == 'test'
}

fn test_typeof() {
	x := Expr(IfExpr{})
	assert x.type_name() == 'IfExpr'
}

type Food = Eggs | Milk

struct FoodWrapper {
mut:
	food Food
}

struct Milk {
mut:
	name string
}

struct Eggs {
mut:
	name string
}

fn test_match_aggregate() {
	f := Food(Milk{'test'})
	match f {
		Milk, Eggs {
			assert f.name == 'test'
		}
	}
}

fn test_if() {
	mut f := Food(Milk{'test'})
	if f is Milk {
		// only works without smartcast
		assert f is Milk
	}
}

fn test_match() {
	mut f := Food(Milk{'test'})
	match f {
		Eggs {
			// only works without smartcast
			assert f is Eggs
		}
		Milk {
			// only works without smartcast
			assert f is Milk
		}
	}
}

type WrapperType = FoodWrapper | string

fn test_non_mut_ident_mut_selector_cast_match() {
	w := WrapperType(FoodWrapper{Food(Eggs{'test'})})
	match w {
		FoodWrapper {
			match w.food {
				Eggs {
					assert w.food.name + '2' == 'test2'
				}
				else {
					assert false
				}
			}
		}
		else {
			assert false
		}
	}
}

fn test_non_mut_ident_mut_selector_cast_if() {
	w := WrapperType(FoodWrapper{Food(Eggs{'test'})})
	if w is FoodWrapper {
		if w.food is Eggs {
			assert w.food.name + '2' == 'test2'
		} else {
			assert false
		}
	} else {
		assert false
	}
}

type Abc = int | string

fn test_string_cast_to_sumtype() {
	a := Abc('test')
	match a {
		int {
			assert false
		}
		string {
			assert true
		}
	}
}

fn test_int_cast_to_sumtype() {
	// literal
	a := Abc(111)
	match a {
		int {
			assert true
		}
		string {
			assert false
		}
	}
	// var
	i := 111
	b := Abc(i)
	match b {
		int {
			assert true
		}
		string {
			assert false
		}
	}
}

type Number = f64 | int

fn is_gt_simple(val string, dst Number) bool {
	match dst {
		int {
			return val.int() > dst
		}
		f64 {
			return dst < val.f64()
		}
	}
}

fn is_gt_nested(val string, dst Number) bool {
	dst2 := dst
	match dst {
		int {
			match dst2 {
				int {
					return val.int() > dst
				}
				// this branch should never been hit
				else {
					return val.int() < dst
				}
			}
		}
		f64 {
			match dst2 {
				f64 {
					return dst < val.f64()
				}
				// this branch should never been hit
				else {
					return dst > val.f64()
				}
			}
		}
	}
}

fn concat(val string, dst Number) string {
	match dst {
		int {
			mut res := val + '(int)'
			res += dst.str()
			return res
		}
		f64 {
			mut res := val + '(float)'
			res += dst.str()
			return res
		}
	}
}

fn get_sum(val string, dst Number) f64 {
	match dst {
		int {
			mut res := val.int()
			res += dst
			return res
		}
		f64 {
			mut res := val.f64()
			res += dst
			return res
		}
	}
}

fn verify_complex_expr(dst Number, get_final bool) bool {
	if !get_final {
		match dst {
			int {
				dst2 := dst
				dst3 := dst2
				dst4 := (dst2 + dst3).str()
				temp := 2 * dst3 + 1
				res := temp - 3
				foo := 1 + dst - dst2
				dst5 := foo.str().int().str()
				return (foo + res) * res - res == 0 && dst4.len == 1 && dst5.len == 1
			}
			f64 {
				dst2 := dst
				dst3, foo := dst2, 2
				mut dst4 := dst3 + 1
				dst4 = dst / 1
				dst4 -= dst2
				mut temp := foo - 4
				temp += foo * (foo - 1)
				bar := !(dst2 < 1) && dst3 - foo - temp > 0 && dst4 == 0
				return bar
			}
		}
	}
	foo := 10
	temp := foo
	return temp + 10 == 20
}

struct IntAndStr {
	foo int
	bar string
	baz &IntAndStr
}

enum Color {
	red
	green
	blue
}

type CommonType = Color | IntAndStr | bool | f64 | int | string

fn as_string(val CommonType) string {
	return 'This is the string representation of "' + val.str() + '"'
}

fn (c CommonType) str() string {
	match c {
		string {
			d := c.int()
			_ := d
			return c
		}
		int {
			d := c
			e := c + d - d
			return e.str()
		}
		f64 {
			return c.str()
		}
		IntAndStr {
			return (c.foo + c.baz.foo).str() + '_' + c.bar + '_' + c.baz.bar
		}
		bool {
			d := c
			return if d { 'true' } else { 'false' }
		}
		Color {
			d := c
			match d {
				.red { return 'enum1_' + d.str() }
				.green { return 'enum2_' + d.str() }
				.blue { return 'enum3_' + d.str() }
			}
		}
	}
}

fn sumtype_match_with_string_interpolation(code int) string {
	match code {
		1 {
			bar := CommonType(5)
			match bar {
				f64 { return "shouldn't happen" }
				bool { return "shouldn't happen" }
				IntAndStr { return "shouldn't happen" }
				int { return "it's an int: ${bar}" }
				string { return "shouldn't happen" }
				Color { return "shouldn't happen" }
			}
		}
		2 {
			bar := CommonType('hello')
			match bar {
				string { return "it's a string: ${bar}" }
				int { return "shouldn't happen" }
				Color { return "shouldn't happen" }
				f64 { return "shouldn't happen" }
				bool { return "shouldn't happen" }
				IntAndStr { return "shouldn't happen" }
			}
		}
		3 {
			bar := CommonType(Color.green)
			match bar {
				string {
					return "shouldn't happen"
				}
				int {
					return "shouldn't happen"
				}
				f64 {
					return "shouldn't happen"
				}
				bool {
					return "shouldn't happen"
				}
				IntAndStr {
					return "shouldn't happen"
				}
				Color {
					match bar {
						.red { return 'red_${bar}' }
						.green { return 'green_${bar}' }
						.blue { return 'blue_${bar}' }
					}
				}
			}
		}
		4 {
			bar := CommonType(1.5)
			match bar {
				string { return "shouldn't happen" }
				int { return "shouldn't happen" }
				Color { return "shouldn't happen" }
				f64 { return "it's a f64: ${bar}" }
				bool { return "shouldn't happen" }
				IntAndStr { return "shouldn't happen" }
			}
		}
		5 {
			mut bar := CommonType('hello')
			bar = CommonType(false)
			match bar {
				string { return "shouldn't happen" }
				int { return "shouldn't happen" }
				Color { return "shouldn't happen" }
				f64 { return "shouldn't happen" }
				bool { return "it's a bool: ${bar}" }
				IntAndStr { return "shouldn't happen" }
			}
		}
		6 {
			// TODO: this should work
			// mut bar := CommonType(100)
			// bar = CommonType(IntAndStr{foo: 2, bar: 'hi', baz: &IntAndStr{foo: 3, bar: 'hello', baz: 0}})
			bar := CommonType(IntAndStr{
				foo: 2
				bar: 'hi'
				baz: &IntAndStr{
					foo: 3
					bar: 'hello'
					baz: 0
				}
			})
			match bar {
				string { return "shouldn't happen" }
				int { return "shouldn't happen" }
				Color { return "shouldn't happen" }
				f64 { return "shouldn't happen" }
				bool { return "shouldn't happen" }
				IntAndStr { return "it's an IntAndStr: ${bar.foo}_${bar.bar}_${bar.baz.foo}_${bar.baz.bar}" }
			}
		}
		else {
			return 'wrong'
		}
	}
}

fn handle(e Expr) string {
	is_literal := e is IntegerLiteral
	assert is_literal
	assert e is IntegerLiteral
	if e is IntegerLiteral {
		assert typeof(e.val).name == 'string'
	}
	match e {
		IntegerLiteral {
			assert e.val == '12'
			// assert e.val == '12' // TODO
			return 'int'
		}
		IfExpr {
			return 'if'
		}
	}
	return ''
}

// Binary Search Tree test by @SleepyRoy
// TODO: make Node.value generic once it's robust enough
// TODO: `return match` instead of returns everywhere inside match

struct Empty {}

struct Node {
	value f64
	left  Tree
	right Tree
}

type Tree = Empty | Node

// return size(number of nodes) of BST
fn size(tree Tree) int {
	return match tree {
		// TODO: remove int() once match gets smarter
		Empty { int(0) }
		Node { 1 + size(tree.left) + size(tree.right) }
	}
}

// insert a value to BST
fn insert(tree Tree, x f64) Tree {
	match tree {
		Empty {
			return Node{x, tree, tree}
		}
		Node {
			return if x == tree.value {
				tree
			} else if x < tree.value {
				Node{
					...tree
					left: insert(tree.left, x)
				}
			} else {
				Node{
					...tree
					right: insert(tree.right, x)
				}
			}
		}
	}
}

// whether able to find a value in BST
fn search(tree Tree, x f64) bool {
	match tree {
		Empty {
			return false
		}
		Node {
			return if x == tree.value {
				true
			} else if x < tree.value {
				search(tree.left, x)
			} else {
				search(tree.right, x)
			}
		}
	}
}

// find the minimal value of a BST
fn min(tree Tree) f64 {
	match tree {
		Empty { return 1e100 }
		Node { return if tree.value < min(tree.left) { tree.value } else { min(tree.left) } }
	}
}

// delete a value in BST (if nonexist do nothing)
fn delete(tree Tree, x f64) Tree {
	match tree {
		Empty {
			return tree
		}
		Node {
			if tree.left is Node && tree.right is Node {
				return if x < tree.value {
					Node{
						...tree
						left: delete(tree.left, x)
					}
				} else if x > tree.value {
					Node{
						...tree
						right: delete(tree.right, x)
					}
				} else {
					Node{
						...tree
						value: min(tree.right)
						right: delete(tree.right, min(tree.right))
					}
				}
			} else if tree.left is Node {
				return if x == tree.value {
					tree.left
				} else {
					Node{
						...tree
						left: delete(tree.left, x)
					}
				}
			} else {
				if x == tree.value {
					return tree.right
				} else {
					return Node{
						...tree
						right: delete(tree.right, x)
					}
				}
			}
		}
	}
}

fn test_binary_search_tree() {
	mut tree := Tree(Empty{})
	input := [0.3, 0.2, 0.5, 0.0, 0.6, 0.8, 0.9, 1.0, 0.1, 0.4, 0.7]
	for i in input {
		tree = insert(tree, i)
	}
	assert size(tree) == 11
	del := [-0.3, 0.0, 0.3, 0.6, 1.0, 1.5]
	for i in del {
		tree = delete(tree, i)
	}
	assert size(tree) == 7
	mut deleted := []f64{}
	for i in input {
		if !search(tree, i) {
			deleted << i
		}
	}
	deleted.sort()
	assert deleted == [0.0, 0.3, 0.6, 1.0]
}

struct Common {
	a int
	b int
}

struct Common2 {
	Common
}

struct Aa {
	Common
	x int
}

struct Bb {
	Common
	x int
}

struct Cc {
	a int
}

struct Dd {
	Common2
}

type MySum = Aa | Bb | Cc | Dd

fn test_sumtype_access_embed_fields() {
	a := MySum(Aa{
		a: 1
	})
	assert a.a == 1
}

fn test_sumtype_access_nested_embed_fields() {
	a := MySum(Dd{
		Common2: Common2{
			a: 2
		}
	})
	assert a.a == 2
}
