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

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.ByteChannel;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import net.ranides.assira.ContractTesters;
import net.ranides.assira.collection.arrays.ArrayUtils;
import org.junit.Test;
import static net.ranides.assira.junit.NewAssert.*;
import net.ranides.assira.test.TAdapter;
import net.ranides.assira.text.Charsets;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public class BufferAdapterTest {
    
    @Test
    public void testAsWriter_CharBuffer() throws IOException {
        TAdapter<Writer, CharBuffer> map = new TAdapter<>();
        
        Function<Writer, String> mr = (ostream) -> {
            CharBuffer b = map.get(ostream);
            return new String(b.array(), 0, b.position());
        };
        
        ContractTesters.runner()
            .param("writer.chars!", mr)
            .supplier(map.map(() -> CharBuffer.allocate(1024), BufferAdapter::asWriter))
			.run();
        assertTrue(true);
    }
    
    @Test
    public void testAsWriter_ByteBuffer() throws IOException {
        TAdapter<Writer, ByteBuffer> map = new TAdapter<>();
        
        Function<Writer, String> mr = (ostream) -> {
            ByteBuffer b = map.get(ostream);
            return new String(b.array(), 0, b.position(), Charsets.UTF16LE);
        };
        
        ContractTesters.runner()
            .param("writer.chars!", mr)
            .supplier(map.map(
                () -> ByteBuffer.allocate(1024), 
                c -> BufferAdapter.asWriter(c, Charsets.UTF16LE)
            ))
			.run();
        assertTrue(true);
    }
    
    @Test
    public void testAsWriter_RO() throws IOException {
        Writer writer = BufferAdapter.asWriter(CharBuffer.allocate(1024).asReadOnlyBuffer());
        
        // undefined behaviour: IOException is allowed but not required
//        writer.append("Hello", 1, 1);
//        writer.write("Hello", 1, 0);
//        writer.write("Hello".toCharArray(), 1, 0);
//        writer.write("");
        
        assertThrows(IOException.class, ()->{
            writer.append("A");
        });
        assertThrows(IOException.class, ()->{
            writer.append("Hello", 1,2);
        });
        assertThrows(IOException.class, ()->{
            writer.append("Hello", 1,2);
        });
        assertThrows(IOException.class, ()->{
            writer.append('c');
        });
        
        assertThrows(IOException.class, ()->{
            writer.write("Hello", 1, 2);
        });
        assertThrows(IOException.class, ()->{
            writer.write("Hello".toCharArray(), 1, 2);
        });
        assertThrows(IOException.class, ()->{
            writer.write("Hello");
        });
        
    }
    
    @Test
    public void testOStream() {
        TAdapter<OutputStream, ByteBuffer> map = new TAdapter<>();
        
        Function<OutputStream, byte[]> mr = (ostream) -> {
            ByteBuffer bos = map.get(ostream);
            return ArrayUtils.slice(bos.array(), 0, bos.position());
        };
       
        ContractTesters.runner()
            .param("ostream.bytes!", mr)
            .supplier(map.map(() -> ByteBuffer.allocate(1024), BufferAdapter::asOutputStream))
			.run();
        assertTrue(true);

    }
    
    @Test
    public void testOStream_RO() throws IOException {
        OutputStream ostream = BufferAdapter.asOutputStream(ByteBuffer.allocate(1024).asReadOnlyBuffer());

        // undefined behaviour: IOException is allowed but not required
//        ostream.write(new byte[0]);
//        ostream.write(new byte[5], 1, 0);
        
        assertThrows(IOException.class, ()->{
            ostream.write('a');
        });
        assertThrows(IOException.class, ()->{
            ostream.write("abc".getBytes(Charsets.US_ASCII));
        });
        assertThrows(IOException.class, ()->{
            ostream.write("abc".getBytes(Charsets.US_ASCII), 1, 1);
        });
    }
    
    @Test
    public void testAsInput() throws IOException {
        Function<byte[], InputStream> mr = (array) -> {
            ByteBuffer bb = ByteBuffer.wrap(array);
            return BufferAdapter.asInputStream(bb);
        };
        ContractTesters.runner()
			.ignore("InputStreamTester.mark")
            .function(new byte[0], mr)
			.run();
        assertTrue(true);
    }
    
    @Test
    public void testAsReader_CharBuffer() throws IOException {
        Function<String, Reader> mr = (text) -> {
            CharBuffer cb = CharBuffer.wrap(text);
            return BufferAdapter.asReader(cb);
        };
        ContractTesters.runner()
            .ignore("ReaderTester.mark")
            .ignore("ReaderTester.readBasic_Append")
            .function("", mr)
			.run();
        assertTrue(true);
    }
    
    @Test
    public void testAsReader_ByteBuffer() throws IOException {
        Function<String, Reader> mr = (text) -> {
            ByteBuffer cb = ByteBuffer.wrap(text.getBytes(Charsets.UTF16LE));
            return BufferAdapter.asReader(cb, Charsets.UTF16LE);
        };
        ContractTesters.runner()
            .ignore("ReaderTester.mark")
            .ignore("ReaderTester.readBasic_Append")
            .function("", mr)
			.run();
        assertTrue(true);
    }
    
    @Test
    public void testAsChannel() {
        TAdapter<ByteChannel, ByteBuffer> map = new TAdapter<>();
        
        Function<ByteChannel, byte[]> mr = (channel) -> {
            return map.get(channel).array();
        };
        ContractTesters.runner()
            .ignore("ChannelTester.basicRead_LE")
            .ignore("ChannelTester.basicWrite_LE")
            .param("channel.bytes!", mr)
            .function(new byte[0], map.map(ByteBuffer::wrap, BufferAdapter::asChannel))
			.run();
        assertTrue(true);
    }
    
    @Test
    public void testAsChannel_RO() throws IOException {
        ByteChannel channel = BufferAdapter.asChannel(ByteBuffer.allocate(1024).asReadOnlyBuffer());
        
        // undefined behaviour: IOException is allowed but not required
//        channel.write(ByteBuffer.allocate(0));        
        
        assertThrows(IOException.class, ()->{
            channel.write(ByteBuffer.allocate(3));
        });
    }
    
    @Test
    public void testObjectStream() throws IOException, ClassNotFoundException {
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        List<Object> list1 = Arrays.asList("A", "B", 4);
        List<Object> list2 = Arrays.asList(7, 8, "C");
        List<Object> list3 = Arrays.asList('E', 5);
        
        try(ObjectOutputStream os = BufferAdapter.asObjectOutput(buffer)) {
            os.writeObject(list1);
            os.writeObject(list2);
        }
        try(ObjectOutputStream os = BufferAdapter.asObjectOutput(buffer)) {
            os.writeObject(list3);
        }
        
        buffer.rewind();
        try(ObjectInputStream is = BufferAdapter.asObjectInput(buffer)) {
            Object o1 = is.readObject();
            Object o2 = is.readObject();
            assertEquals(list1, o1);
            assertEquals(list2, o2);
        }
        try(ObjectInputStream is = BufferAdapter.asObjectInput(buffer)) {
            Object o3 = is.readObject();
            assertEquals(list3, o3);
        }
    }
    
    @Test
    public void testObjectStream_Errors() {
        assertThrows(IOException.class, ()->{
            BufferAdapter.asObjectInput(ByteBuffer.allocate(0));
        });
        assertThrows(IOException.class, ()->{
            BufferAdapter.asObjectOutput(ByteBuffer.allocate(0));
        });
        assertThrows(IOException.class, ()->{
            BufferAdapter.asObjectOutput(ByteBuffer.allocate(1024).asReadOnlyBuffer());
        });
    }
    
    @Test
    public void testToArray() {
        char[] data1 = "hello world".toCharArray();
        byte[] data2 = "hello world".getBytes(Charsets.UTF8);
        
        assertArrayEquals(data1, BufferAdapter.toArray(CharBuffer.wrap(data1)));
        assertArrayEquals(data2, BufferAdapter.toArray(ByteBuffer.wrap(data2)));
    }
    
}
