/*
 *  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 groovy

import org.junit.jupiter.api.Test

import static groovy.test.GroovyAssert.fail
import static org.junit.jupiter.api.Assertions.assertFalse
import static org.junit.jupiter.api.Assertions.assertTrue


/**
 * Various tests for Strings.
 */
class StringTest {

    @Test
    void testString() {
        def s = "abcd"
        assert s.length() == 4
        assert 4 == s.length()

        // test polymorphic size() method like collections
        assert s.size() == 4

        s = s + "efg" + "hijk"

        assert s.size() == 11
        assert "abcdef".size() == 6
    }

    @Test
    void testStringPlusNull() {
        def y = null
        def x = "hello " + y
        assert x == "hello null"
    }

    @Test
    void testNextPrevious() {
        def x = 'a'
        def y = x.next()
        assert y == 'b'

        def z = 'z'.previous()
        assert z == 'y'

        z = 'z'
        def b = z.next()
        assert b != 'z'

        assert b > z
        assert z.charAt(0) == 'z'
        assert b.charAt(0) == '{'
    }

    @Test
    void testApppendToString() {
        def name = "Gromit"
        def result = "hello " << name << "!"

        assert result.toString() == "hello Gromit!"
    }

    @Test
    void testApppendToStringBuffer() {
        def buffer = new StringBuffer()

        def name = "Gromit"
        buffer << "hello " << name << "!"

        assert buffer.toString() == "hello Gromit!"
    }

    @Test
    void testApppendAndSubscipt() {
        def result =  'hello' << " Gromit!"
        result[1..4] = 'i'
        assert result.toString() == "hi Gromit!"
    }

    @Test
    void testSimpleStringLiterals() {
        assertLength("\n", 1)
        assertLength("\"", 1)
        assertLength("\'", 1)
        assertLength("\\", 1)
        assertContains("\${0}", 4, "{0}")
        assertContains("x\
y", 2, "xy")

        assertLength('\n', 1)
        assertLength('\'', 1)
        assertLength('\\', 1)
        assertContains('${0}', 4, '{0}')
        assertContains('x\
y', 2, 'xy')
    }

    @Test
    void testMinusRemovesFirstOccurenceOfString() {
        assert "abcdeabcd" - 'bc' == 'adeabcd'
    }

    @Test
    void testMinusEscapesRegexChars() {
        assert "abcdeab.d.f" - '.d.' == 'abcdeabf'
    }

    @Test
    void testMultilineStringLiterals() {
        assertContains(""""x""", 2, '"x');
        assertContains("""""x""", 3, '""x');
        assertContains("""x
y""", 3, 'x\ny');
        assertContains("""\n
\n""", 3, '\n\n\n');

        assertContains(''''x''', 2, "'x");
        assertContains('''''x''', 3, "''x");
        assertContains('''x
y''', 3, 'x\ny');
        assertContains('''\n
\n''', 3, '\n\n\n');

    }

    @Test
    void testRegexpStringLiterals() {
        assert "foo" == /foo/
        assert '\\$$' == /\$$/
        // Backslash before newline or slash disappears (all others are preserved):
        assert "/\\*" == /\/\*/
        def path = /C:\Windows\System32\//.replaceAll(/\\${''}/, '/')
        assert path == 'C:/Windows/System32/'
        assert "" == /\
/
    }

    @Test
    void testMultilineRegexpStringContainingNormalRegexp() {
        def script = /
        'foo' ==~ \/f.o\/
        /
        assert new GroovyShell().evaluate(script)
    }

    @Test
    void testMultilineRegexpXml() {
        def xml = /
<xml>
foo
<\/xml>
/
        assert "\n<xml>\nfoo\n</xml>\n" == xml
    }

    @Test
    void testMultilineSlashyRegexpEscaping() {
        def str = 'groovy.codehaus.org and www.aboutgroovy.com'
        def re = /(?x)  # to enable whitespace and comments
        (                 # capture the hostname in $1
          (?:             # these parens for grouping only
            (?! [-_])     # lookahead for neither underscore nor dash
            [\w-]+        # hostname component
            \.            # and the domain dot
          )+              # now repeat that whole thing a bunch of times
          [A-Za-z]        # next must be a letter
          [\w-]+          # now trailing domain part
        )                 # end of $1 capture
        /
        def finder = (str =~ re)
        def out = str
        (0..<finder.count).each{
            def adr = finder[it][0]
            out = out.replaceAll(adr, "$adr[${adr.size()}]")
        }
        assert out == 'groovy.codehaus.org[19] and www.aboutgroovy.com[19]'
    }

    @Test
    void testDollarSlashyFirstCharEscaping() {
        def VAR = 'foo'
        def result = $/$/VAR/$
        assert result == '/VAR'
        result = $/$$VAR/$
        assert result == '$VAR'
        result = $/$VAR/$
        assert result == 'foo'
    }

    @Test
    void testMultilineDollarSlashyRegexpEscaping() {
        def str = 'groovy.codehaus.org and www.aboutgroovy.com'
        def re = $/(?x)  # to enable whitespace and comments
        (                 # capture the hostname in $1
          (?:             # these parens for grouping only
            (?! [-_])     # lookahead for neither underscore nor dash
            [\w-]+        # hostname component
            \.            # and the domain dot
          )+              # now repeat that whole thing a bunch of times
          [A-Za-z]        # next must be a letter
          [\w-]+          # now trailing domain part
        )                 # end of $1 capture
        /$
        def finder = (str =~ re)
        def out = str
        (0..<finder.count).each{
            def adr = finder[it][0]
            out = out.replaceAll(adr, "$adr[${adr.size()}]")
        }
        assert out == 'groovy.codehaus.org[19] and www.aboutgroovy.com[19]'
    }

    @Test
    void testBoolCoerce() {
        // Explicit coercion
        assertFalse((Boolean) "")
        assertTrue((Boolean) "content")

        // Implicit coercion in statements
        String s = null
        if (s) {
            fail("null should have evaluated to false, but didn't")
        }
        s = ''
        if (s) {
            fail("'' should have evaluated to false, but didn't")
        }
        s = 'something'
        if (!s) {
            fail("'something' should have evaluated to false, but didn't")
        }

    }

    @Test
    void testDollarEscaping() {
        def text = $/a/b\c$$ $//$
        assert text == 'a/b\\c$ /'

        //GROOVY-8171
        text = $/$$//$
        assert text == '$/'

        text = $/$$$$//$
        assert text == '$$/'

        text = $/$$$$$//$
        assert text == '$$/'

        //GROOVY-10024
        def s1 = $/abc\\/$
        def s2 = $/def\/$
        def s3 = $/ghi/$
        assert s1 == 'abc\\\\' && s2 == 'def\\' && s3 == 'ghi'
    }

    @Test
    void testSplit() {
        def text = "hello there\nhow are you"
        def splitted = text.split().toList()
        assert splitted == ['hello', 'there', 'how', 'are', 'you']
    }

    @Test
    void testSplitEmptyText() {
        def text = ""
        def splitted = text.split().toList()
        assert splitted == []
    }

    @Test
    void testReadLines() {
        assert "a\nb".readLines() == ['a', 'b']
        assert "a\rb".readLines() == ['a', 'b']
        assert "a\r\nb".readLines() == ['a', 'b']
        assert "a\n\nb".readLines() == ['a', '', 'b']
    }

    @Test
    void testReplace() {
        assert "".replace("", "") == ""
        assert "".replace("", "r") == "r"
        assert "a".replace("", "r") == "rar"
        assert "a".replace("b", "c") == "a"
        assert "a".replace("a", "c") == "c"
        assert "aa".replace("a", "c") == "cc"
        assert "ab".replace("b", "c") == "ac"
        assert "ba".replace("b", "c") == "ca"
        assert "aaa".replace("b", "c") == "aaa"
        assert "aaa".replace("a", "c") == "ccc"
        assert "aba".replace("b", "c") == "aca"
        assert "baa".replace("b", "c") == "caa"
        assert "aab".replace("b", "c") == "aac"
        assert "aa.".replace(".", "c") == "aac"
        assert 'aba'.replace('b', '$') == 'a$a'
        assert 'aba'.replace('b', '\\') == 'a\\a'
        assert 'a\\a'.replace('\\', 'x') == 'axa'
        assert '\\'.replace('\\', 'x') == 'x'
        assert '\\\\'.replace('\\', 'x') == 'xx'
        assert '\\z\\'.replace('\\', 'x') == 'xzx'
        assert 'a\\\\Ea'.replace('\\', 'x') == 'axxEa'
        assert '\\Qa\\\\Ea'.replace('\\', '$') == '$Qa$$Ea'
        assert 'a\\((z))\\Qa'.replace('\\', 'x') == 'ax((z))xQa'
        assert (/\Q\E\\\Q\E/).replace(/\Q\E\\\Q\E/, 'z') == 'z'
    }

    @Test
    void testNormalize() {
        assert "a".normalize() == "a"
        assert "\n".normalize() == "\n"
        assert "\r".normalize() == "\n"
        assert "\r\n".normalize() == "\n"
        assert "a\n".normalize() == "a\n"
        assert "a\r".normalize() == "a\n"
        assert "a\r\n".normalize() == "a\n"
        assert "a\r\n\r".normalize() == "a\n\n"
        assert "a\r\n\r\n".normalize() == "a\n\n"
        assert "a\nb\rc\r\nd".normalize() == "a\nb\nc\nd"
        assert "a\n\nb".normalize() == "a\n\nb"
        assert "a\n\r\nb".normalize() == "a\n\nb"
    }

    @Test
    void testDenormalize() {
        def LS = System.getProperty('line.separator')
        assert "\n".denormalize() == LS
        assert "\r".denormalize() == LS
        assert "\r\n".denormalize() == LS
        assert "a\n".denormalize() == "a${LS}"
        assert "a\r".denormalize() == "a${LS}"
        assert "a\r\n".denormalize() == "a${LS}"
        assert "a\r\n\r".denormalize() == "a${LS}${LS}"
        assert "a\r\n\r\n".denormalize() == "a${LS}${LS}"
        assert "a\nb\rc\r\nd".denormalize() == "a${LS}b${LS}c${LS}d"
        assert "a\n\nb".denormalize() == "a${LS}${LS}b"
        assert "a\n\r\nb".denormalize() == "a${LS}${LS}b"
        assert 'a\nb\r\nc\n\rd'.denormalize() == "a${LS}b${LS}c${LS}${LS}d"
    }

    void innerNormalizationFileRoundTrip(String s) {
        def f = File.createTempFile("groovy.StringTest", ".txt")
        f.deleteOnExit()

        def sd = s.denormalize()
        f.write(sd)
        assert sd == f.text

        f.write(s);
        assert s == f.text

        def rt = (s.denormalize()).normalize()
        assert s.normalize() == rt

        if (!s.contains('\r')) assert s == rt
    }

    void doNormalizationFileRoundTrip(String s) {
        [s, s.replace('\n', '\r'), s.replace('\n', '\r\n'), s.replace('\n', '\n\n')].each {
            innerNormalizationFileRoundTrip(it)
            innerNormalizationFileRoundTrip(it.reverse())
        }
    }

    @Test
    void testNormalizationFileRoundTrip() {
        doNormalizationFileRoundTrip("a line 1\nline 2")
        doNormalizationFileRoundTrip("a line 1\nline 2\n")
        doNormalizationFileRoundTrip("")
        doNormalizationFileRoundTrip("\n")
        doNormalizationFileRoundTrip("a")
        doNormalizationFileRoundTrip("abcdef")
        doNormalizationFileRoundTrip("a\n")
        doNormalizationFileRoundTrip("abcdef\n")
    }

    @Test
    void testSplitEqualsTokenize() {
        def text = """
        A text with different words and
        numbers like 3453 and 3,345,454.97 and
        special characters %&)( and also
        everything mixed together 45!kw?
      """

        def splitted = text.split().toList()
        def tokenized = text.tokenize()
        assert splitted == tokenized
    }

    private assertLength(s, len) {
        if (s.length() != len)  println "*** length != $len: $s"
        assert s.length() == len
    }

    private assertContains(s, len, subs) {
        assertLength(s, len)
        if (s.indexOf(subs) < 0)  println "*** missing $subs: $s"
        assert s.indexOf(subs) >= 0
    }

    @Test
    void testExpandUnexpand() {
        assert '\t\tabc\tdef\n12345\t67\t '.expand().unexpand() == '\t\tabc\tdef\n12345\t67\t '
        assert '1234567\t8\t '.expand() == '1234567 8        '
        assert '    x    '.unexpand() == '    x\t '
        assert '    x    \n'.unexpand() == '    x\t \n'
    }

    void "test JDK14 Escape-S should be replaced by space"() {
        assert 'ab\scd\s'.size() == 6
        assert 'ab\scd\s'.bytes  == [97, 98, 32, 99, 100, 32]
        assert "ab\scd\s".size() == 6
        assert "ab\scd\s".bytes  == [97, 98, 32, 99, 100, 32]
    }

    void "test JDK14 Escape-S should not impact slashy or dollar-slashy strings"() {
        assert /ab\scd\s/.size() == 8
        assert /ab\scd\s/.bytes  == [97, 98, 92, 115, 99, 100, 92, 115]
        assert $/ab\scd\s/$.size() == 8
        assert $/ab\scd\s/$.bytes == [97, 98, 92, 115, 99, 100, 92, 115]
    }

    void "test JDK14 Escape-S multi-line example"() {
        // control case (existing functionality)
        String colors = '''\
            red   \n\
            green \n\
            blue  \n\
        '''.stripIndent(true)
        assert colors.readLines()*.size() == [6, 6, 6]

        colors = '''\
            red  \s
            green\s
            blue \s
        '''.stripIndent(true)
        assert colors.readLines()*.size() == [6, 6, 6]
    }
}
