/*
 * @author Ranides Atterwim <ranides@gmail.com>
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.text;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Function;
import net.ranides.assira.ContractTesters;
import net.ranides.assira.collection.arrays.NativeArray;
import static net.ranides.assira.junit.NewAssert.*;
import net.ranides.assira.test.TAdapter;
import org.junit.Test;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public class StrBuilderTest {

    @Test
    public void testUsage() {
        int[] array = new int[]{0xA2,0xB2,0xC2,0xD2};
        List<Integer> list1 = Arrays.asList(0xA1,0xB1,0xC1,0xD1);
        List<Integer> list2 = Arrays.asList(10,11,12,13);
        List<Integer> list3 = Arrays.asList(3,4,5);
        
        StrBuilder sb = new StrBuilder();
        sb
            .options()
                .open("{ ").close(" }").separator(",")
            .builder()
                .open().append(list1, Integer::toHexString).close()
                .endl()
                .open().append(array.length, i -> Integer.toHexString(array[i])).close()
                .endl();
        sb
            .open("<", ">", ":").append(list2).close()
            .endl()
            .open("[", "]", " ")
                .item().append("Q")
                .item().append("P")
                .item().open().append(list3).close()
                .item().open("{","}").append(list3).close()
            .close()
            .endl();
                
//        System.out.printf("%s%n", sb.toString());
        
        String expected = String.format(
            "{ a1,b1,c1,d1 }%n" +
            "{ a2,b2,c2,d2 }%n" +
            "<10:11:12:13>%n" +
            "[Q P [3 4 5] {3 4 5}]%n"
        );
        assertEquals(expected, sb.toString());
    }
    
    @Test
    public void testOpenClose() {
        StrBuilder sb = new StrBuilder();
        
        assertThrows(IllegalStateException.class, sb::close);
        
        List<Integer> list = Arrays.asList(1,2,3);
        sb
            .options().open("{").close("}").separator("-").builder()
            .open().append(list).close().append(" ")
            .open("<", ">", "+").append(list).append(" ")
                .open("(", ")").append(list).append(" ")
                    .open("//").append(list).append(" ").close()
                .close()
            .close();
        
//        System.out.printf("%s%n", sb.toString());

        assertEquals("{1-2-3} <1+2+3 (1+2+3 //1+2+3 ))>", sb.toString());
        
        assertThrows(IllegalStateException.class, sb::close);
    }
    
    @Test
    public void testOptions() {
        StrBuilder sb = new StrBuilder();
        
        sb
            .options().blank("b1").endl("o1").separator("c1").builder()
            .open()
                .options().blank("b2").endl("o2").builder()
                .open()
                    .options().blank("b3").builder();
        
        assertEquals("{ blank='62 33', endl='6F 32', separator='63 31', open='', close='' }", sb.options().toString());
        sb.close();
        assertEquals("{ blank='62 32', endl='6F 32', separator='63 31', open='', close='' }", sb.options().toString());
        sb.close();
        assertEquals("{ blank='62 31', endl='6F 31', separator='63 31', open='', close='' }", sb.options().toString());
        
        assertThrows(IllegalStateException.class, sb::close);
        
        sb
            .options().blank("b1").endl("o1").separator("c1").builder()
            .open()
                .options().blank("b2").endl("o2").builder()
                .open()
                    .options().blank("b3").builder();
        sb.clear();
        assertEquals("{ blank='62 33', endl='6F 32', separator='63 31', open='', close='' }", sb.options().toString());
        
        assertThrows(IllegalStateException.class, sb::close);
    }
    
    @Test
    public void testAppend_String() {
        StrBuilder builder = new StrBuilder();
        builder.options().blank("-");
        
        // String
        builder.append("Hello ");
        builder.append((String)null );
        builder.append("..world..", 2, 7);
        builder.append((String)null, 1, 5);
        
        assertEquals("Hello -world-", builder.toString());
        
        // char
        builder.append('!');
        
        // CharSequence
        builder.append(cs(" Quick "));
        builder.append((CharSequence)null);
        builder.append(cs("..fox.."), 2, 5);
        builder.append((CharSequence)null, 1, 5);
        
        assertEquals("Hello -world-! Quick -fox-", builder.toString());
        
        // StringBuffer
        builder.append(new StringBuffer(" jumps "));
        builder.append((StringBuffer)null);
        builder.append(new StringBuffer("..over..."), 2, 6);
        builder.append((StringBuffer)null, 1, 5);
        
        assertEquals("Hello -world-! Quick -fox- jumps -over-", builder.toString());
        
        // StringBuilder
        builder.append(new StringBuilder(" lazy "));
        builder.append((StringBuilder)null);
        builder.append(new StringBuilder("..dog..."), 2, 5);
        builder.append((StringBuilder)null, 1, 5);
        
        assertEquals("Hello -world-! Quick -fox- jumps -over- lazy -dog-", builder.toString());
        
    }
    
    @Test
    public void testAppend_Chars() {
        StrBuilder builder = new StrBuilder();
        builder.options().blank("-");
        
        // char[]
        builder.append("Quick ".toCharArray());
        builder.append((char[])null);
        builder.append("..fox..".toCharArray(), 2, 5);
        builder.append((char[])null, 1, 5);
        
        assertEquals("Quick -fox-", builder.toString());
        
        // Object
        builder.append(os(" jumps over lazy dog"));
        builder.append((Object)null);
        assertEquals("Quick -fox- jumps over lazy dog-", builder.toString());
        
    }
    
    @Test
    public void testAppend_Primitive() {
        StrBuilder builder = new StrBuilder();

        // boolean
        builder
            .append(true).append(" ")
            .append(false).append(" ");
        assertEquals("true false ", builder.toString());
        
        // numbers
        builder
            .append(334).append(" ")
            .append(8000L).append(" ")
            .append(0.5f).append(" ")
            .append(0.25).append(" ");
            
        assertEquals("true false 334 8000 0.5 0.25 ", builder.toString());
    }
    
    @Test
    public void testAppend_Iterable() {
        StrBuilder builder = new StrBuilder();
        builder.options()
            .separator(",")
            .blank("-")
            .endl(" / ");
        
        // Iterable
        builder.append(Arrays.asList("a","b","c")).endl();
        builder.append(Arrays.asList("d")).endl();
        builder.append(Arrays.asList()).endl();
        
        assertEquals("a,b,c / d /  / ", builder.toString());
        
        // Iterator
        builder.append(Arrays.asList("A","B","C").iterator()).endl();
        builder.append(Arrays.asList("D").iterator()).endl();
        builder.append(Arrays.asList().iterator()).endl();
        
        assertEquals("a,b,c / d /  / A,B,C / D /  / ", builder.toString());
        
        // Iterable + Function
        builder.append(Arrays.asList(11,12,13), v -> String.valueOf(v%10)).endl();
        builder.append(Arrays.asList(14), v -> String.valueOf(v%10)).endl();
        builder.append(Arrays.<Integer>asList(), v -> String.valueOf(v%10)).endl();
        
        assertEquals("a,b,c / d /  / A,B,C / D /  / 1,2,3 / 4 /  / ", builder.toString());
        
        // Iterator + Function
        builder.append(Arrays.asList(11,12,13).iterator(), v -> String.valueOf(7 * (v%10))).endl();
        builder.append(Arrays.asList(14).iterator(), v -> String.valueOf(7 * (v%10))).endl();
        builder.append(Arrays.<Integer>asList().iterator(), v -> String.valueOf(7 * (v%10))).endl();
        
        assertEquals("a,b,c / d /  / A,B,C / D /  / 1,2,3 / 4 /  / 7,14,21 / 28 /  / ", builder.toString());
    }
    
    @Test
    public void testAppend_Array() {
        StrBuilder builder = new StrBuilder();
        builder.options()
            .separator(",")
            .blank("-")
            .endl(" / ");
        
        // array
        builder.append(new String[]{"a","b","c"}).endl();
        builder.append(new String[]{"d"}).endl();
        builder.append(new String[]{}).endl();
        
        assertEquals("a,b,c / d /  / ", builder.toString());
        
        // array + Function
        builder.append(new Integer[]{11,12,13}, v -> String.valueOf(v%10)).endl();
        builder.append(new Integer[]{14}, v -> String.valueOf(v%10)).endl();
        builder.append(new Integer[]{}, v -> String.valueOf(v%10)).endl();
        
        assertEquals("a,b,c / d /  / 1,2,3 / 4 /  / ", builder.toString());
        
        // NativeArray
        builder.append(NativeArray.wrap(new String[]{"A","B","C"})).endl();
        builder.append(NativeArray.wrap(new String[]{"D"})).endl();
        builder.append(NativeArray.wrap(new String[]{})).endl();
        
        assertEquals("a,b,c / d /  / 1,2,3 / 4 /  / A,B,C / D /  / ", builder.toString());
        
        // size + IntFunction
        builder.append(3, v -> String.valueOf(7*(1+v)) ).endl();
        builder.append(1, v -> String.valueOf(7*(1+v)) ).endl();
        builder.append(0, v -> String.valueOf(7*(1+v)) ).endl();
        
        assertEquals("a,b,c / d /  / 1,2,3 / 4 /  / A,B,C / D /  / 7,14,21 / 7 /  / ", builder.toString());

    }
    
    @Test
    public void testReserve() {
        StrBuilder sb = new StrBuilder(128);
        sb.append("Hello world");
        assertEquals(128, sb.capacity());
        assertEquals(11, sb.length());
        
        sb.reserve(64);
        assertEquals(128, sb.capacity());
        sb.reserve(160);
        assertEquals(256, sb.capacity());
        sb.reserve(513);
        assertEquals(513, sb.capacity());
    }
    
    @Test
    public void testTrim() {
        StrBuilder sb = new StrBuilder(128);
        sb.append("Hello world");
        assertEquals(128, sb.capacity());
        assertEquals(11, sb.length());
        
        sb.trim(64);
        assertEquals(64, sb.capacity());
        sb.trim();
        assertEquals(11, sb.capacity());
        
        sb.reserve(128);
        assertEquals(128, sb.capacity());
        sb.trim(8);
        assertEquals(11, sb.capacity());
    }
    
    @Test
    public void testResize() {
        StrBuilder sb = new StrBuilder(128);
        sb.append("Hello world");

        sb.resize(8, '.');
        assertEquals("Hello wo", sb.toString());
                
        sb.resize(12, '.');
        assertEquals("Hello wo....", sb.toString());
        
        assertThrows(IllegalArgumentException.class, ()->{
            sb.resize(-5, '.');
        });
        
        sb.resize(0, '.');
        assertEquals("", sb.toString());
    }
    
    @Test
    public void testClear() {
        StrBuilder sb = new StrBuilder(8);
        sb.append("Hello world");
        assertEquals(16, sb.capacity());
        assertEquals(11, sb.length());
        
        sb.clear();
        assertEquals(16, sb.capacity());
        assertEquals(0, sb.length());
        assertEquals("", sb.toString());
    }
    
    @Test
    public void testSequence() {
        StrBuilder sb = new StrBuilder().append("Hello world");
        
        StringTester.assertString("Hello world", sb);
        StringTester.assertString("ello wo", sb.subSequence(1, 8));
        
        sb.setCharAt(0, 'h');
        sb.setCharAt(1, 'a');
        StringTester.assertString("hallo world", sb);
        
        assertThrows(IndexOutOfBoundsException.class, ()->{
            sb.setCharAt(-1, 'z');
        });
        assertThrows(IndexOutOfBoundsException.class, ()->{
            sb.setCharAt(53, 'z');
        });
    }
    
    @Test
    public void testGetChars() {
        StrBuilder sb = new StrBuilder(32).append("Hello world");
        
        assertArrayEquals("Hello world".toCharArray(), sb.getChars());
        
        assertArrayEquals("llo wo".toCharArray(), sb.getChars(2,8));
        assertArrayEquals("".toCharArray(), sb.getChars(2,2));
        assertThrows(IndexOutOfBoundsException.class, ()->{
            sb.getChars(2,13);
        });
        assertThrows(IndexOutOfBoundsException.class, ()->{
            sb.getChars(13,13);
        });
        assertThrows(IndexOutOfBoundsException.class, ()->{
            sb.getChars(-1,3);
        });
        
        char[] a = "###########...".toCharArray();
        sb.getChars(a);
        assertArrayEquals("Hello world...".toCharArray(), a);
        
        char[] b = "###########...".toCharArray();
        sb.getChars(2,8,b,3);
        assertArrayEquals("###llo wo##...".toCharArray(), b);
    }
    
    @Test
    public void testFragmet() {
        StrBuilder sb = new StrBuilder(32).append("Hello world");
        
        assertEquals("", sb.getLeftFragment(0));
        assertEquals("Hell", sb.getLeftFragment(4));
        assertEquals("Hello world", sb.getLeftFragment(60));
        
        assertEquals("", sb.getRightFragment(0));
        assertEquals("orld", sb.getRightFragment(4));
        assertEquals("Hello world", sb.getRightFragment(60));
    }
    
    @Test
    public void testReverse() {
        StrBuilder sb = new StrBuilder(32).append("Hello world");
        
        sb.reverse();
        assertEquals("dlrow olleH", sb.toString());
        sb.append("!");
        assertEquals("dlrow olleH!", sb.toString());
        sb.reverse();
        sb.append("?");
        assertEquals("!Hello world?", sb.toString());
    }
    
    @Test
    public void testToBuilder() {
        StrBuilder sb = new StrBuilder(32).append("Hello world");
        assertEquals("Hello world!", sb.toBuilder().append("!").toString());
        assertEquals("Hello world", sb.toString());
    }
    
    @Test
    @SuppressFBWarnings("EC_UNRELATED_TYPES")
    public void testEquals() {
        StrBuilder a = new StrBuilder(16).append("Hello");
        StrBuilder b = new StrBuilder(160).append("He").append("llo");
        StrBuilder c = new StrBuilder(160).append("He").append("ll!");
        
        assertTrue(a.equals(a));
        
        assertFalse(a.equals(null));
        assertFalse(a.equals(new Object()));
        
        assertFalse(a.equals(new StrBuilder(160).append("Hallo")));
        assertFalse(a.equals(new StrBuilder(160).append("Hello!")));
        
        assertFalse(a.equals("Hello!"));
        assertFalse(a.equals("Hella"));
        
        assertTrue(a.equals("Hello"));

        assertTrue(a.equals(b));
        assertEquality(a, b, c);
    }
    
    @Test
    public void testWriter() throws IOException {
        StrBuilder sb = new StrBuilder();
        Writer writer = sb.asWriter();
        sb.append("Hello ");
        
        // #close should be NOP
        writer.close(); 
        
        writer.write('w');
        writer.write("orld. ".toCharArray());
        writer.write("##tex..".toCharArray(), 2, 5);
        writer.write("t :)");
        writer.write("##EOF..", 2, 5);
        
        // we don't have to use #flush
        
        assertEquals("Hello world. text :)EOF", sb.toString());
    }
    
    @Test
    public void testReader() throws IOException {
        TAdapter<Reader, StrBuilder> map = new TAdapter<>();
        
        Function<String, Reader> rf = map.map(
            (String text) -> new StrBuilder().append(text), builder -> builder.asReader()
        );
        BiConsumer<Reader, String> ra = (reader, text) -> {
            map.get(reader).append(text);
        };
        ContractTesters.runner()
            .param("reader.appender!", ra)
            .function("", rf)
			.run();
        assertTrue(true);
    }
    
    @Test
    public void testHashcode() {
        assertEquals(Arrays.hashCode("Hello".toCharArray()), new StrBuilder(32).append("Hello").hashCode());
    }

    
    private static CharSequence cs(String value) {
        return StringUtils.wrap(value.toCharArray());
    }
    
    private static Object os(String value) {
        return new Object(){
            @Override
            public String toString() {
                return value;
            }
        };
    }
    
}
