package otto

import (
	"fmt"
	"testing"
)

func TestRegExp(t *testing.T) {
	tt(t, func() {
		test, _ := test()

		test(`
            [
                /abc/.toString(),
                /abc/gim.toString(),
                ""+/abc/gi.toString(),
                new RegExp("1(\\d+)").toString(),
            ];
        `, "/abc/,/abc/gim,/abc/gi,/1(\\d+)/")

		test(`
            [
                new RegExp("abc").exec("123abc456"),
                null === new RegExp("xyzzy").exec("123abc456"),
                new RegExp("1(\\d+)").exec("123abc456"),
                new RegExp("xyzzy").test("123abc456"),
                new RegExp("1(\\d+)").test("123abc456"),
                new RegExp("abc").exec("123abc456"),
            ];
        `, "abc,true,123,23,false,true,abc")

		test(`new RegExp("abc").toString()`, "/abc/")
		test(`new RegExp("abc", "g").toString()`, "/abc/g")
		test(`new RegExp("abc", "mig").toString()`, "/abc/gim")

		result := test(`/(a)?/.exec('b')`, ",")
		is(result.object().get("0"), "")
		is(result.object().get("1"), "undefined")
		is(result.object().get(propertyLength), 2)

		result = test(`/(a)?(b)?/.exec('b')`, "b,,b")
		is(result.object().get("0"), "b")
		is(result.object().get("1"), "undefined")
		is(result.object().get("2"), "b")
		is(result.object().get(propertyLength), 3)

		test(`/\u0041/.source`, "\\u0041")
		test(`/\a/.source`, "\\a")
		test(`/\;/.source`, "\\;")

		test(`/a\a/.source`, "a\\a")
		test(`/,\;/.source`, ",\\;")
		test(`/ \ /.source`, " \\ ")

		// Start sanity check...
		test("eval(\"/abc/\").source", "abc")
		test("eval(\"/\u0023/\").source", "#")
		test("eval(\"/\u0058/\").source", "X")
		test("eval(\"/\\\u0023/\").source == \"\\\u0023\"", true)
		test("'0x' + '0058'", "0x0058")
		test("'\\\\' + '0x' + '0058'", "\\0x0058")
		// ...stop sanity check

		test(`abc = '\\' + String.fromCharCode('0x' + '0058'); eval('/' + abc + '/').source`, "\\X")
		test(`abc = '\\' + String.fromCharCode('0x0058'); eval('/' + abc + '/').source == "\\\u0058"`, true)
		test(`abc = '\\' + String.fromCharCode('0x0023'); eval('/' + abc + '/').source == "\\\u0023"`, true)
		test(`abc = '\\' + String.fromCharCode('0x0078'); eval('/' + abc + '/').source == "\\\u0078"`, true)

		test(`
            var abc = Object.getOwnPropertyDescriptor(RegExp, "prototype");
            [   [ typeof RegExp.prototype ],
                [ abc.writable, abc.enumerable, abc.configurable ] ];
        `, "object,false,false,false")
	})
}

func TestRegExp_global(t *testing.T) {
	tt(t, func() {
		test, _ := test()

		test(`
            var abc = /(?:ab|cd)\d?/g;
            var found = [];
            do {
                match = abc.exec("ab  cd2  ab34  cd");
                if (match !== null) {
                    found.push(match[0]);
                } else {
                    break;
                }
            } while (true);
            found;
        `, "ab,cd2,ab3,cd")
	})
}

func TestRegExp_exec(t *testing.T) {
	tt(t, func() {
		test, _ := test()

		test(`
            abc = /./g;
            def = '123456';
            ghi = 0;
            while (ghi < 100 && abc.exec(def) !== null) {
                ghi += 1;
            }
            [ ghi, def.length, ghi == def.length ];
        `, "6,6,true")

		test(`
            abc = /[abc](\d)?/g;
            def = 'a0 b c1 d3';
            ghi = 0;
            lastIndex = 0;
            while (ghi < 100 && abc.exec(def) !== null) {
                lastIndex = abc.lastIndex;
                ghi += 1;

            }
            [ ghi, lastIndex ];
        `, "3,7")

		test(`
            abc = /(\d)?(s)/g;
            def = 's';
            ghi = abc.exec(def);
            [ ghi[1] === undefined, ghi[2] === 's' ];
        `, "true,true")

		test(`
            abc = /(\d)?(s)/g;
            def = '%s';
            abc.lastIndex = 1;
            ghi = abc.exec(def);
            [ ghi[1] === undefined, ghi[2] === 's' ];
        `, "true,true")

		test(`
            var abc = /[abc](\d)?/.exec("a0 b c1 d3");
            [ abc.length, abc.input, abc.index, abc ];
        `, "2,a0 b c1 d3,0,a0,0")

		test(`raise:
            var exec = RegExp.prototype.exec;
            exec("Xyzzy");
        `, "TypeError: Calling RegExp.exec on a non-RegExp object")

		test(`
            var abc = /\w{3}\d?/.exec("CE\uFFFFL\uFFDDbox127");
            [ abc.input.length, abc.length, abc.input, abc.index, abc ];
        `, "11,1,CE\uFFFFL\uFFDDbox127,5,box1")

		test(`
        var abc = /\w{3}\d?/.exec("CE😋box127");
        [ abc.input.length, abc.length, abc.input, abc.index, abc ];
    `, "10,1,CE😋box127,4,box1")

		test(`RegExp.prototype.exec.length`, 1)
		test(`RegExp.prototype.exec.prototype`, "undefined")
	})
}

func TestRegExp_test(t *testing.T) {
	tt(t, func() {
		test, _ := test()

		test(`RegExp.prototype.test.length`, 1)
		test(`RegExp.prototype.test.prototype`, "undefined")
	})
}

func TestRegExp_toString(t *testing.T) {
	tt(t, func() {
		test, _ := test()

		test(`RegExp.prototype.toString.length`, 0)
		test(`RegExp.prototype.toString.prototype`, "undefined")
	})
}

func TestRegExp_zaacbbbcac(t *testing.T) {
	if true {
		return
	}

	tt(t, func() {
		test, _ := test()

		// FIXME? TODO /(z)((a+)?(b+)?(c))*/.exec("zaacbbbcac")
		test(`
            var abc = /(z)((a+)?(b+)?(c))*/.exec("zaacbbbcac");
            [ abc.length, abc.index, abc ];
        `, "6,0,zaacbbbcac,z,ac,a,,c")
	})
}

func TestRegExpCopying(t *testing.T) {
	tt(t, func() {
		test, _ := test()

		test(`
            abc = /xyzzy/i;
            def = RegExp(abc);
            abc.indicator = 1;
            [ abc.indicator, def.indicator ];
        `, "1,1")

		test(`raise:
            RegExp(new RegExp("\\d"), "1");
        `, "TypeError: Cannot supply flags when constructing one RegExp from another")
	})
}

func TestRegExp_multiline(t *testing.T) {
	tt(t, func() {
		test, _ := test()

		test(`
            var abc = /s$/m.exec("pairs\nmakes\tdouble");
            [ abc.length, abc.index, abc ];
        `, "1,4,s")
	})
}

func TestRegExp_source(t *testing.T) {
	tt(t, func() {
		test, _ := test()

		test(`
            [ /xyzzy/i.source, /./i.source ];
        `, "xyzzy,.")

		test(`
            var abc = /./i;
            var def = new RegExp(abc);
            [ abc.source, def.source, abc.source === def.source ];
        `, ".,.,true")

		test(`
            var abc = /./i;
            var def = abc.hasOwnProperty("source");
            var ghi = abc.source;
            abc.source = "xyzzy";
            [ def, abc.source ];
        `, "true,.")
	})
}

func TestRegExp_newRegExp(t *testing.T) {
	tt(t, func() {
		test, _ := test()

		test(`
            Math.toString();
            var abc = new RegExp(Math,eval("\"g\""));
            [ abc, abc.global ];
        `, "/[object Math]/g,true")
	})
}

func TestRegExp_flags(t *testing.T) {
	tt(t, func() {
		test, _ := test()

		test(`
            var abc = /./i;
            var def = new RegExp(abc);
            [ abc.multiline == def.multiline, abc.global == def.global, abc.ignoreCase == def.ignoreCase ];
        `, "true,true,true")
	})
}

func TestRegExp_controlCharacter(t *testing.T) {
	tt(t, func() {
		test, _ := test()

		for code := 0x41; code < 0x5a; code++ {
			val := string(rune(code - 64))
			test(fmt.Sprintf(`
                var code = 0x%x;
                var string = String.fromCharCode(code %% 32);
                var result = (new RegExp("\\c" + String.fromCharCode(code))).exec(string);
                [ code, string, result ];
            `, code), fmt.Sprintf("%d,%s,%s", code, val, val))
		}
	})
}

func TestRegExp_notNotEmptyCharacterClass(t *testing.T) {
	tt(t, func() {
		test, _ := test()

		test(`
            var abc = /[\s\S]a/m.exec("a\naba");
            [ abc.length, abc.input, abc ];
        `, "1,a\naba,\na")
	})
}

func TestRegExp_compile(t *testing.T) {
	tt(t, func() {
		test, _ := test()

		test(`
            var abc = /[\s\S]a/;
            abc.compile('^\w+');
        `, "undefined")
	})
}
