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

import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SeekableByteChannel;
import net.ranides.assira.text.StringEncoder;
import net.ranides.assira.text.Strings;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public final class NIOTester {
    
    private NIOTester() {
        // utility class
    }
    
    public static void testRWChannel(SeekableByteChannel channel) throws IOException {
        testRead(channel);
        testPosition(channel);
        testTruncate(channel);
        testWrite(channel);
        testWriteEOF(channel);
        testReadEOF(channel);
        testCloseR(channel);
        testCloseW(channel);
    }
    
    public static void testROChannel(SeekableByteChannel channel) throws IOException {
        testRead(channel);
        testPosition(channel);
        testTruncate(channel);
        testReadEOF(channel);
        testCloseR(channel);
    }
    
    public static void testEndianReads(SeekableByteChannel channel) throws IOException {
        ByteBuffer buffer1B = ByteBuffer.allocate(7).order(ByteOrder.BIG_ENDIAN);
        channel.position(0);
        channel.read(buffer1B);
        assertEquals("11 12 13 14 15 16 17",  hex(buffer1B));
        
        ByteBuffer buffer1L = ByteBuffer.allocate(7).order(ByteOrder.LITTLE_ENDIAN);
        channel.position(0);
        channel.read(buffer1L);
        assertEquals("12 11 14 13 16 15 18",  hex(buffer1L));
        
        ByteBuffer buffer2B = ByteBuffer.allocate(7).order(ByteOrder.BIG_ENDIAN);
        channel.position(1);
        channel.read(buffer2B);
        assertEquals("12 13 14 15 16 17 18",  hex(buffer2B));
        
        ByteBuffer buffer2L = ByteBuffer.allocate(7).order(ByteOrder.LITTLE_ENDIAN);
        channel.position(1);
        channel.read(buffer2L);
        assertEquals("11 14 13 16 15 18 17",  hex(buffer2L));
    }
    
    private static void testRead(SeekableByteChannel channel) throws IOException {
        ByteBuffer buffer1 = ByteBuffer.allocate(7);
        assertEquals(7, channel.read(buffer1));
        assertEquals("00 01 02 03 04 05 06",  hex(buffer1));
        assertEquals(0, channel.read(buffer1));
        
        ByteBuffer buffer2 = ByteBuffer.allocate(8);
        assertEquals(8, channel.read(buffer2));
        assertEquals("07 08 09 0A 0B 0C 0D 0E",  hex(buffer2));

        ByteBuffer buffer3 = ByteBuffer.allocate(10);
        buffer3.put(new byte[]{0x51, 0x52, 0x53, 0x54});
        assertEquals(6, channel.read(buffer3));
        assertEquals("51 52 53 54 0F 10 11 12 13 14",  hex(buffer3));
        
        ByteBuffer buffer4 = ByteBuffer.allocate(10);
        buffer4.limit(8);
        assertEquals(8, channel.read(buffer4));
        assertEquals("15 16 17 18 19 1A 1B 1C 00 00",  hex(buffer4));
    }
    
    private static void testPosition(SeekableByteChannel channel) throws IOException {
        assertEquals(29, channel.position());
        assertEquals(64, channel.size());
        assertEquals(40, channel.position(40).position());
        assertEquals(12, channel.position(12).position());
        assertEquals(32, channel.position(32).position());
        
        ByteBuffer buffer5 = ByteBuffer.allocate(8);
        assertEquals(8, channel.read(buffer5));
        assertEquals("20 21 22 23 24 25 26 27",  hex(buffer5));
        
        assertTrue( channel.position(1024).position() >= 64 );
        ByteBuffer buffer6 = ByteBuffer.allocate(8);
        assertEquals(-1, channel.read(buffer6));
        
        assertEquals(48, channel.position(48).position() );
        assertEquals(8, channel.read(buffer6));
        assertEquals("30 31 32 33 34 35 36 37",  hex(buffer6));
        
        assertEquals(60, channel.position(60).position() );
        ByteBuffer buffer7 = ByteBuffer.allocate(16);
        assertEquals(4, channel.read(buffer7));
    }

    private static void testTruncate(SeekableByteChannel channel) throws IOException {
        assertEquals(64, channel.position() );
        channel.truncate(52);
        assertEquals(52, channel.size());
        assertEquals(52, channel.position());
        channel.truncate(60);
        assertEquals(52, channel.size());
        assertEquals(52, channel.position());
    }
    
    private static void testWrite(SeekableByteChannel channel) throws IOException {
        assertEquals(16, channel.position(16).position());
        ByteBuffer buffer8 = buffer("71 72 73 74");
        assertEquals(4, channel.write(buffer8));
        
        ByteBuffer buffer9 = buffer("55 55 61 62 63 64 65 66 67 68 55 55");
        buffer9.get(new byte[2]);
        buffer9.limit(10);
        assertEquals(8, channel.write(buffer9));
        
        ByteBuffer buffer11 = buffer("91 92 93 94 95");
        ByteBuffer buffer12 = buffer("96 97 98 99 9A");
        assertEquals(32, channel.position(32).position());
        assertEquals(5, channel.write(buffer11));
        assertEquals(5, channel.write(buffer12));

        
        ByteBuffer buffer10 = buffer("41 42 43 44 45 46 47 48");
        assertEquals(46, channel.position(46).position());
        assertEquals(6, channel.write(buffer10));
        
        try {
            channel.write(buffer10);
            fail("EOFException expected");
        } catch(EOFException cause) {
            assertTrue(true);
        }
    }
    
    private static void testWriteEOF(SeekableByteChannel channel) throws IOException {
        assertTrue( channel.position(1024).position() >= 52 );
        ByteBuffer buffer1 = ByteBuffer.allocate(32);
        try {
            channel.write(buffer1);
            fail("EOFException expected");
        } catch(EOFException cause) {
            assertTrue(true);
        }
        assertEquals(-1, channel.read(buffer1));
    }
    
    private static void testReadEOF(SeekableByteChannel channel) throws IOException {
        assertTrue( channel.position(1024).position() >= 52 );
        ByteBuffer buffer1 = ByteBuffer.allocate(32);
        assertEquals(-1, channel.read(buffer1));
    }
    
    private static void testCloseR(SeekableByteChannel channel) throws IOException {
        assertEquals(16, channel.position(16).position());
        assertTrue(channel.isOpen());
        channel.close();
        assertFalse(channel.isOpen());
        
        try {
            channel.close();
            fail("ClosedChannelException expected");
        } catch(ClosedChannelException cause) {
            assertTrue(true);
        }
        
        try {
            ByteBuffer buffer12 = ByteBuffer.allocate(8);
            channel.read(buffer12);
            fail("ClosedChannelException expected");
        } catch(ClosedChannelException cause) {
            assertTrue(true);
        }
    }
    
    private static void testCloseW(SeekableByteChannel channel) throws IOException {
        assertFalse( channel.isOpen() );
        try {
            ByteBuffer buffer11 = ByteBuffer.allocate(8);
            channel.write(buffer11);
            fail("ClosedChannelException expected");
        } catch(ClosedChannelException cause) {
            assertTrue(true);
        }
    }
    
    public static String hex(ByteBuffer buffer) {
        return hex(buffer.array());
    }
    
    public static String hex(byte[] array) {
        return StringEncoder.bytesToHex(array," ");
    }
    
    public static ByteBuffer buffer(String values) {
        return ByteBuffer.wrap(array(values));
    }
    
    public static byte[] array(String values) {
        return StringEncoder.hexToBytes(values.replaceAll(" ", ""));
    }
    
    public static void hexdump(char[] data) {
        for(int i=0; i<data.length; i++) {
            System.out.printf("%04X ", (int)data[i]);
            if(7 == i%8) {
                System.out.println();
            }
        }
        System.out.println("------------");
    }
}
