/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package test.text.harmony_regex

import kotlin.text.*
import kotlin.test.*

class MatchResultTest {

    fun assertTrue(msg: String, value: Boolean) = assertTrue(value, msg)
    fun assertFalse(msg: String, value: Boolean) = assertFalse(value, msg)

    internal var testPatterns = arrayOf("(a|b)*abb", "(1*2*3*4*)*567", "(a|b|c|d)*aab", "(1|2|3|4|5|6|7|8|9|0)(1|2|3|4|5|6|7|8|9|0)*", "(abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ)*", "(a|b)*(a|b)*A(a|b)*lice.*", "(a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z)(a|b|c|d|e|f|g|h|" + "i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z)*(1|2|3|4|5|6|7|8|9|0)*|while|for|struct|if|do")

    internal var groupPatterns = arrayOf("(a|b)*aabb", "((a)|b)*aabb", "((a|b)*)a(abb)", "(((a)|(b))*)aabb", "(((a)|(b))*)aa(b)b", "(((a)|(b))*)a(a(b)b)")

    @Test fun testReplaceAll() {
        val input = "aabfooaabfooabfoob"
        val pattern = "a*b"
        val regex = Regex(pattern)

        assertEquals("-foo-foo-foo-", regex.replace(input, "-"))
    }

    @Test fun testReplaceFirst() {
        val input = "zzzdogzzzdogzzz"
        val pattern = "dog"
        val regex = Regex(pattern)

        assertEquals("zzzcatzzzdogzzz", regex.replaceFirst(input, "cat"))
    }

    /*
     * Class under test for String group(int)
     */
    @Test fun testGroupint() {
        val positiveTestString = "ababababbaaabb"

        // test IndexOutOfBoundsException
        // //
        for (i in groupPatterns.indices) {
            val regex = Regex(groupPatterns[i])
            val result = regex.matchEntire(positiveTestString)!!
            try {
                // groupPattern <index + 1> equals to number of groups
                // of the specified pattern
                // //
                result.groups[i + 2]
                fail("IndexOutBoundsException expected")
                result.groups[i + 100]
                fail("IndexOutBoundsException expected")
                result.groups[-1]
                fail("IndexOutBoundsException expected")
                result.groups[-100]
                fail("IndexOutBoundsException expected")
            } catch (e: IndexOutOfBoundsException) {
            }
        }

        val groupResults = arrayOf(
                arrayOf("a"),
                arrayOf("a", "a"),
                arrayOf("ababababba", "a", "abb"),
                arrayOf("ababababba", "a", "a", "b"),
                arrayOf("ababababba", "a", "a", "b", "b"),
                arrayOf("ababababba", "a", "a", "b", "abb", "b")
        )

        for (i in groupPatterns.indices) {
            val regex = Regex(groupPatterns[i])
            val result = regex.matchEntire(positiveTestString)!!
            for (j in 0..groupResults[i].size - 1) {
                assertEquals(groupResults[i][j], result.groupValues[j + 1], "i: $i j: $j")
            }
        }

    }

    @Test fun testGroup() {
        val positiveTestString = "ababababbaaabb"
        val negativeTestString = "gjhfgdsjfhgcbv"
        for (element in groupPatterns) {
            val regex = Regex(element)
            val result = regex.matchEntire(positiveTestString)!!
            assertEquals(positiveTestString, result.groupValues[0])
            assertEquals(positiveTestString, result.groups[0]!!.value)
            assertEquals(0 until positiveTestString.length, result.groups[0]!!.range)
        }

        for (element in groupPatterns) {
            val regex = Regex(element)
            val result = regex.matchEntire(negativeTestString)
            assertEquals(result, null)
        }
    }

    @Test fun testGroupPossessive() {
        val regex = Regex("((a)|(b))++c")
        assertEquals("a", regex.matchEntire("aac")!!.groupValues[1])
    }

    @Test fun testMatchesMisc() {
        val posSeq = arrayOf(
                arrayOf("abb", "ababb", "abababbababb", "abababbababbabababbbbbabb"),
                arrayOf("213567", "12324567", "1234567", "213213567", "21312312312567", "444444567"),
                arrayOf("abcdaab", "aab", "abaab", "cdaab", "acbdadcbaab"),
                arrayOf("213234567", "3458", "0987654", "7689546432", "0398576", "98432", "5"),
                arrayOf("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
                        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"),
                arrayOf("ababbaAabababblice", "ababbaAliceababab", "ababbAabliceaaa", "abbbAbbbliceaaa", "Alice"),
                arrayOf("a123", "bnxnvgds156", "for", "while", "if", "struct"))

        for (i in testPatterns.indices) {
            val regex = Regex(testPatterns[i])
            for (j in 0..posSeq[i].size - 1) {
                assertTrue("Incorrect match: " + testPatterns[i] + " vs " + posSeq[i][j], regex.matches(posSeq[i][j]))
            }
        }
    }

    @Test fun testMatchesQuantifiers() {
        val testPatternsSingles = arrayOf("a{5}", "a{2,4}", "a{3,}")
        val testPatternsMultiple = arrayOf("((a)|(b)){1,2}abb", "((a)|(b)){2,4}", "((a)|(b)){3,}")

        val stringSingles = arrayOf(
                arrayOf("aaaaa", "aaa"),
                arrayOf("aa", "a", "aaa", "aaaaaa", "aaaa", "aaaaa"),
                arrayOf("aaa", "a", "aaaa", "aa")
        )

        val stringMultiples = arrayOf(
                arrayOf("ababb", "aba"),
                arrayOf("ab", "b", "bab", "ababa", "abba", "abababbb"),
                arrayOf("aba", "b", "abaa", "ba")
        )

        for (i in testPatternsSingles.indices) {
            val regex = Regex(testPatternsSingles[i])
            for (j in 0..stringSingles.size / 2 - 1) {
                assertTrue("Match expected, but failed: " + regex.pattern + " : " + stringSingles[i][j],
                        regex.matches(stringSingles[i][j * 2])
                )
                assertFalse("Match failure expected, but match succeed: " + regex.pattern + " : " + stringSingles[i][j * 2 + 1],
                        regex.matches(stringSingles[i][j * 2 + 1])
                )
            }
        }

        for (i in testPatternsMultiple.indices) {
            val regex = Regex(testPatternsMultiple[i])
            for (j in 0..stringMultiples.size / 2 - 1) {
                assertTrue("Match expected, but failed: " + regex.pattern + " : " + stringMultiples[i][j],
                        regex.matches(stringMultiples[i][j * 2])
                )
                assertFalse("Match failure expected, but match succeed: " + regex.pattern + " : " + stringMultiples[i][j * 2 + 1],
                        regex.matches(stringMultiples[i][j * 2 + 1])
                )
            }
        }

        // Test for the optimized '.+' quantifier node.
        assertFalse(Regex(".+abc").matches("abc"))
        assertFalse(Regex(".+\nabc", RegexOption.DOT_MATCHES_ALL).matches("\nabc"))
        assertFalse(Regex(".+").matches(""))
        assertFalse(Regex(".+\n", RegexOption.DOT_MATCHES_ALL).matches("\n"))
        assertFalse(Regex(".+abc").containsMatchIn("abc"))
        assertFalse(Regex(".+\nabc", RegexOption.DOT_MATCHES_ALL).containsMatchIn("\nabc"))
        assertFalse(Regex(".+").containsMatchIn(""))
        assertFalse(Regex(".+\n", RegexOption.DOT_MATCHES_ALL).containsMatchIn("\n"))

        assertTrue(Regex(".+abc").matches("aabc"))
        assertTrue(Regex(".+\nabc", RegexOption.DOT_MATCHES_ALL).matches("a\nabc"))
        assertTrue(Regex(".+").matches("a"))
        assertTrue(Regex(".+\n", RegexOption.DOT_MATCHES_ALL).matches("a\n"))
        assertTrue(Regex(".+abc").containsMatchIn("aabc"))
        assertTrue(Regex(".+\nabc", RegexOption.DOT_MATCHES_ALL).containsMatchIn("a\nabc"))
        assertTrue(Regex(".+").containsMatchIn("a"))
        assertTrue(Regex(".+\n", RegexOption.DOT_MATCHES_ALL).containsMatchIn("a\n"))
    }

    @Test fun testQuantVsGroup() {
        val patternString = "(d{1,3})((a|c)*)(d{1,3})((a|c)*)(d{1,3})"
        val testString = "dacaacaacaaddaaacaacaaddd"

        val regex = Regex(patternString)

        val result = regex.matchEntire(testString)!!
        assertEquals("dacaacaacaaddaaacaacaaddd", result.groupValues[0])
        assertEquals("d", result.groupValues[1])
        assertEquals("acaacaacaa", result.groupValues[2])
        assertEquals("dd", result.groupValues[4])
        assertEquals("aaacaacaa", result.groupValues[5])
        assertEquals("ddd", result.groupValues[7])
    }

    /*
 * Class under test for boolean find()
 */
    @Test fun testFind() {
        var testPattern = "(abb)"
        var testString = "cccabbabbabbabbabb"
        val regex = Regex(testPattern)
        var result = regex.find(testString)
        var start = 3
        var end = 6
        while (result != null) {
            assertEquals(start, result.groups[1]!!.range.start)
            assertEquals(end, result.groups[1]!!.range.endInclusive + 1)

            start = end
            end += 3
            result = result.next()
        }

        testPattern = "(\\d{1,3})"
        testString = "aaaa123456789045"

        val regex2 = Regex(testPattern)
        var result2 = regex2.find(testString)
        start = 4
        val length = 3
        while (result2 != null) {
            assertEquals(testString.substring(start, start + length), result2.groupValues[1])
            start += length
            result2 = result2.next()
        }
    }

    @Test fun testSEOLsymbols() {
        val regex = Regex("^a\\(bb\\[$")
        assertTrue(regex.matches("a(bb["))
    }

    @Test fun testGroupCount() {
        for (i in groupPatterns.indices) {
            val regex = Regex(groupPatterns[i])
            val result = regex.matchEntire("ababababbaaabb")!!
            assertEquals(i + 1, result.groups.size - 1)
        }
    }

    @Test fun testReluctantQuantifiers() {
        val regex = Regex("(ab*)*b")
        val result = regex.matchEntire("abbbb")
        if (result != null) {
            assertEquals("abbb", result.groupValues[1])
        } else {
            fail("Match expected: (ab*)*b vs abbbb")
        }
    }

    @Test fun testEnhancedFind() {
        val input = "foob"
        val pattern = "a*b"
        val regex = Regex(pattern)
        val result = regex.find(input)!!
        assertEquals("b", result.groupValues[0])
    }

    @Test fun testPosCompositeGroup() {
        val posExamples = arrayOf("aabbcc", "aacc", "bbaabbcc")
        val negExamples = arrayOf("aabb", "bb", "bbaabb")
        val posPat = Regex("(aa|bb){1,3}+cc")
        val negPat = Regex("(aa|bb){1,3}+bb")

        for (element in posExamples) {
            assertTrue(posPat.matches(element))
        }

        for (element in negExamples) {
            assertFalse(negPat.matches(element))
        }

        assertTrue(Regex("(aa|bb){1,3}+bb").matches("aabbaabb"))
    }

    @Test fun testPosAltGroup() {
        val posExamples = arrayOf("aacc", "bbcc", "cc")
        val negExamples = arrayOf("bb", "aa")
        val posPat = Regex("(aa|bb)?+cc")
        val negPat = Regex("(aa|bb)?+bb")

        for (element in posExamples) {
            assertTrue(posPat.toString() + " vs: " + element, posPat.matches(element))
        }

        for (element in negExamples) {
            assertFalse(negPat.matches(element))
        }

        assertTrue(Regex("(aa|bb)?+bb").matches("aabb"))
    }

    @Test fun testRelCompGroup() {
        var res = ""
        for (i in 0..3) {
            val regex = Regex("((aa|bb){$i,3}?).*cc")
            val result = regex.matchEntire("aaaaaacc")
            assertTrue(regex.toString() + " vs: " + "aaaaaacc", result != null)
            assertEquals(res, result!!.groupValues[1])
            res += "aa"
        }
    }

    @Test fun testRelAltGroup() {
        var regex = Regex("((aa|bb)??).*cc")
        var result = regex.matchEntire("aacc")
        assertTrue(regex.toString() + " vs: " + "aacc", result != null)
        assertEquals("", result!!.groupValues[1])

        regex = Regex("((aa|bb)??)cc")
        result = regex.matchEntire("aacc")
        assertTrue(regex.toString() + " vs: " + "aacc", result != null)
        assertEquals("aa", result!!.groupValues[1])
    }

    @Test fun testIgnoreCase() {
        var regex = Regex("(aa|bb)*", RegexOption.IGNORE_CASE)
        assertTrue(regex.matches("aAbb"))

        regex = Regex("(a|b|c|d|e)*", RegexOption.IGNORE_CASE)
        assertTrue(regex.matches("aAebbAEaEdebbedEccEdebbedEaedaebEbdCCdbBDcdcdADa"))

        regex = Regex("[a-e]*", RegexOption.IGNORE_CASE)
        assertTrue(regex.matches("aAebbAEaEdebbedEccEdebbedEaedaebEbdCCdbBDcdcdADa"))
    }

    @Test fun testQuoteReplacement() {
        assertEquals("\\\\aaCC\\$1", Regex.escapeReplacement("\\aaCC$1"))
    }

    @Test fun testOverFlow() {
        var regex = Regex("(a*)*")
        var result = regex.matchEntire("aaa")
        assertTrue(result != null)
        assertEquals("", result!!.groupValues[1])

        assertTrue(Regex("(1+)\\1+").matches("11"))
        assertTrue(Regex("(1+)(2*)\\2+").matches("11"))

        regex = Regex("(1+)\\1*")
        result = regex.matchEntire("11")

        assertTrue(result != null)
        assertEquals("11", result!!.groupValues[1])

        regex = Regex("((1+)|(2+))(\\2+)")
        result = regex.matchEntire("11")

        assertTrue(result != null)
        assertEquals("1", result!!.groupValues[2])
        assertEquals("1", result.groupValues[1])
        assertEquals("1", result.groupValues[4])
        assertEquals("", result.groupValues[3])
    }

    @Test fun testUnicode() {
        assertTrue(Regex("\\x61a").matches("aa"))
        assertTrue(Regex("\\u0061a").matches("aa"))
        assertTrue(Regex("\\0141a").matches("aa"))
        assertTrue(Regex("\\0777").matches("?7"))
    }

    @Test fun testUnicodeCategory() {
        assertTrue(Regex("\\p{Ll}").matches("k")) // Unicode lower case
        assertTrue(Regex("\\P{Ll}").matches("K")) // Unicode non-lower
        // case
        assertTrue(Regex("\\p{Lu}").matches("K")) // Unicode upper case
        assertTrue(Regex("\\P{Lu}").matches("k")) // Unicode non-upper
        // case combinations
        assertTrue(Regex("[\\p{L}&&[^\\p{Lu}]]").matches("k"))
        assertTrue(Regex("[\\p{L}&&[^\\p{Ll}]]").matches("K"))
        assertFalse(Regex("[\\p{L}&&[^\\p{Lu}]]").matches("K"))
        assertFalse(Regex("[\\p{L}&&[^\\p{Ll}]]").matches("k"))

        // category/character combinations
        assertFalse(Regex("[\\p{L}&&[^a-z]]").matches("k"))
        assertTrue(Regex("[\\p{L}&&[^a-z]]").matches("K"))

        assertTrue(Regex("[\\p{Lu}a-z]").matches("k"))
        assertTrue(Regex("[a-z\\p{Lu}]").matches("k"))

        assertFalse(Regex("[\\p{Lu}a-d]").matches("k"))
        assertTrue(Regex("[a-d\\p{Lu}]").matches("K"))

        assertFalse(Regex("[\\p{L}&&[^\\p{Lu}&&[^G]]]").matches("K"))

    }

    @Test fun testSplitEmpty() {

        val regex = Regex("")
        val s = regex.split("", 0)

        assertEquals(2, s.size)
        assertEquals("", s[0])
        assertEquals("", s[1])
    }

    @Test fun testFindDollar() {
        val regex = Regex("a$")
        val result = regex.find("a\n")
        assertTrue(result != null)
        assertEquals("a", result!!.groupValues[0])
    }

    /*
     * Regression test for HARMONY-674
     */
    @Test fun testPatternMatcher() {
        assertTrue(Regex("(?:\\d+)(?:pt)").matches("14pt"))
    }

    /**
     * Inspired by HARMONY-3360
     */
    @Test fun test3360() {
        val str = "!\"#%&'(),-./"
        val regex = Regex("\\s")
        assertFalse(regex.containsMatchIn(str))
    }

    /**
     * Regression test for HARMONY-3360
     */
    @Test fun testGeneralPunctuationCategory() {
        val s = arrayOf(",", "!", "\"", "#", "%", "&", "'", "(", ")", "-", ".", "/")
        val regexp = "\\p{P}"

        for (i in s.indices) {
            val regex = Regex(regexp)
            assertTrue(regex.containsMatchIn(s[i]))
        }
    }

    /**
     * Regression test for https://github.com/JetBrains/kotlin-native/issues/2297
     */
    @Test fun test2297() {
        fun testMatches(pattern: String, input: String) {
            assertTrue(Regex(pattern).matches(input), "\"$pattern\" should match \"$input\"")
        }

        testMatches("^(:[0-5]?[0-9])+$", input = ":20:30")
        testMatches("(.{1,}){2}", input = "aa")

        testMatches("(.+b)+", input = "0b0b")
        testMatches("(.+?b)+", input = "0b0b")
        testMatches("(.?b)+", input = "0b0b")
        testMatches("(.??b)+", input = "0b0b")
        testMatches("(.*b)+", input = "0b0b")
        testMatches("(.*?b)+", input = "0b0b")
        testMatches("(.{1,2}b)+", input = "0b00b")
        testMatches("(.{1,2}?b)+", input = "0b00b")

        testMatches("([0]?[0]?)+", input = "0000")
        testMatches("([0]?[0]?b)+", input = "00b00b")
        testMatches("((b{2}){3})+", input = "bbbbbbbbbbbb")

        testMatches("[^a]", input = "b")
    }

    @Test fun kt28158() {
        val comment = "😃😃😃😃😃😃"
        val regex = Regex("(.{3,})\\1+", RegexOption.IGNORE_CASE)
        assertTrue(comment.contains(regex))
    }
}