package tests.wurstscript.tests;

import com.google.common.base.Charsets;
import com.google.common.io.Files;
import de.peeeq.wurstio.jassinterpreter.InterpreterException;
import de.peeeq.wurstscript.ast.ClassDef;
import de.peeeq.wurstscript.ast.FuncDef;
import de.peeeq.wurstscript.ast.WurstModel;
import org.testng.Assert;
import org.testng.annotations.Test;

import java.io.File;
import java.io.IOException;

import static de.peeeq.wurstscript.utils.Utils.string;

public class BugTests extends WurstScriptTest {
    public static final String TEST_DIR = "./testscripts/concept/";

    @Test
    public void localsInOndestroy() throws IOException {
        testAssertOkFile(new File(TEST_DIR + "OndestroyL.wurst"), true);
    }

    @Test
    public void cyclic() throws IOException {
        testAssertOkFile(new File(TEST_DIR + "CyclicError.wurst"), false);
    }

    @Test
    public void forfrom() throws IOException {
        testAssertOkFileWithStdLib(new File(TEST_DIR + "ForFrom.wurst"), false);
    }

    @Test
    public void ObjectRecycler() throws IOException {
        testAssertOkFileWithStdLib(new File(TEST_DIR + "ObjectRecycler.wurst"), false);
    }

    @Test
    public void intBoundaries() {
        testAssertOkLines(false,
                "package test",
                "	int i1 = -2147483648",
                "	int i2 = 2147483647",
                "	int i3 = 0 + (-2147483648)",
                "endpackage");
    }

    @Test
    public void intBoundariesL() {
        testAssertErrorsLines(false, "Invalid number",
                "package test",
                "	int i1 = -2147483649",
                "endpackage");
    }

    @Test
    public void intBoundariesH() {
        testAssertErrorsLines(false, "Invalid number",
                "package test",
                "	int i1 = 2147483648",
                "endpackage");
    }

    @Test
    public void bug62_codearray() {
        testAssertErrorsLines(false, "Code arrays",
                "package test",
                "	code array coar",
                "endpackage");
    }

    @Test
    public void bug61_break() {
        testAssertErrorsLines(false, "inside a loop",
                "package test",
                "	init",
                "		break",
                "endpackage");
    }

    @Test
    public void test_empty_escapesequence() {
        testAssertErrorsLines(false, "error",
                "package test",
                "	function foo() returns string",
                "		return \"\\ \" ",
                "endpackage");
    }


    @Test
    public void test_unit_array() {
        testAssertOkLines(false,
                "type unit extends handle",
                "package test",
                "	native testSuccess()",
                "	init",
                "		unit array blub",
                "		blub[4] = null",
                "		if blub[4] == null",
                "			testSuccess()",
                "endpackage");
    }

    @Test
    public void test_inline_jass_div() {
        testAssertOkLines(false,
                "function divide takes integer a, integer b returns integer",
                "	return a / b",
                "endfunction",
                "package test",
                "	native testSuccess()",
                "	init",
                "		if divide(17,3) == 5",
                "			testSuccess()",
                "endpackage");
    }

    @Test
    public void test_init_order_jass_warning() {
        testAssertErrorsLines(false, "Variable b may not have been initialized",
                "function foo takes nothing returns integer",
                "	local integer a = b",
                "	local integer b = 3",
                "	return a + b",
                "endfunction",
                "package test",
                "	native testSuccess()",
                "	init",
                "		if foo() == 6",
                "			testSuccess()",
                "endpackage");
    }

    @Test
    public void test_init_order_globals_warning() {
        testAssertErrorsLines(false, "Global variable <b> must be declared before it is used.",
                "package test",
                "	integer a = b",
                "	integer b = 3",
                "	native testSuccess()",
                "	init",
                "		if a + b == 6",
                "			testSuccess()",
                "endpackage");
    }

    @Test
    public void test_init_order_globals_warning_jass() {
        testAssertErrorsLines(false, "Global variable b used before it is declared.",
                "globals",
                "	integer a = b",
                "	integer b = 3",
                "endglobals",
                "package test",
                "	native testSuccess()",
                "	init",
                "		if a + b == 6",
                "			testSuccess()",
                "endpackage");
    }

    @Test
    public void classVarInitOrderShouldError_770() {
        testAssertErrorsLines(false, "used before it is initialized",
                "package test",
                "class B",
                "    var i = 0",
                "    function get() returns int",
                "        return i",
                "class A",
                "    private var foo = b.get()",
                "    private var b = new B()",
                "endpackage");
    }

    @Test
    public void dependencyDiagnosticsAreMuted() {
        CompilationResult res = test()
            .setStopOnFirstError(false)
            .withCu(compilationUnit("_build/dependencies/dep/Dep.wurst",
                "package dep",
                "init",
                "    if true",
                "endpackage"))
            .run();

        Assert.assertTrue(res.getGui().getErrorList().isEmpty(), "Expected no errors.");
        Assert.assertTrue(res.getGui().getWarningList().isEmpty(),
            "Expected no warnings from _build/dependencies sources.");
    }

    @Test
    public void projectDiagnosticsStayVisible() {
        CompilationResult res = test()
            .setStopOnFirstError(false)
            .withCu(compilationUnit("wurst/Local.wurst",
                "package local",
                "init",
                "    if true",
                "endpackage"))
            .run();

        Assert.assertTrue(res.getGui().getErrorList().isEmpty(), "Expected no errors.");
        Assert.assertTrue(res.getGui().getWarningList().stream()
                .anyMatch(w -> w.getMessage().contains("empty then-block")),
            "Expected warning for project sources.");
    }


    @Test
    public void test_for_from() {
        testAssertOkLines(false,
                "type unit extends handle",
                "type group extends handle",
                "package test",
                "	@extern native FirstOfGroup(group g) returns unit",
                "	@extern native GroupRemoveUnit(group g, unit u)",
                "	function group.hasNext() returns boolean",
                "		return FirstOfGroup(this) != null",
                "	function group.next() returns unit",
                "		let u = FirstOfGroup(this)",
                "		GroupRemoveUnit(this, u)",
                "		return u",
                "	function group.close()",
                "		skip",
                "	init",
                "		group g = null",
                "		for unit u from g",
                "			skip",
                "endpackage");
    }


    @Test
    public void test_for_in() {
        testAssertOkLines(false,
                "type unit extends handle",
                "type group extends handle",
                "package test",
                "	@extern native FirstOfGroup(group g) returns unit",
                "	@extern native GroupRemoveUnit(group g, unit u)",
                "	function group.iterator() returns group",
                "		// a correct implementation would return a copy",
                "		return this",
                "	function group.hasNext() returns boolean",
                "		return FirstOfGroup(this) != null",
                "	function group.next() returns unit",
                "		let u = FirstOfGroup(this)",
                "		GroupRemoveUnit(this, u)",
                "		return u",
                "	function group.close()",
                "		skip",
                "	init",
                "		group g = null",
                "		for unit u in g",
                "			skip",
                "endpackage");
    }


    @Test
    public void test_correct_escapesequence() {
        testAssertOkLines(false,
                "package test",
                "	function foo() returns string",
                "		return \"\\\\ \" ",
                "endpackage");
    }


    @Test
    public void varname_with_interface() {
        testAssertOkLines(false,
                "package test",
                "	interface I",
                "		function foo()",
                "	class C implements I",
                "		function foo()",
                "	init",
                "		I a = new C()",
                "		int a2 = 1337",
                "endpackage");
    }

    @Test
    public void testCodeNull() {
        testAssertOkLines(false,
                "package test",
                "	function foo(code c)",
                "		skip",
                "	init",
                "		foo(null)",
                "endpackage");
    }

    @Test
    public void cyclicDependency() {
        testAssertErrorsLines(false, "cyclic class hierarchy",
                "package test",
                "	class A extends A",
                "endpackage");
    }

    @Test
    public void cyclicDependency2() {
        testAssertErrorsLines(false, "Interface I has a cyclic class hierarchy",
                "package test",
                "	interface I extends I",
                "		function foo()",
                "endpackage");
    }

    @Test
    public void nonAbstractClass() {
        testAssertErrorsLines(false, "Non-abstract class A cannot have abstract functions like blub",
                "package test",
                "	class A",
                "		abstract function blub()",
                "endpackage");
    }

    @Test
    public void staticOverride() {
        testAssertErrorsLines(false, "Cannot override",
                "package test",
                "	abstract class A",
                "		abstract function blub()",
                "	class B extends A",
                "		override static function blub()",
                "endpackage");
    }

    @Test
    public void realIndex() {
        testAssertOkLines(false,
                "package test",
                "tuple color(int r, int g, int b)",
                "enum COLOR",
                "	YELLOW",
                "	ORANGE",
                "color array colors",
                "class Test",
                "	COLOR col",
                "	function colorize()",
                "		color c = color(0,0,0)",
                "		if col == COLOR.YELLOW",
                "			c = colors[col castTo int +1]",
                "		else if col == COLOR.ORANGE",
                "			c = colors[col castTo int -1]",
                "		else",
                "			c = colors[col castTo int]",
                "init",
                "	let t = new Test()",
                "	t.colorize()",
                "endpackage");
    }

    @Test
    public void constFolding() { // see #124
        testAssertOkLines(false,
                "package test",
                "init",
                "	let a = 0.00023 * 0.06",
                "	let b = 20.5 * 300.1",
                "endpackage");
    }

    @Test
    public void inlinerBugShortCircuit() { // see #123
        testAssertOkLines(true,
                "package test",
                "native testSuccess()",
                "var z = 0",
                "function sideEffect() returns boolean",
                "	z++",
                "	return true",
                "init",
                "	let b = false and sideEffect()",
                "	if z == 0",
                "		testSuccess()",
                "endpackage");
    }

    @Test
    public void inlinerBugShortCircuit2() { // see #123
        testAssertOkLines(true,
                "package test",
                "native testSuccess()",
                "var z = 0",
                "function sideEffect() returns boolean",
                "	z++",
                "	return true",
                "init",
                "	if z == 1 and sideEffect()",
                "		skip",
                "	else",
                "		testSuccess()",
                "endpackage");
    }


    @Test
    public void flattenBug() {
        testAssertOkLines(true,
                "package test",
                "native testSuccess()",
                "var x = 1",
                "function sideEffect(int r) returns int",
                "	x = 4",
                "	return r",
                "init",
                "	let y = x + sideEffect(2)",
                "	if y == 3",
                "		testSuccess()",
                "endpackage");
    }

    @Test
    public void forLoop() { // see #122
        testAssertErrorsLines(false, "must be int",
                "package test",
                "init",
                "	for s = \"\" to \"aaaa\" step \"a\"",
                "endpackage");
    }


    @Test
    public void division() {
        testAssertErrorsLines(false, "div",
                "package test",
                "int x = 5 div 3.",
                "endpackage"
        );
    }

    @Test
    public void classNull() {
        testAssertOkLines(true,
                "package test",
                "native testSuccess()",
                "class A",
                "A a = null",
                "init",
                "	if not (a != null)",
                "		testSuccess()",
                "endpackage"
        );
    }

    @Test
    public void dynamicVarFromStaticContext() { // see #139
        testAssertErrorsLines(false, "from static context",
                "package test",
                "class A",
                "	int i",
                "	static function foo() returns int",
                "		return A.i",
                "endpackage");
    }

    @Test
    public void dynamicVarFromStaticContext2() {
        testAssertErrorsLines(false, "from context",
                "package test",
                "class A",
                "	int i",
                "class B",
                "	function foo() returns int",
                "		return A.i",
                "endpackage");
    }

    @Test
    public void duplicateNamesOk() {
        testAssertOkLines(false,
                "package test",
                "class A",
                "	int i",
                "class B",
                "	int i",
                "endpackage");
    }

    @Test
    public void duplicateNames() {
        testAssertErrorsLines(false, "An element with name i already exists",
                "package test",
                "class A",
                "	int i",
                "	int i",
                "endpackage");
    }

    @Test
    public void duplicateNames2() {
        testAssertOkLines(false,
                "package test",
                "class A",
                "	int i",
                "	function i()",
                "endpackage");
    }


    @Test
    public void polarOfffsetInline() { // #149
        testAssertOkLines(true,
                "package test",
                "native testSuccess()",
                "@extern native Cos(real x) returns real",
                "@extern native Sin(real x) returns real",
                "public tuple angle(real radians)",
                "public function angle.toVec(real len) returns vec2",
                "	return vec2(Cos(this.radians)*len, Sin(this.radians)*len)",
                "public tuple vec2( real x, real y )",
                "public function vec2.op_plus( vec2 v )	returns vec2",
                "	return vec2(this.x + v.x, this.y + v.y)",
                "public function vec2.polarOffset(angle ang, real dist) returns vec2",
                "	return this + ang.toVec(dist)",
                "init",
                "	vec2 v = vec2(1,2)",
                "	v = v.polarOffset(angle(1.5708), 10)",
                "	if v.x >= 0.99 and v.x <= 1.01 and v.y >= 11.99 and v.y <= 12.01",
                "		testSuccess()",
                "endpackage"
        );
    }

    @Test
    public void inlineBug() {
        testAssertOkLines(false,
                "package test",
                "tuple vec2(real x, real y)",
                "tuple vec3(real x, real y, real z)",
                "public function vec2.withZ(real z) returns vec3",
                "	return vec3(this.x, this.y, z)",
                "init",
                "	vec2(3,4).withZ(5)",
                "endpackage");
    }

    @Test
    public void cyclicDepReadVars() {
        testAssertOkLines(false,
                "package test",
                "function self(int n) returns int",
                "	if n > 0",
                "		return self(n-1)",
                "	return n",
                "init",
                "	self(5)",
                "endpackage");
    }


    @Test
    public void staticGenerics1() {
        testAssertErrorsLines(false, "Type variables must not be used in static contexts",
                "package test",
                "class C<T>",
                "	static T t",
                "endpackage");
    }

    @Test
    public void staticGenerics2() {
        testAssertErrorsLines(false, "Type variables must not be used in static contexts",
                "package test",
                "class C<T>",
                "	static function foo(T t)",
                "endpackage");
    }

    @Test
    public void staticGenerics3() {
        testAssertOkLines(false,
                "package test",
                "class C<T>",
                "	static function foo<T>(T t)",
                "endpackage");
    }


    @Test
    public void recursive() {
        testAssertOkLines(true,
                "package test",
                "native testSuccess()",
                "function f(int n) returns int",
                "	if n <= 0",
                "		return 0",
                "	return n + f(n-1)",
                "init",
                "	if 5050 == f(100)",
                "		testSuccess()",
                "endpackage");
    }

    @Test
    public void recursive2() {
        testAssertOkLines(false,
                "package test",
                "native testSuccess()",
                "enum Stat",
                "	A",
                "	B",
                "int array statSubstat",
                "function getStat(Stat s) returns int",
                "	if statSubstat[s castTo int] != -1",
                "		let a = getStat(1 castTo Stat)",
                "	return 0",
                "init",
                "	if getStat(Stat.A) == 0",
                "		testSuccess()",
                "endpackage");
    }


    @Test
    public void underscore() {
        testAssertOkLines(false,
                "package test",
                "@extern native I2S(int i) returns string",
                "init",
                "	int _i = 1",
                "	I2S(_i)",
                "endpackage");
    }

    @Test
    public void hotdoc() {
        testAssertOkLines(false,
                "package test",
                "/** test */",
                "function foo()",
                "endpackage");
    }


    @Test
    public void memberMethodParens() {
        testAssertOkLines(false,
                "package test",
                "class A",
                "	function foo()",
                "init",
                "	(new A()).foo()",
                "endpackage");
    }

    @Test
    public void localOptimizerFail() { // essence of #237
        testAssertOkLines(true,
                "package test",
                "native testSuccess()",
                "function foo(int i) returns int",
                "	return 1",
                "function colors_hexs(int i) returns string",
                "	return \"a\"",
                "init",
                "	var aaa = foo(1)",
                "	var bbb = foo(2)",
                "	var fff = aaa",
                "	var ggg = fff div 16",
                "	var kkk = fff - ggg * 16",
                "	var ccc = \"|cff\" + colors_hexs(ggg) + colors_hexs(kkk)",
                "	var eee = bbb",
                "	var hhh = eee div 16",
                "	var iii = eee - hhh * 16",
                "	var ddd = ccc + colors_hexs(hhh) + colors_hexs(iii)",
                "	testSuccess()",
                "endpackage");
    }

    @Test
    public void genericsNull() {
        testAssertOkLines(false,
                "package test",
                "class A<T>",
                "	function foo(T t)",
                "function handleToIndex(handle h) returns int",
                "	return 1",
                "function handleFromIndex(int i) returns handle",
                "	return null",
                "init",
                "	new A<handle>().foo(null)",
                "endpackage");
    }

    @Test
    public void funcrefs1() {
        testAssertOkLines(false,
                "package test",
                "native do(code c)",
                "int x = 20",
                "function bar() returns int",
                "	if x > 0",
                "		do(function bar)",
                "		bar()",
                "		x--",
                "		return 1",
                "	else",
                "		return 2",
                "init",
                "	do(function bar)",
                "endpackage");
    }

    @Test
    public void funcrefs2() {
        testAssertOkLines(false,
                "package test",
                "native do(code c)",
                "int x = 20",
                "function bar() returns int",
                "	if x > 0",
                "		blub()",
                "		do(function bar)",
                "		x--",
                "		return 1",
                "	else",
                "		return 2",
                "function blub() returns int",
                "	if x > 0",
                "		bar()",
                "		do(function bar)",
                "		x--",
                "		return 1",
                "	else",
                "		return 2",
                "init",
                "	do(function bar)",
                "	do(function bar)",
                "endpackage");
    }

    @Test
    public void typenameAsFuncname1() {
        testAssertErrorsLines(false, "The name",
                "package test",
                "function integer()",
                "init",
                "	integer()",
                "endpackage"
        );
    }

    @Test
    public void typenameAsFuncname2() {
        testAssertErrorsLines(false, "The name",
                "type agent			    extends     handle",
                "package test",
                "function agent()",
                "init",
                "	agent()",
                "endpackage"
        );
    }

    @Test
    public void underscores_in_name() {
        testAssertOkLines(false,
                "package test",
                "native print(string msg)",
                "init",
                "	var _test_ = \"Hello\"",
                "	print(_test_)",
                "endpackage"
        );
    }

    @Test
    public void underscore_end() {
        testAssertOkLines(true,
                "package test",
                "native testSuccess()",
                "var y_ = 1",
                "function foo_(int x_) returns int",
                "    return x_ + y_",
                "init",
                "    int i_ = 2",
                "    if foo_(i_) == 3",
                "       testSuccess()");

    }

    @Test
    public void extensionFunc_noreturn() { // see #280
        testAssertErrorsLines(false, "missing a body",
                "package test",
                "function int.foo() returns int",
                "endpackage"
        );
    }

    @Test
    public void func_noreturn() { // see #280
        testAssertErrorsLines(false, "missing a body",
                "package test",
                "function foo() returns int",
                "endpackage"
        );
    }

    @Test
    public void classesCyclic() { // see #310
        testAssertErrorsLines(false, "cyclic class hierarchy",
                "package test",
                "class A extends B",
                "class B extends A",
                "endpackage"
        );
    }


    @Test
    public void doubleNativeDecl() { // see #353
        testAssertOkLines(true,
                "package test",
                "native testSuccess()",
                "init",
                "	testSuccess()",
                "endpackage",
                "package other",
                "native testSuccess()",
                "init",
                "	testSuccess()",
                "endpackage"
        );
    }

    @Test
    public void optBug() {
        testAssertOkLines(true,
                "package test",
                "native testSuccess()",
                "@extern native I2S(int x) returns string",
                "init",
                "	int x = 0",
                "	x = x + 1",
                "	x = x",
                "	I2S(x)",
                "	testSuccess()",
                "endpackage"
        );
    }

    @Test
    public void unreadVarWarning() { // #380
        testAssertOkLines(true,
                "package test",
                "native testSuccess()",
                "init",
                "	var done = false",
                "	while not done",
                "		done = true",
                "	testSuccess()"
        );
    }

    @Test
    public void unreadVarWarning2() { // #380
        testAssertErrorsLines(true, "i is never read",
                "package test",
                "@annotation public function annotation()",
                "@annotation public function extern()",
                "@extern native I2S(int x) returns string",
                "native testSuccess()",
                "init",
                "	var i = 5",
                "	I2S(i)",
                "	i = i + 1",
                "	testSuccess()"
        );
    }

    @Test
    public void unreadVarWarning3() { // #380
        testAssertErrorsLines(true, "closure-captured variable",
            "package test",
            "@annotation public function annotation()",
            "@annotation public function extern()",
            "@extern native I2S(int x) returns string",
            "native testSuccess()",
            "interface Fn",
            "	function apply()",
            "function foo(Fn _f)",
            "init",
            "	var i = 5",
            "	foo() ->",
            "		i++",
            "	if i == 5",
            "		testSuccess()"
        );
    }

    @Test
    public void forRangeStartReadsParameter() {
        CompilationResult result = test()
            .setStopOnFirstError(false)
            .executeProg(false)
            .lines(
                "package test",
                "function foo(int bar)",
                "    for i = bar to 10",
                "        skip",
                "endpackage"
            );

        Assert.assertTrue(
            result.getGui().getWarningList().stream()
                .noneMatch(w -> w.getMessage().contains("The parameter bar is never read")),
            "Unexpected unused warning for parameter bar: " + result.getGui().getWarningList()
        );
    }

    @Test
    public void forRangeLoopVarMutationWarns() {
        testAssertWarningsLines(false, "unexpected iteration side effects",
            "package test",
            "init",
            "    int stackPointer = 3",
            "    for i = 0 to stackPointer",
            "        i--",
            "endpackage");
    }


    @Test
    public void unreadVarWarningArrays() { // #813
        testAssertOkLines(false,
                "package test",
                "@annotation public function annotation()",
                "@annotation public function extern()",
                "@extern native I2S(int x) returns string",
                "init",
                "    integer array b",
                "    b[0] = 0 // Warning.",
                "    b[1] = 0 // Warning.",
                "    b[2] = 0 // No warning.",
                "    I2S(b[0])",
                "    I2S(b[1])",
                "    for i = 0 to 2",
                "        I2S(b[i])"
                );
    }


    @Test
    public void closureClassConstructor() { // # 440
        testAssertOkLines(true,
                "package test",
                "native testSuccess()",
                "abstract class Hey",
                "	construct()",
                "		testSuccess()",
                "	abstract function foo(int x) returns int",
                "init",
                "	Hey you = (int x) -> x + 1",
                "endpackage"
        );
    }


    @Test
    public void closureClassConstructorInvalid() {
        testAssertErrorsLines(false, "No default constructor for class",
                "package test",
                "abstract class Hey",
                "	construct(int x)",
                "	abstract function foo(int x) returns int",
                "init",
                "	Hey you = (int x) -> x + 1",
                "endpackage"
        );
    }

    @Test
    public void recursiveTuple() {
        testAssertErrorsLines(false, "Parameter bar is recursive",
                "package test",
                "tuple foo(foo bar)",
                "class C",
                "    foo f1 = foo(f2)",
                "    foo f2 = foo(f1)"
        );
    }

    @Test
    public void recursiveTuple2() {
        testAssertErrorsLines(false, "is recursive",
                "package test",
                "tuple foo(bar bar)",
                "tuple bar(foo foo)"
        );
    }

    @Test
    public void testLinePos() { // #462
        String input =
                "package test\n" +
                        "abstract class Hey\n" +
                        "	function foo()";

        WurstModel model = test().executeProg(false).withStdLib(false).withCu(compilationUnit("testLine", input)).run().getModel();

        model.accept(new WurstModel.DefaultVisitor() {
            @Override
            public void visit(ClassDef c) {
                super.visit(c);
                Assert.assertEquals(2, c.getSource().getLine());
            }
        });

        model.accept(new WurstModel.DefaultVisitor() {
            @Override
            public void visit(FuncDef funcDef) {
                super.visit(funcDef);
                Assert.assertEquals(3, funcDef.getSource().getLine());
            }
        });
    }


    @Test
    public void functionWithUnderscore() {
        testAssertOkLines(true,
                "package test",
                "	native testSuccess()",
                "	function _coke()",
                "		testSuccess()",
                "	init",
                "		_coke()",
                "endpackage");
    }

    @Test
    public void testInferInSuper() {
        testAssertOkLines(false,
                "package test",
                "class A",
                "    construct(code c)",
                "class B extends A",
                "    construct()",
                "        super(null)");
    }


    @Test
    public void ovveride_nativeTypeReturn() {
        testAssertOkLinesWithStdLib(false,
                "package test",
                "abstract class A",
                "    abstract function foo() returns rect",
                "class B extends A",
                "    override function foo() returns rect",
                "        return null");
    }

    @Test
    public void parseHexValues() {
        testAssertOkLines(true,
                "package test",
                "native testSuccess()",
                "init",
                "    int i1 = 12345678",
                "    int i2 = 0xBC614E",
                "    int i3 = $BC614E",
                "    if i1 == i2 and i2 == i3",
                "       testSuccess()");

    }


    @Test
    public void extensionMethodStatic() { // See #614
        testAssertErrorsLines(true, "Reference to A can only be used for calling static methods, but not for calling extension method method 'bar'.",
                "package Test",
                "native testSuccess()",
                "abstract class A",
                "    abstract function foo()",
                "public function A.bar(A listener) returns A",
                "    return listener",
                "init",
                "    let a = A.bar(() -> testSuccess())",
                "    a.foo()",
                ""
        );

    }

    @Test
    public void testNameShadowError() {
        testAssertErrorsLines(true, "Variable x hides another local variable with the same name",
            "package Test",
            "native testSuccess()",
            "function foo() returns bool",
            "    var x = 0",
            "    var sum = 0",
            "    for x in x",
            "        sum += x",
            "    return true",
            "init",
            "    if foo()",
            "        testSuccess()"
        );
    }

    @Test
    public void testCyclicDependencyError() {
        testAssertErrorsLines(true, "For loop target int doesn't provide a iterator() function",
                "package Test",
                "native testSuccess()",
                "function foo() returns bool",
                "    var x = 0",
                "    var sum = 0",
                "    for i in x",
                "        sum += i",
                "    return true",
                "init",
                "    if foo()",
                "        testSuccess()"
        );
    }


    @Test
    public void testStacktrace() {
        testAssertOkLines(true,
                "package MagicFunctions",
                "public function getStackTraceString() returns string",
                "    return \"\"",
                "endpackage",
                "package Test",
                "import MagicFunctions",
                "native testSuccess()",
                "native println(string s)",
                "function foo(int x, int y) returns bool",
                "    println(getStackTraceString())",
                "    return x < y",
                "function bar(int x) returns int",
                "    if x == 2",
                "        println(getStackTraceString())",
                "    return x",
                "init",
                "    if foo(bar(1), bar(2))",
                "        testSuccess()"
        );

        try {
            String jass = Files.toString(
                new File(TEST_OUTPUT_PATH + "BugTests_testStacktrace_stacktraceinlopt.j"),
                Charsets.UTF_8
            );
            Assert.assertTrue(jass.contains("wurst_stack_depth"));
            Assert.assertTrue(jass.contains("wurst_stack"));
            Assert.assertFalse(jass.contains("return \"\""));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Test
    public void testClassImplementsClass() {
        testAssertErrorsLines(false, "not an interface",
                "package Test",
                "abstract class A",
                "    construct(int i)",
                "class B implements A"
        );
    }

    @Test
    public void testClassExtendsClassWithoutNoArgConstructor() {
        testAssertErrorsLines(false, "The extended class <A> does not expose a no-arg constructor",
                "package Test",
                "class A",
                "    construct(int i)",
                "class B extends A"
        );
    }

    @Test
    public void ticket706() {
        testAssertErrorsLines(false, "Classes may only extend other classes",
                "package Test",
                "interface I",
                "class A extends I",
                "class B extends A",
                "init",
                "    new B()"
        );
    }

    @Test
    public void ticket709() {
        testAssertOkLinesWithStdLib(false,
                "package Test",
                "init",
                "    var s = \".\"",
                "    for i = 1 to 10",
                "        s += s",
                "    print(s)"
        );
    }


    @Test
    public void stringPlusNull1() {
        testAssertOkLines(true,
                "package Test",
                "native testSuccess()",
                "function nullString() returns string",
                "    return null",
                "init",
                "    var s = \"a\" + nullString()",
                "    if s == \"a\"",
                "        testSuccess()"
        );
    }

    @Test
    public void stringPlusNull2() {
        testAssertOkLines(true,
                "package Test",
                "native testSuccess()",
                "function nullString() returns string",
                "    return null",
                "init",
                "    var s = nullString() + nullString()",
                "    if s == null",
                "        testSuccess()"
        );
    }

    @Test
    public void stringPlusNull3() {
        testAssertOkLines(true,
                "package Test",
                "native testSuccess()",
                "function nullString() returns string",
                "    return null",
                "init",
                "    var s = nullString() + \"a\"",
                "    if s == \"a\"",
                "        testSuccess()"
        );
    }

    @Test
    public void cyclicForLoop() { // #717
        testAssertErrorsLines(true, "Could not find variable a",
                "package Test",
                "native testSuccess()",
                "function nullString() returns string",
                "    return null",
                "init",
                "    for a in a"
        );
    }

    @Test
    public void implementNothing() { // #719
        testAssertErrorsLines(false, "mismatched input '\\n' expecting {'thistype', ID}",
                "package Test",
                "class A implements",
                "init",
                "    new A()"
        );
    }

    @Test(expectedExceptions = {InterpreterException.class})
    public void subStringError() { // #728
        testAssertOkLines(true,
                "package Test",
                "native testSuccess()",
                "@extern native SubString(string s, int s, int e) returns string",
                "init",
                "    if SubString(\"blubber\", 1000, 1002) == null",
                "        testSuccess()"
        );
    }

    @Test
    public void underlineArray() {
        testAssertOkLines(true,
                "package Test",
                "native testSuccess()",
                "init",
                "    int array _ar",
                "    _ar[1] = 2",
                "    if _ar[1] == 2",
                "        testSuccess()",
                "    testSuccess()"
        );
    }

    @Test
    public void test_null_in_jass() {
        testAssertOkLines(false,
                "function blub takes integer a returns integer",
                "	if a == null then",
                "		return 1",
                "	else",
                "		return 2",
                "	endif",
                "endfunction",
                "package test",
                "	native testSuccess()",
                "	init",
                "		if blub(0) == 1 and blub(42) == 2",
                "			testSuccess()",
                "endpackage");
    }

    @Test
    public void testNestedTimerClosure() { // see #765
        testAssertOkLines(false,
                "package Hello",
                "native blub(code c)",
                "public function doPeriodically(real time, CallbackPeriodic cb) returns CallbackPeriodic",
                "    cb.start(time)",
                "    return cb",
                "",
                "public tuple dialog_update_fn_res(string name, bool display)",
                "",
                "public abstract class PeriodicDialogUpdateFn",
                "    protected abstract function call(CallbackPeriodic cb) returns dialog_update_fn_res",
                "",
                "public abstract class CallbackPeriodic",
                "    private PeriodicDialogUpdateFn update_fn = null",
                "",
                "    protected abstract function call(thistype cb)",
                "",
                "    function start(real time)",
                "        blub(function staticCallback)",
                "",
                "    private static function staticCallback()",
                "        CallbackPeriodic cb = null",
                "        if cb.update_fn != null",
                "            cb.update_fn.call(cb)",
                "",
                "class X",
                "",
                "class LinkedList<X>",
                "    function forEach(LLItrClosure<X> f)",
                "",
                "public interface LLItrClosure<T>",
                "    function run(T t)",
                "",
                "init",
                "    doPeriodically(1.0) cb ->",
                "        let clean_queue = new LinkedList<X>()",
                "        clean_queue.forEach() itr ->",
                ""
        );
    }


    @Test
    public void negativeNumberLiterals() {
        testAssertOkLines(true,
                "package Test",
                "native testSuccess()",
                "native testFail(string msg)",
                "init",
                "    if 0117 != 79",
                "        testFail(\"a\")",
                "    if -0117 != -79",
                "        testFail(\"b\")",
                "    if 0x4f != 79",
                "        testFail(\"c\")",
                "    if -0x4f != -79",
                "        testFail(\"d\")",
                "    if $4f != 79",
                "        testFail(\"e\")",
                "    if -$4f != -79",
                "        testFail(\"f\")",
                "    testSuccess()"
        );
    }

	@Test
	public void testSelfAssignmentWarning() {
		testAssertErrorsLines(false, "The assignment to local variable i probably has no effect",
			"package test",
			"@annotation public function annotation()",
			"@annotation public function extern()",
			"@extern native I2S(int x) returns string",
			"native testSuccess()",
			"init",
			"	var i = 5",
			"	I2S(i)",
			"	i = i",
			"	I2S(i)",
			"	testSuccess()"
		);
	}

	@Test
	public void testSelfAssignmentWarningDot() {
		testAssertErrorsLines(false, "The assignment to variable i probably has no effect",
			"package test",
			"@annotation public function annotation()",
			"@annotation public function extern()",
			"@extern native I2S(int x) returns string",
			"native testSuccess()",
			"class A",
			"	var i = 5",
			"	construct()",
			"		this.i = i",
			"init",
			"	new A()",
			"	testSuccess()"
		);
	}

	@Test
	public void testSelfAssignmentNoWarning() {
		testAssertOkLines(true,
			"package test",
			"@annotation public function annotation()",
			"@annotation public function extern()",
			"@extern native I2S(int x) returns string",
			"native testSuccess()",
			"class A",
			"	var i = 5",
			"	construct(int i)",
			"		this.i = i",
			"init",
			"	new A(1)",
			"	testSuccess()"
		);
	}

    @Test
    public void bitset_add() {
        testAssertOkLines(true,
            "package Test",
            "native testSuccess()",
            "native testFail(string msg)",
            "@extern native I2S(int i) returns string",
            "public tuple bitset(int val)",
            "public function int.pow(int x) returns int",
            "    int result = 1",
            "    for int i=1 to x",
            "        result *= this",
            "    return result",
            "public function bitset.add(int v) returns bitset",
            "    let pow = 2 .pow(v)",
            "    return not this.containsPow(pow) ? bitset(this.val + pow) : this",
            "function bitset.containsPow(int pow) returns boolean",
            "    return (this.val mod (pow * 2)) >= pow",
            "init",
            "    let a = bitset(5)", // {0,2}
            "    let res = a.add(1)",
            "    if res.val == 7",
            "        testSuccess()",
            "    else",
            "        testFail(I2S(res.val))"
        );
    }

    @Test
    public void middlewareOverload() throws IOException {
        testAssertOkFile(new File(TEST_DIR + "MiddlewareOverload.wurst"), true);
    }

    @Test
    public void middlewareOverloadMin() throws IOException {
        testAssertOkFile(new File(TEST_DIR + "MiddlewareOverload_min.wurst"), false);
    }

    @Test
    public void cannotVallDynamicBug() {
        testAssertOkLines(true,
            "package Test",
            "native testSuccess()",
            "public abstract class VoidFunction<T>",
            "    abstract function call(T t)",
            "int x = 0",
            "function foo(int i)",
            "    x++",
            "    VoidFunction<int> f = j -> bar(j - 1)",
            "    f.call(i)",
            "function bar(int i)",
            "    x++",
            "    VoidFunction<int> f = j -> foo(j - 1)",
            "    if i > 0",
            "        f.call(i)",
            "init",
            "    bar(10)",
            "    if x == 11",
            "        testSuccess()"
        );
    }

    @Test
    public void cycle_with_generics() {
        testAssertOkLines(true,
            "package Test",
            "native testSuccess()",
            "public abstract class VoidFunction<T>",
            "    abstract function call(T t)",
            "int x = 0",
            "function foo(int i)",
            "    x++",
            "    VoidFunction<int> f = j -> bar(j - 1)",
            "    f.call(i)",
            "function bar(int i)",
            "    x++",
            "    VoidFunction<int> f = j -> foo(j - 1)",
            "    if i > 0",
            "        f.call(i)",
            "init",
            "    bar(10)",
            "    if x == 11",
            "        testSuccess()"
        );
    }

    @Test
    public void executeFuncWithStackTrace() {
        testAssertOkLines(true,
            "package Test",
            "native testSuccess()",
            "@extern native ExecuteFunc(string f)",
            "function getStackTraceString() returns string",
            "    return \"foo\"",
            "class A",
            "    function bar()",
            "        testSuccess()",
            "A a = new A",
            "function foo()",
            "    a.bar()", // calling a function to ensure stacktraces are needed
            "init",
            "    ExecuteFunc(\"foo\")"
        );
    }

    @Test
    public void executeFuncBridgeKeepsCallerStackFrames() {
        test().executeProg(false).executeTests(false).lines(
            "package Test",
            "@extern native ExecuteFunc(string f)",
            "function getStackTraceString() returns string",
            "    return \"\"",
            "function foo()",
            "    getStackTraceString()",
            "function caller()",
            "    getStackTraceString()",
            "    ExecuteFunc(\"foo\")",
            "    getStackTraceString()",
            "init",
            "    caller()"
        );

        try {
            String jass = Files.toString(
                new File(TEST_OUTPUT_PATH + "BugTests_executeFuncBridgeKeepsCallerStackFrames_stacktraceinlopt.j"),
                Charsets.UTF_8
            );
            Assert.assertTrue(jass.contains("function bridge_foo"));
            Assert.assertFalse(jass.contains("bridge_oldStackDepth"));
            Assert.assertFalse(jass.contains("set wurst_stack_depth = 0"));
            Assert.assertTrue(jass.contains("via ExecuteFunc"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Test
    public void agentTypeComparisonsWurst() {
        testAssertErrorsLinesWithStdLib(true, "Cannot compare types sound with rect",
            "package Test",
            "function compare(sound s, rect r) returns boolean",
            "    return s == r",
            "init",
            "    compare(null, null)"
        );
    }

    @Test
    public void minusRewrite() {
        testAssertOkLines(true,
            "package Test",
            "native testSuccess()",
            "init",
            "    let i = 0 - 100",
            "    if i == -100",
            "        testSuccess()"
        );
    }

    @Test
    public void duplicateNameInClassHierachy() {
        testAssertErrorsLines(false, "Variable x in class B hides variable x from superclass A",
            "package test",
            "native testSuccess()",
            "class A",
            "    int x",
            "class B extends A",
            "    int x",
            "init",
            "	let b = new B()",
            "	if b != null",
            "		testSuccess()",
            "endpackage");
    }

    @Test
    public void callingDestroyThisInConstructor() {
        testAssertWarningsLines(false, "Should not destroy 'this' in constructor",
            "package test",
            "native testSuccess()",
            "class A",
            "    construct()",
            "       destroy this",
            "init",
            "	let b = new A()",
            "	if b != null",
            "		testSuccess()",
            "endpackage");
    }
    @Test
    public void derivedGenericClassConstructor() {
        testAssertOkLinesWithStdLib(true,
            "package test",
            "import LinkedList",
            "public class ListIterator<T> extends LLIterator<T>",
            "    construct(LinkedList<T> parent)",
            "        super(parent)",
            "init",
            "	let b = new ListIterator<int>(new LinkedList<int>())",
            "	if b != null",
            "		testSuccess()",
            "endpackage");
    }

    @Test
    public void derivedGenericClassConstructorNewGenerics() {
        testAssertOkLines(true,
            "package test",
            "native testSuccess()",
            "class A<T:>",
            "    T value",
            "class B<X:>",
            "    A<X> a",
            "    construct(A<X> a)",
            "        this.a = a",
            "public class C<T:> extends B<T>",
            "    construct(A<T> parent)",
            "        super(parent)",
            "init",
            "	let b = new C<int>(new A<int>())",
            "	if b != null",
            "		testSuccess()",
            "endpackage");
    }


    @Test
    public void overloadsHiddenBySubclassName_onlyZeroArgSeen_errorsOnArgs() {
        testAssertOkLines(true,
            "package Test",
            "native testSuccess()",
            "native println(string s)",
            "class A",
            "    function func(int i)",
            "        println(\"func_int...\")",
            "",
            "    function func(string s)",
            "        println(\"func_string...\")",
            "",
            "class B extends A",
            "    function func()",
            "        println(\"func_noarg\")",
            "",
            "    function func(bool b)",
            "        println(\"func_boolean...\")",
            "",
            "init",
            "    let b = new B()",
            "    b.func()",
            "    b.func(true)",
            "    b.func(1)",
            "    b.func(\"1\")",
            "    testSuccess()"
        );
    }

    @Test
    public void iteratorManualType() {
        testAssertOkLinesWithStdLib(true,
            "package test",
            "import Printing",
            "import LinkedListModule",
            "class A",
            "    use LinkedListModule",
            "    string s",
            "init",
            "    A a1 = new A()",
            "    a1.s = \"hello\"",
            "    A a2 = new A()",
            "    a2.s = \"world\"",
            "    int itrCount = 0",
            "    let itr = A.iterator()",
            "    while itr.hasNext()",
            "        let a = itr.next()",
            "        itrCount += 1",
            "        print(a.s)",
            "    itr.close()",
            "    if itrCount == 2",
            "        testSuccess()",
            "endpackage"

            );
    }

    @Test
    public void moduleInOtherPackage() {
        String pkgLinkedList = string(
            "package LinkedListModule",
            "public module LinkedListModule",
            "    static function iterator() returns Iterator",
            "        return new Iterator()",
            "    static class Iterator",
            "        LinkedListModule.thistype current = null",
            "        function hasNext() returns boolean",
            "            return false",
            "        function next() returns LinkedListModule.thistype",
            "            return current",
            "        function close()",
            "            destroy this",
            "endpackage"
        );

        // --- Minimal string iterator stub to create competing iterator symbol ---
        String pkgStrIter = string(
            "package StrIter",
            "public function string.iterator() returns StringIterator",
            "    return new StringIterator(this)",
            "public class StringIterator",
            "    string str",
            "    construct(string s)",
            "        this.str = s",
            "    function hasNext() returns boolean",
            "        return false",
            "    function next() returns string",
            "        return \"\"",
            "    function close()",
            "        destroy this",
            "endpackage"
        );

        // --- Reproducer using both packages ---
        String pkgHello = string(
            "package Hello",
            "import LinkedListModule",
            "import StrIter",
            "native print(string s)",
            "native testSuccess()",
            "",
            "class A",
            "    use LinkedListModule",
            "",
            "init",
            "    let itr = A.iterator()",
            "    while itr.hasNext()",
            "        let a = itr.next()",
            "        destroy a",
            "    itr.close()",
            "    let s = \"hello\".iterator()",
            "    while s.hasNext()",
            "        print(s.next())",
            "    s.close()",
            "    testSuccess()",
            "endpackage"
        );
        testAssertOkLines(true,
            pkgLinkedList,
            pkgStrIter,
            pkgHello
        );
    }

    @Test
    public void linkedListModule_perClassStatics_andTyping_ok() {
        testAssertOkLinesWithStdLib(true,
            "package Test",
            "import LinkedListModule",
            "",
            "native println(string s)",
            "",
            "class A",
            "    use LinkedListModule",
            "",
            "class B extends A",
            "    use LinkedListModule",
            "",
            "class C extends A",
            "",
            "class D extends C",
            "    use LinkedListModule",
            "",
            "function doSmth()",
            "    // Iteration should compile for any class that uses LinkedListModule:",
            "    for a in A",
            "        // ok: iterates A list",
            "    for b in B",
            "        // ok: iterates B list (distinct from A)",
            "",
            "    // Accessing class statics should be typed to that concrete class:",
            "    B b2 = B.first        // must typecheck as B, not A",
            "    A a2 = A.first        // must typecheck as A",
            "",
            "    // D extends C but only D uses the module; typing must be D:",
            "    var d = D.first       // type D",
            "    while d != null",
            "        d = d.next        // type D",
            "",
            "init",
            "    // We don’t rely on runtime behavior here—this test targets typing/name resolution.",
            "    // If all lines above typecheck and run, we count it as success:",
            "    doSmth()",
            "    testSuccess()"
        );
    }

    @Test
    public void testTooltipGenerator() {
        test().withStdLib().lines(
            "package Test",
            "import AbilityObjEditing",
            "import HashMap",
            "",
            "@configurable constant TITLE_COLOR = \"|cff3B97D3\"",
            "@configurable constant TITLE_TTYPE = \"Target Type:\"",
            "@configurable constant MAX_TITLES = 10",
            "",
            "@configurable function buildLearnTooltip(string name) returns string",
            "\treturn \"|cffFFCC00Learn|r \" + name + \" - [|cffffcc00Level %d|r]\"",
            "",
            "@configurable function buildActiveTooltip(string name, int lvl) returns string",
            "\treturn name + \"  - Level \" + lvl.toString()",
            "",
            "@configurable function buildPassiveTooltip(string name, int lvl) returns string",
            "\treturn name + \"  - Level \" + lvl.toString()",
            "",
            "@configurable function buildDescription(string description) returns string",
            "\treturn \"|cff919191\" + description + \"|r\"",
            "\t",
            "public class AbilityTooltipGenerator implements TooltipGenerator",
            "\tprivate constant propMap = new IterableMap<string, StringLevelClosure>",
            "\tprivate var description = \"\"",
            "",
            "\tprivate var levels = 1",
            "\tprivate var hotkey = \"\"",
            "\tprivate var name = \"\"",
            "",
            "\tconstruct(string description)",
            "\t\tthis.description = description",
            "",
            "\tconstruct()",
            "",
            "\toverride function addProperty(string title, StringLevelClosure lc)",
            "\t\tpropMap.put(title, lc)",
            "",
            "\toverride function applyToDef(AbilityDefinition def)",
            "\t\tlevels = propMap.has(\"Levels\") ? fixValue(propMap.getAndRemove(\"Levels\").run(0)).toInt() : 1",
            "\t\tname = propMap.has(\"Name\") ? propMap.getAndRemove(\"Name\").run(0) : \"unnamed\"",
            "\t\thotkey = propMap.has(\"Hotkey\") ? propMap.getAndRemove(\"Hotkey\").run(0) : \"Q\"",
            "\t\tdef..setTooltipLearn(generateTooltipLearn())",
            "\t\t..setTooltipLearnExtended(generateTooltipExtended(-1, true))",
            "\t\tfor i = 1 to levels",
            "\t\t\tdef..setTooltipNormal(i, generateTooltipNormal(i))",
            "\t\t\t..setTooltipNormalExtended(i, generateTooltipExtended(i, false))",
            "",
            "\tprivate function generateTooltipLearn() returns string",
            "\t\treturn buildLearnTooltip(name)",
            "",
            "\tprivate function generateTooltipNormal(int lvl) returns string",
            "\t\treturn buildActiveTooltip(name, lvl)",
            "",
            "\tprivate function generateTooltipExtended(int lvl, boolean learn) returns string",
            "\t\tvar s = \"\"",
            "",
            "\t\tfor key in propMap",
            "\t\t\tvar tmp = \"\"",
            "\t\t\tvar isConstantValue = true",
            "\t\t\tlet val = fixValue(propMap.get(key).run(1))",
            "\t\t\tfor i = 2 to levels",
            "\t\t\t\tlet tval = fixValue(propMap.get(key).run(i))",
            "\t\t\t\tif val != tval",
            "\t\t\t\t\tisConstantValue = false",
            "\t\t\t\t\tbreak",
            "",
            "\t\t\tif isConstantValue",
            "\t\t\t\ttmp += \"|cffFFCC00\" + val + \"|r \"",
            "\t\t\telse",
            "\t\t\t\tfor i = 1 to levels",
            "\t\t\t\t\ttmp += fixValue(propMap.get(key).run(i)) + (i < levels ? \"/\" : \"\")",
            "",
            "\t\t\ts += TITLE_COLOR + key + \":|r \" + colorLevelValue(tmp, lvl, levels) + \"\\n\"",
            "\t\ts += \"\\n\"",
            "\t\ts += buildDescription(description)",
            "\t\treturn s",
            "",
            "\tprivate static function fixValue(string value) returns string",
            "\t\tif value == \"\"",
            "\t\t\treturn \"\"",
            "\t\tvar s = value",
            "\t\tlet len = s.length()",
            "\t\tlet lastChar = s.substring(len-1, len)",
            "\t\tif lastChar == \".\"",
            "\t\t\ts = s.substring(0, len-1)",
            "\t\telse if len > 1 and s.substring(len-2, len) == \".0\"",
            "\t\t\ts = s.substring(0, len-2)",
            "\t\treturn s",
            "",
            "\tprivate static function colorLevelValue(string oldString, int lvl, int maxLevel) returns string",
            "\t\tvar _newString = \"\"",
            "\t\tvar charCount = 0",
            "\t\tvar charPosCount = 0",
            "\t\tint array charPos",
            "\t\tlet oldLen = oldString.length()",
            "\t\tfor char in oldString",
            "\t\t\tif char == \"/\"",
            "\t\t\t\tcharPos[charPosCount] = charCount",
            "\t\t\t\tcharPosCount++",
            "\t\t\tcharCount++",
            "\t\tif lvl == -1",
            "\t\t\t_newString = oldString",
            "\t\telse if charPosCount <= 0",
            "\t\t\t_newString = \"|cffFFCC00\" + oldString",
            "\t\telse if maxLevel <= 1",
            "\t\t\t_newString = \"|cffFFCC00\" + oldString",
            "\t\telse if lvl == 1",
            "\t\t\t_newString = \"|cffFFCC00\" + oldString.substring(0, charPos[0]) + \"|r\" + oldString.substring(charPos[0], oldLen)",
            "\t\telse if lvl == maxLevel",
            "\t\t\t_newString = oldString.substring(0, charPos[lvl-2] + 1) + \"|cffFFCC00\" + oldString.substring(charPos[lvl-2] + 1, oldLen) + \"|r\"",
            "\t\telse",
            "\t\t\t_newString = oldString.substring(0, charPos[lvl-2] + 1) + \"|cffFFCC00\" + oldString.substring(charPos[lvl-2] + 1, charPos[lvl-1]) + \"|r\" + oldString.substring(charPos[lvl-1], oldLen)",
            "\t\treturn _newString",
            "constant h = compiletime(gen())",
            "",
            "function gen() returns int",
            "    let myInt = 20",
            "    let tgen = new AbilityTooltipGenerator(\"Test1\")",
            "    new AbilityDefinitionItemHealAoe('1234')",
            "        ..registerTooltipGenerator(tgen)",
            "        ..tooltipStartListen()",
            "        ..addTooltipProperty(\"String\", _ -> \"a\")",
            "        ..tooltipStopListen(true)",
            "",
            "    return 1",
            "init",
            "    testSuccess()"

        );
    }

}
