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

/**
 * Tests for DGM methods on CharSequence.
 */
class GroovyCharSequenceMethodsTest extends GroovyTestCase {

    private static CharSequence makeCharSequence(String s) {
        [
                toString   : { -> s },
                subSequence: { int f, int t -> s.substring(f, t) },
                length     : { -> s.length() },
                charAt     : { int i -> s.chars[i] },
        ] as CharSequence
    }

    def cs1 = makeCharSequence('Today is Thu Jul 28 06:38:07 EST 2011')

    def cs2 = makeCharSequence('Foobar')

    def cs3 = makeCharSequence('''\
                |Foo
                |bar
                |''')

    def csEmpty = makeCharSequence('')

    void testIsCase() {
        // direct
        assert cs2.isCase('Foobar')
        assert !cs2.isCase('Baz')
        // typical usage
        switch(cs2) {
            case 'Foobar': break
            default: assert false, 'Should not get here'
        }
    }

    void testSize() {
        assert cs1.size() == 37
    }

    void testGetAtRange() {
        assert cs2.getAt(1..3) == 'oob'
        assert cs2[1..3] == 'oob'
        assert cs2.getAt(3..1) == 'boo'
        assert cs2.getAt(-3..-1) == 'bar'
        assert cs2[-3..-1] == 'bar'
    }

    void testGetAtCollection() {
        assert cs2.getAt([0, 4, 5]) == 'Far'
        assert cs2[1, 3, 5] == 'obr'
        assert cs2[3, 5, 2] == 'bro'
    }

    void testGetAtEmptyRange() {
        assert cs2.getAt(new EmptyRange(null)) == ''
    }

    void testReverse() {
        assert cs2.reverse() == 'rabooF'
    }

    void testStripMargin() {
        assert cs3.stripMargin() == 'Foo\nbar\n'
    }

    void testStripIndent() {
        assert cs3.stripIndent() == '|Foo\n|bar\n|'
    }

    void testIsAllWhitespace() {
        assert !cs2.isAllWhitespace()
        assert makeCharSequence(' \t\n\r').isAllWhitespace()
    }

    void testReplace() {
        assert cs2.replaceFirst(~/[ab]/, csEmpty) == 'Fooar'
        assert cs2.replaceAll(~/[ab]/, csEmpty) == 'Foor'
    }

    void testMatches() {
        assert cs2.matches(~/.oo.*/)
        assert !cs2.matches(~/.*z.*/)
    }

    void testFind() {
        def csDigits = makeCharSequence(/\d{4}/)
        assert cs1.find(csDigits) == '2011'
        assert cs1.find(csDigits, {"--$it--"}) == '--2011--'
        assert cs1.find(~/\d\d:\d\d/) == '06:38'
        assert cs1.find(~/\d\d:\d\d/, {"|$it|"}) == '|06:38|'
    }

    void testFindAll() {
        def csDigits = makeCharSequence(/\d\d/)
        assert cs1.findAll(csDigits) == ['28', '06', '38', '07', '20', '11']
        assert cs1.findAll(csDigits, {"<$it>"}) == ['<28>', '<06>', '<38>', '<07>', '<20>', '<11>']
        assert cs1.findAll(~/\s\d\d/) == [' 28', ' 06', ' 20']
        assert cs1.findAll(~/\s\d\d/, {"<$it >"}) == ['< 28 >', '< 06 >', '< 20 >']
    }

    void testPad() {
        assert cs2.padLeft(10) == '    Foobar'
        assert cs2.padLeft(10, '+') == '++++Foobar'
        assert cs2.padRight(10) == 'Foobar    '
        assert cs2.padRight(10, '+') == 'Foobar++++'
        assert cs2.center(10) == '  Foobar  '
        assert cs2.center(10, '+') == '++Foobar++'
    }

    void testDrop() {
        assert cs2.drop(3) == 'bar'
    }

    void testDropTakeTC() {
        assertScript '''
            @groovy.transform.TypeChecked
            def method() {
                assert 'Foo Bar'.drop(4).toLowerCase() == 'bar'
                assert 'Foo Bar'.take(3).toLowerCase() == 'foo'
            }

            method()
        '''
    }

    void testAsBoolean() {
        assert cs1 && cs2
        assert !csEmpty
    }

    void testLeftShift() {
        assert cs2.leftShift('___').toString() == 'Foobar___'
        assert (cs2 << 'baz').toString() == 'Foobarbaz'
    }

    void testSplit() {
        def parts = cs1.split()
        assert parts instanceof CharSequence[]
        assert parts.size() == 8
        assert parts[2] == 'Thu'
    }

    void testTokenize() {
        def parts = cs1.tokenize()
        assert parts instanceof List
        assert parts.size() == 8
        assert parts[2] == 'Thu'
        parts = cs1.tokenize(':')
        assert parts.size() == 3
        assert parts[1] == '38'
    }

    void testCapitalize() {
        def csfoo = makeCharSequence('foo')
        assert csfoo.capitalize() == 'Foo'
        assert cs2.capitalize() == 'Foobar'
    }

    void testUncapitalize() {
        def csfoo = makeCharSequence('Foo')
        assert csfoo.uncapitalize() == 'foo'
        assert cs2.uncapitalize() == 'foobar'
    }

    void testExpand() {
        def csfoobar = makeCharSequence('foo\tbar')
        assert csfoobar.expand() == 'foo     bar'
        assert csfoobar.expand(4) == 'foo bar'
        csfoobar = makeCharSequence('\tfoo\n\tbar')
        assert csfoobar.expand(4) == '    foo\n    bar'
    }

    void testUnexpand() {
        def csfoobar = makeCharSequence('foo     bar')
        assert csfoobar.unexpand() == 'foo\tbar'
        assert csfoobar.unexpand(4) == 'foo\t\tbar'
        csfoobar = makeCharSequence('     foo\n    bar')
        assert csfoobar.unexpand(4) == '\t foo\n\tbar'
    }

    void testPlus() {
        assert cs2.plus(42) == 'Foobar42'
        assert cs2 + 42 == 'Foobar42'
    }

    void testMinus() {
        def csoo = makeCharSequence('oo')
        assert cs2.minus(42) == 'Foobar'
        assert cs2.minus(csoo) == 'Fbar'
        assert cs2 - csoo == 'Fbar'
    }

    void testContains() {
        def csoo = makeCharSequence('oo')
        def csbaz = makeCharSequence('baz')
        assert cs2.contains(csoo)
        assert !cs2.contains(csbaz)
    }

    void testCount() {
        def cszero = makeCharSequence('0')
        def csbar = makeCharSequence('|')
        assert cs1.count(cszero) == 3
        assert cs3.count(csbar) == 3
    }

    void testNext() {
        assert cs2.next() == 'Foobas'
        assert ++cs2 == 'Foobas'
    }

    void testPrevious() {
        assert cs2.previous() == 'Foobaq'
        assert --cs2 == 'Foobaq'
    }

    void testMultiply() {
        assert cs2.multiply(2) == 'FoobarFoobar'
        assert cs2 * 3 == 'FoobarFoobarFoobar'
    }

    void testToInteger() {
        def csFourteen = makeCharSequence('014')
        assert csFourteen.isInteger()
        def fourteen = csFourteen.toInteger()
        assert fourteen instanceof Integer
        assert fourteen == 14
    }

    void testToLong() {
        def csFourteen = makeCharSequence('014')
        assert csFourteen.isLong()
        def fourteen = csFourteen.toLong()
        assert fourteen instanceof Long
        assert fourteen == 14L
    }

    void testToShort() {
        def csFourteen = makeCharSequence('014')
        def fourteen = csFourteen.toShort()
        assert fourteen instanceof Short
        assert fourteen == 14
    }

    void testToBigInteger() {
        def csFourteen = makeCharSequence('014')
        assert csFourteen.isBigInteger()
        def fourteen = csFourteen.toBigInteger()
        assert fourteen instanceof BigInteger
        assert fourteen == 14G
    }

    void testToFloat() {
        def csThreePointFive = makeCharSequence('3.5')
        assert csThreePointFive.isFloat()
        def threePointFive = csThreePointFive.toFloat()
        assert threePointFive instanceof Float
        assert threePointFive == 3.5
    }

    void testToDouble() {
        def csThreePointFive = makeCharSequence('3.5')
        assert csThreePointFive.isDouble()
        def threePointFive = csThreePointFive.toDouble()
        assert threePointFive instanceof Double
        assert threePointFive == 3.5D
    }

    void testToBigDecimal() {
        def csThreePointFive = makeCharSequence('3.5')
        assert csThreePointFive.isBigDecimal()
        assert csThreePointFive.isNumber()
        def threePointFive = csThreePointFive.toBigDecimal()
        assert threePointFive instanceof BigDecimal
        assert threePointFive == 3.5G
    }

    void testEachLine() {
        def result = []
        cs3.eachLine{ line, num -> result << "$num:${line.size()}" }
        assert result == ["0:20", "1:20", "2:17"]
        result = []
        cs3.eachLine(10){ line, num -> result << "$num:${line.size()}" }
        assert result == ["10:20", "11:20", "12:17"]
    }

    void testSplitEachLine() {
        def regexOp = /\s*\*\s*/
        def csOp = makeCharSequence(regexOp)
        def csTwoLines = makeCharSequence('10*15\n11 * 9')
        def result = []
        csTwoLines.splitEachLine(csOp){ left, right -> result << left.toInteger() * right.toInteger() }
        assert result == [150, 99]
        result = []
        csTwoLines.splitEachLine(~regexOp){ left, right -> result << left.toInteger() * right.toInteger() }
        assert result == [150, 99]
    }

    void testReadLines() {
        def lines = cs3.readLines()
        assert lines.size() == 3
        assert lines[1].trim() == '|bar'
    }

    void testToList() {
        def chars = cs2.toList()
        assert chars.size() == 6
        assert chars[0] == 'F'
        assert chars[3] == 'b'
        assert chars[-1] == 'r'
    }

    void testToSet() {
        def chars = cs2.toSet()
        assert chars.size() == 5
        assert 'F' in chars
        assert 'b' in chars
    }

    void testGetChars() {
        def chars = cs2.chars
        assert chars instanceof char[]
        assert chars.size() == 6
        assert chars[0] == 'F'
        assert chars[3] == 'b'
        assert chars[-1] == 'r'
    }

    private enum Coin { penny, nickel, dime, quarter }
    void testAsType() {
        def csDime = makeCharSequence('dime')
        def dime = csDime as Coin
        assert dime instanceof Coin
        assert dime == Coin.dime
    }

    void testEachMatch() {
        def result = []
        def regexDigits = /(\d)(.)(\d)/
        def csDigits = makeCharSequence(regexDigits)
        assert cs1.eachMatch(csDigits) { all, first, delim, second -> result << "$first $delim $second" }
        assert result == ['8   0', '6 : 3', '8 : 0', '2 0 1']
        result = []
        assert cs1.eachMatch(~regexDigits) { all, first, delim, second -> result << "$first $delim $second" }
        assert result == ['8   0', '6 : 3', '8 : 0', '2 0 1']
    }

    void testTr() {
        assert cs1.tr(':uoa', '/___') == 'T_d_y is Th_ J_l 28 06/38/07 EST 2011'
    }

    void testReplaceAllFirst() {
        def csDigit = makeCharSequence(/\d/)
        def csUnder = makeCharSequence(/_/)

        assert cs1.replaceAll(~/\d/, csUnder) == 'Today is Thu Jul __ __:__:__ EST ____'
        assert cs1.replaceAll(csDigit, csUnder) == 'Today is Thu Jul __ __:__:__ EST ____'
        assert cs1.replaceAll(csDigit, { '_' }) == 'Today is Thu Jul __ __:__:__ EST ____'
        assert cs1.replaceFirst(~/\d/, csUnder) == 'Today is Thu Jul _8 06:38:07 EST 2011'
        assert cs1.replaceFirst(csDigit, csUnder) == 'Today is Thu Jul _8 06:38:07 EST 2011'
        assert cs1.replaceFirst(csDigit, { '_' }) == 'Today is Thu Jul _8 06:38:07 EST 2011'
    }

    void testNormalizeDenormalize() {
        def text = 'the quick brown\nfox jumped\r\nover the lazy dog'
        def csText = makeCharSequence(text)
        assert csText.normalize() == text.normalize()
        assert csText.normalize().denormalize() == text.normalize().denormalize()
    }

}
