package types

import (
	"fmt"
	"reflect"
	"testing"
)

func TestParse(t *testing.T) {
	i := 1
	chk := func(input string, n *Node, expected string) {
		t.Run(fmt.Sprintf("Parse%d", i), func(t *testing.T) {
			if n.String() != expected {
				t.Errorf("Mismatch parsing %s -- Got:\n%s\nExpected:\n%s\n", input, n.String(), expected)
			}
		})
		i++
	}

	runParseTest := func(input, expected string) {
		n, err := Parse(input)
		if err != nil {
			t.Errorf("Parse error parsing %s\n", input)
		}
		chk(input, n, expected)
	}

	runSigTest := func(input, expected string) {
		rem, n := MethodSignature(input, NewNode("AST"))
		if rem != "" {
			t.Errorf("Parse error parsing %s. Remainder = %s\n", input, rem)
		}
		chk(input, n, expected)
	}

	runParseTest(`int`,
		`<TypeName> ''
-<TypeSpecifier> 'int'
`)
	runParseTest(`int`,
		`<TypeName> ''
-<TypeSpecifier> 'int'
`)
	runParseTest(`char*`,
		`<TypeName> ''
-<TypeSpecifier> 'char'
-<Pointer> '*'
`)
	runParseTest(`struct Str`,
		`<TypeName> ''
-<Struct> 'struct'
--<Identifier> 'Str'
`)
	runParseTest(`uint`,
		`<TypeName> ''
-<TypedefName> 'uint'
`)
	runParseTest(`uind`,
		`<TypeName> ''
-<TypedefName> 'uind'
`)
	runParseTest(`void ()`,
		`<TypeName> ''
-<TypeSpecifier> 'void'
-<Function> ''
`)
	runParseTest(`void (*)(struct NSRange)`,
		`<TypeName> ''
-<TypeSpecifier> 'void'
-<Parenthesized> ''
--<Pointer> '*'
-<Function> ''
--<ParameterDeclaration> ''
---<Struct> 'struct'
----<Identifier> 'NSRange'
`)
	runParseTest(`void (* _Nullable)(int)`,
		`<TypeName> ''
-<TypeSpecifier> 'void'
-<Parenthesized> ''
--<Pointer> '*'
--<NullableAnnotation> '_Nullable'
-<Function> ''
--<ParameterDeclaration> ''
---<TypeSpecifier> 'int'
`)
	runParseTest(`NSRange[3]`,
		`<TypeName> ''
-<TypedefName> 'NSRange'
-<Array> ''
--<Length> '3'
`)
	runParseTest(`struct _NSRange[3]`,
		`<TypeName> ''
-<Struct> 'struct'
--<Identifier> '_NSRange'
-<Array> ''
--<Length> '3'
`)
	runParseTest(`struct _NSRange (*)(int, char[])`,
		`<TypeName> ''
-<Struct> 'struct'
--<Identifier> '_NSRange'
-<Parenthesized> ''
--<Pointer> '*'
-<Function> ''
--<ParameterDeclaration> ''
---<TypeSpecifier> 'int'
--<ParameterDeclaration> ''
---<TypeSpecifier> 'char'
---<Array> ''
`)
	runParseTest(`struct _NSRange (*)(int, char[],NSRange)`,
		`<TypeName> ''
-<Struct> 'struct'
--<Identifier> '_NSRange'
-<Parenthesized> ''
--<Pointer> '*'
-<Function> ''
--<ParameterDeclaration> ''
---<TypeSpecifier> 'int'
--<ParameterDeclaration> ''
---<TypeSpecifier> 'char'
---<Array> ''
--<ParameterDeclaration> ''
---<TypedefName> 'NSRange'
`)
	runParseTest(`struct _NSRange (*)(int, char[],struct _NSRange)`,
		`<TypeName> ''
-<Struct> 'struct'
--<Identifier> '_NSRange'
-<Parenthesized> ''
--<Pointer> '*'
-<Function> ''
--<ParameterDeclaration> ''
---<TypeSpecifier> 'int'
--<ParameterDeclaration> ''
---<TypeSpecifier> 'char'
---<Array> ''
--<ParameterDeclaration> ''
---<Struct> 'struct'
----<Identifier> '_NSRange'
`)
	runParseTest(`mytype`,
		`<TypeName> ''
-<TypedefName> 'mytype'
`)
	runParseTest(`const int`,
		`<TypeName> ''
-<TypeQualifier> 'const'
-<TypeSpecifier> 'int'
`)
	runParseTest(`int[*]`,
		`<TypeName> ''
-<TypeSpecifier> 'int'
-<Array> ''
--<Length> '*'
`)
	runParseTest(`int[3][5]`,
		`<TypeName> ''
-<TypeSpecifier> 'int'
-<Array> ''
--<Length> '3'
-<Array> ''
--<Length> '5'
`)
	runParseTest(`int *`,
		`<TypeName> ''
-<TypeSpecifier> 'int'
-<Pointer> '*'
`)
	runParseTest(`int * _Nonnull`,
		`<TypeName> ''
-<TypeSpecifier> 'int'
-<Pointer> '*'
-<NullableAnnotation> '_Nonnull'
`)
	runParseTest(`int **`,
		`<TypeName> ''
-<TypeSpecifier> 'int'
-<Pointer> '*'
-<Pointer> '*'
`)
	runParseTest(`int *[3]`,
		`<TypeName> ''
-<TypeSpecifier> 'int'
-<Pointer> '*'
-<Array> ''
--<Length> '3'
`)
	runParseTest(`int (*)[3]`,
		`<TypeName> ''
-<TypeSpecifier> 'int'
-<Parenthesized> ''
--<Pointer> '*'
-<Array> ''
--<Length> '3'
`)
	runParseTest(`int (*)[*]`,
		`<TypeName> ''
-<TypeSpecifier> 'int'
-<Parenthesized> ''
--<Pointer> '*'
-<Array> ''
--<Length> '*'
`)
	runParseTest(`int *()`,
		`<TypeName> ''
-<TypeSpecifier> 'int'
-<Pointer> '*'
-<Function> ''
`)
	runParseTest(`int *(char*)`,
		`<TypeName> ''
-<TypeSpecifier> 'int'
-<Pointer> '*'
-<Function> ''
--<ParameterDeclaration> ''
---<TypeSpecifier> 'char'
---<Pointer> '*'
`)
	runParseTest(`RANGE *(char*)`,
		`<TypeName> ''
-<TypedefName> 'RANGE'
-<Pointer> '*'
-<Function> ''
--<ParameterDeclaration> ''
---<TypeSpecifier> 'char'
---<Pointer> '*'
`)
	runParseTest(`int (*)(void)`,
		`<TypeName> ''
-<TypeSpecifier> 'int'
-<Parenthesized> ''
--<Pointer> '*'
-<Function> ''
--<ParameterDeclaration> ''
---<TypeSpecifier> 'void'
`)
	runParseTest(`int (*)(int, unsigned int)`,
		`<TypeName> ''
-<TypeSpecifier> 'int'
-<Parenthesized> ''
--<Pointer> '*'
-<Function> ''
--<ParameterDeclaration> ''
---<TypeSpecifier> 'int'
--<ParameterDeclaration> ''
---<TypeSpecifier> 'unsigned'
---<TypeSpecifier> 'int'
`)
	runParseTest(`int (* _Nullable)(int, unsigned int)`,
		`<TypeName> ''
-<TypeSpecifier> 'int'
-<Parenthesized> ''
--<Pointer> '*'
--<NullableAnnotation> '_Nullable'
-<Function> ''
--<ParameterDeclaration> ''
---<TypeSpecifier> 'int'
--<ParameterDeclaration> ''
---<TypeSpecifier> 'unsigned'
---<TypeSpecifier> 'int'
`)
	runParseTest(`int (*const [])(unsigned int, ...)`,
		`<TypeName> ''
-<TypeSpecifier> 'int'
-<Parenthesized> ''
--<Pointer> '*'
--<TypeQualifier> 'const'
--<Array> ''
-<Function> ''
--<ParameterDeclaration> ''
---<TypeSpecifier> 'unsigned'
---<TypeSpecifier> 'int'
--<ParameterDeclaration> ''
---<Ellipsis> '...'
`)
	runParseTest(`BOOL (* _Nullable)()`,
		`<TypeName> ''
-<TypedefName> 'BOOL'
-<Parenthesized> ''
--<Pointer> '*'
--<NullableAnnotation> '_Nullable'
-<Function> ''
`)
	runParseTest(`BOOL (* _Nullable)(const void * _Nonnull, const void * _Nonnull)`,
		`<TypeName> ''
-<TypedefName> 'BOOL'
-<Parenthesized> ''
--<Pointer> '*'
--<NullableAnnotation> '_Nullable'
-<Function> ''
--<ParameterDeclaration> ''
---<TypeQualifier> 'const'
---<TypeSpecifier> 'void'
---<Pointer> '*'
---<NullableAnnotation> '_Nonnull'
--<ParameterDeclaration> ''
---<TypeQualifier> 'const'
---<TypeSpecifier> 'void'
---<Pointer> '*'
---<NullableAnnotation> '_Nonnull'
`)
	runParseTest(`BOOL (* _Nullable)(const void * _Nonnull, const void * _Nonnull, int (* _Nullable)(const void * _Nonnull))`,
		`<TypeName> ''
-<TypedefName> 'BOOL'
-<Parenthesized> ''
--<Pointer> '*'
--<NullableAnnotation> '_Nullable'
-<Function> ''
--<ParameterDeclaration> ''
---<TypeQualifier> 'const'
---<TypeSpecifier> 'void'
---<Pointer> '*'
---<NullableAnnotation> '_Nonnull'
--<ParameterDeclaration> ''
---<TypeQualifier> 'const'
---<TypeSpecifier> 'void'
---<Pointer> '*'
---<NullableAnnotation> '_Nonnull'
--<ParameterDeclaration> ''
---<TypeSpecifier> 'int'
---<Parenthesized> ''
----<Pointer> '*'
----<NullableAnnotation> '_Nullable'
---<Function> ''
----<ParameterDeclaration> ''
-----<TypeQualifier> 'const'
-----<TypeSpecifier> 'void'
-----<Pointer> '*'
-----<NullableAnnotation> '_Nonnull'
`)
	runParseTest(`BOOL (* _Nullable)(const void * _Nonnull, const void * _Nonnull, NSUInteger (* _Nullable)(const void * _Nonnull))`,
		`<TypeName> ''
-<TypedefName> 'BOOL'
-<Parenthesized> ''
--<Pointer> '*'
--<NullableAnnotation> '_Nullable'
-<Function> ''
--<ParameterDeclaration> ''
---<TypeQualifier> 'const'
---<TypeSpecifier> 'void'
---<Pointer> '*'
---<NullableAnnotation> '_Nonnull'
--<ParameterDeclaration> ''
---<TypeQualifier> 'const'
---<TypeSpecifier> 'void'
---<Pointer> '*'
---<NullableAnnotation> '_Nonnull'
--<ParameterDeclaration> ''
---<TypedefName> 'NSUInteger'
---<Parenthesized> ''
----<Pointer> '*'
----<NullableAnnotation> '_Nullable'
---<Function> ''
----<ParameterDeclaration> ''
-----<TypeQualifier> 'const'
-----<TypeSpecifier> 'void'
-----<Pointer> '*'
-----<NullableAnnotation> '_Nonnull'
`)
	runParseTest(`NSEnumerator<ObjectType> * _Nonnull`,
		`<TypeName> ''
-<TypedefName> 'NSEnumerator'
-<GenericList> ''
--<Generic> ''
---<TypedefName> 'ObjectType'
-<Pointer> '*'
-<NullableAnnotation> '_Nonnull'
`)
	runParseTest(`NSArray<NSString *> * _Nonnull`,
		`<TypeName> ''
-<TypedefName> 'NSArray'
-<GenericList> ''
--<Generic> ''
---<TypedefName> 'NSString'
---<Pointer> '*'
-<Pointer> '*'
-<NullableAnnotation> '_Nonnull'
`)
	runParseTest(`id  _Nonnull (^ _Nonnull)(id _Nullable, NSArray<NSExpression *> * _Nonnull, NSMutableDictionary * _Nullable)`,
		`<TypeName> ''
-<TypedefName> 'id'
-<NullableAnnotation> '_Nonnull'
-<Parenthesized> ''
--<Block> '^'
--<NullableAnnotation> '_Nonnull'
-<Function> ''
--<ParameterDeclaration> ''
---<TypedefName> 'id'
---<NullableAnnotation> '_Nullable'
--<ParameterDeclaration> ''
---<TypedefName> 'NSArray'
---<GenericList> ''
----<Generic> ''
-----<TypedefName> 'NSExpression'
-----<Pointer> '*'
---<Pointer> '*'
---<NullableAnnotation> '_Nonnull'
--<ParameterDeclaration> ''
---<TypedefName> 'NSMutableDictionary'
---<Pointer> '*'
---<NullableAnnotation> '_Nullable'
`)
	runSigTest(`(void)pressed`,
		`<MethodSignature> ''
-<TypeName> ''
--<TypeSpecifier> 'void'
-<Identifier> 'pressed'
`)
	runSigTest(`(void)performSelector:(SEL)aSelector target:(id)target argument:(id)arg order:(NSUInteger)order modes:(NSArray<NSRunLoopMode> *)modes`,
		`<MethodSignature> ''
-<TypeName> ''
--<TypeSpecifier> 'void'
-<Identifier> 'performSelector'
-<MethodParameter> ''
--<TypeName> ''
---<TypedefName> 'SEL'
--<Identifier> 'aSelector'
-<MethodParameter> ''
--<Identifier> 'target'
--<TypeName> ''
---<TypedefName> 'id'
--<Identifier> 'target'
-<MethodParameter> ''
--<Identifier> 'argument'
--<TypeName> ''
---<TypedefName> 'id'
--<Identifier> 'arg'
-<MethodParameter> ''
--<Identifier> 'order'
--<TypeName> ''
---<TypedefName> 'NSUInteger'
--<Identifier> 'order'
-<MethodParameter> ''
--<Identifier> 'modes'
--<TypeName> ''
---<TypedefName> 'NSArray'
---<GenericList> ''
----<Generic> ''
-----<TypedefName> 'NSRunLoopMode'
---<Pointer> '*'
--<Identifier> 'modes'
`)
}

func TestFuncs(t *testing.T) {
	i := 1
	var n *Node
	var err error
	f := func(input string, actual, expected interface{}) {
		t.Run(fmt.Sprintf("TestFunc%d", i), func(t *testing.T) {
			if err != nil {
				t.Errorf("Error parsing %s\n", input)
			}
			if actual == nil && expected == nil {
				return
			}
			if !reflect.DeepEqual(actual, expected) {
				t.Errorf("Test failed for %s\n", input)
			}
		})
		i++
	}
	str := "int*"
	n, err = Parse(str)
	f(str, n.HasFunc(), false)
	str = "int (*)(void)"
	n, err = Parse(str)
	f(str, n.HasFunc(), true)
	n, err = nil, nil
	f("", n.HasFunc(), false)
	f("", n.IsId(), false)
	f("", n.IsFunction(), false)
	f("", n.IsInstancetype(), false)
	f("", n.IsArray(), false)
	f("", n.IsStruct(), false)
	if n.BaseType() != nil {
		t.Errorf("BaseType() for nil node is not nil\n")
	}
	if n.PointsTo() != nil {
		t.Errorf("PointsTo() for nil node is not nil\n")
	}
	if n.ReturnType() != nil {
		t.Errorf("ReturnType() for nil node is not nil\n")
	}
	f("", n.String(), "")
	f("", n.Qualifiers(), "")
	f("", n.Annotations(), "")

	str = "int"
	n, err = Parse(str)
	f(str, n.isIndirect("Pointer"), false)
	if n.ReturnType() != nil {
		t.Errorf("Return type for non-function is not nil\n")
	}

	str = "int*"
	n, err = Parse(str)
	f(str, n.isIndirect("Pointer"), true)
	f(str, n.isIndirect("Array"), false)

	str = "int* _Nullable"
	n, err = Parse(str)
	f(str, n.isIndirect("Pointer"), true)

	str = "int* _Nonnull"
	n, err = Parse(str)
	f(str, n.isIndirect("Pointer"), true)
	f(str, n.CType(), "int* _Nonnull")
	f(str, n.CTypeSimplified(), "int*")
	f(str, n.Annotations(), "_Nonnull")

	n, err = Parse("int[]")
	n2, err2 := Parse("int")
	if err2 != nil {
		t.Errorf("Cannot parse int")
	}
	f(str, n.isIndirect("Pointer"), false)
	f(str, n.isIndirect("Array"), true)
	f(str, n.ArrayOf(), n2)
	f(str, n.CType(), "int[]")
	f(str, n.CTypeSimplified(), "int[]")

	str = "const int*[]"
	n, err = Parse(str)
	f(str, n.isIndirect("Array"), true)
	f(str, n.Qualifiers(), "const")

	str = "int (*)[]"
	n, err = Parse(str)
	f(str, n.isIndirect("Array"), true)
	f(str, n.isIndirect("Pointer"), true)

	str = "int (* _Nullable * _Nonnull)[]"
	n, err = Parse(str)
	f(str, n.isIndirect("Pointer"), true)

	str = "int (*)(void)"
	n, err = Parse(str)
	n2, err2 = Parse("int(void)")
	if err2 != nil {
		t.Errorf("Failed to parse int(void)")
	}
	f(str, n.PointsTo(), n2)
	f(str, n.IsPointer(), true)
	f(str, n.IsFunction(), false)
	f(str, n.CType(), "int (*)(void)")

	str = "int *(void)"
	n, err = Parse(str)
	f(str, n.IsFunction(), true)
	n2, _ = Parse("int*")
	f(str, n.ReturnType(), n2)

	str = "__kindof NSRange"
	n, err = Parse(str)
	f(str, n.IsStruct(), false)
	f(str, n.IsInstancetype(), false)

	str = "struct NSRange"
	n, err = Parse(str)
	f(str, n.IsStruct(), true)
	f(str, n.IsFunction(), false)

	str = "id"
	n, err = Parse(str)
	f(str, n.IsId(), true)
	f(str, n.IsPointer(), true)

	str = "int * _Nullable * _Nonnull"
	n, err = Parse(str)
	n2, _ = Parse("int")
	f(str, n.BaseType(), n2)

	str = "__kindof id"
	n, err = Parse(str)
	f(str, n.IsId(), true)
	f(str, n.IsPointer(), true)

	str = "NSArray<ObjectType> *"
	n, err = Parse(str)
	f(str, n.CType(), "NSArray <ObjectType>*")
	f(str, n.CTypeSimplified(), "NSArray*")

	str = "NSRange *(int, NSRange *)"
	n, err = Parse(str)
	f(str, n.renameTypedefs("NSRange", "struct NSRange"), true)
	f(str, n.CType(), "struct NSRange* (int, struct NSRange*)")

	str = "int*%&#$"
	n, err = Parse(str)
	if err == nil {
		fmt.Printf("%s\n", n.String())
		t.Errorf("Parse should have failed for %s\n", str)
	}

	var r func(i int) *Node
	r = func(i int) *Node {
		n := &Node{}
		if i > 0 {
			n.Children = []*Node{r(i - 1)}
		}
		return n
	}
	n = r(150)
	str = n.String()
	if str[len(str)-8:] != "too deep" {
		fmt.Printf(str[len(str)-8:])
		t.Errorf("Deep recursion did not produce an error from (*Node)String()\n")
	}
}

func ExampleDebug() {
	Debug = true
	dbg("one %s two %d", "string", 15)
	// Output: one string two 15
	Debug = false
}
