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

import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.ByteChannel;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SeekableByteChannel;
import java.util.function.Function;
import javax.annotation.Resource;
import net.ranides.assira.junit.TestContract;
import static net.ranides.assira.junit.NewAssert.*;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public class ChannelTester {
    
    @Resource(name = "channel.bytes!")
    private Function<ByteChannel, byte[]> os;
	
	@TestContract
    public void basicRead(Function<byte[], ByteChannel> f) throws IOException {
		ByteChannel channel = f.apply(new byte[]{
			10,11,12,13,14,15,16,17,18,19,
			20,21,22,23,24,25,26,27,28,29,
			30,31,32,33
		});
		assertTrue( channel.isOpen() );
		
		assertRead(new byte[]{10,11,12,13}, channel);
		assertRead(new byte[]{14,15,16,17}, channel);
		assertRead(new byte[]{18,19,20}, channel);
		assertRead(new byte[]{21,22,23}, channel);
		assertRead(new byte[]{24,25,26}, channel);
		assertRead(new byte[]{27,28,29,30,31}, channel);
		assertRead(new byte[]{}, channel);
		assertRead(new byte[]{32}, channel);
		assertRead(new byte[]{33}, channel);
		
		assertEquals(-1, channel.read(ByteBuffer.allocate(5)));
		assertEquals(-1, channel.read(ByteBuffer.allocate(0)));
		
		channel.close();
		assertFalse( channel.isOpen() );
	}
	
	@TestContract
    public void basicReadRandom(Function<byte[], SeekableByteChannel> f) throws IOException {
		SeekableByteChannel channel = f.apply(new byte[]{
			10,11,12,13,14,15,16,17,18,19,
			20,21,22,23,24,25,26,27,28,29,
			30,31,32,33
		});
		assertEquals(24, channel.size());
		
		assertRead(new byte[]{10,11,12,13}, channel);
		assertRead(new byte[]{14,15,16,17}, channel);
		channel.position(channel.position()+3);
		assertRead(new byte[]{21,22,23}, channel);
		channel.position(channel.position()+3);
		assertRead(new byte[]{27,28,29,30,31}, channel);
		assertRead(new byte[]{}, channel);
		assertRead(new byte[]{32}, channel);
		assertRead(new byte[]{33}, channel);
		
		assertEquals(-1, channel.read(ByteBuffer.allocate(5)));
		assertEquals(-1, channel.read(ByteBuffer.allocate(0)));
		
		assertEquals(24, channel.size());
		
		channel.position(1);
		assertRead(new byte[]{11,12,13}, channel);
		assertRead(new byte[]{14,15,16,17}, channel);
		
		assertEquals(24, channel.size());
		
		channel.close();
	}
	
	@TestContract
    public void basicReadRandomTruncate(Function<byte[], SeekableByteChannel> f) throws IOException {
		SeekableByteChannel channel = f.apply(new byte[]{
			10,11,12,13,14,15,16,17,18,19,
			20,21,22,23,24,25,26,27,28,29,
			30,31,32,33
		});
		assertEquals(24, channel.size());
		
		channel.truncate(16);
		channel.truncate(20);
		assertEquals(16, channel.size());
		
		assertRead(new byte[]{10,11,12,13}, channel);
		assertRead(new byte[]{14,15,16,17}, channel);
		channel.position(channel.position()+3);
		assertRead(new byte[]{21,22,23}, channel);
		channel.position(channel.position()+3);
		
		assertEquals(-1, channel.read(ByteBuffer.allocate(5)));
		assertEquals(-1, channel.read(ByteBuffer.allocate(0)));
		
		assertEquals(16, channel.size());
		
		channel.position(1);
		assertRead(new byte[]{11,12,13}, channel);
		assertRead(new byte[]{14,15,16,17}, channel);
		
		assertEquals(8, channel.position());
		assertEquals(16, channel.size());
		
		channel.truncate(4);
		assertEquals(4, channel.position());
		assertEquals(4, channel.size());
		
		channel.close();
		
		assertThrows(ClosedChannelException.class, ()->{
			channel.read(ByteBuffer.allocate(4));
		});
	}
	
	@TestContract
    public void basicRead_LE(Function<byte[], ByteChannel> f) throws IOException {
		ByteChannel channel = f.apply(new byte[]{
			10,11,12,13,14,15,16,17,18,19,
			20,21,22,23,24,25,26,27,28,29,
			30,31,32,33
		});
		assertRead_LE(new byte[]{11,10,13,12}, channel);
		assertRead_LE(new byte[]{15,14,17,16}, channel);
		assertRead_LE(new byte[]{19,18,21}, channel);
		assertRead_LE(new byte[]{20,23,22}, channel);
		assertRead_LE(new byte[]{25,24,27}, channel);
		assertRead_LE(new byte[]{26,29,28,31,30}, channel);
		assertRead_LE(new byte[]{}, channel);
		assertRead_LE(new byte[]{33}, channel);
		assertRead_LE(new byte[]{32}, channel);
		
		assertEquals(-1, channel.read(ByteBuffer.allocate(5)));
		assertEquals(-1, channel.read(ByteBuffer.allocate(0)));
		
		channel.close();
	}
    
    @TestContract
    public void basicWrite(Function<byte[], ByteChannel> f) throws IOException {
		ByteChannel channel = f.apply(new byte[32]);
        assertTrue(channel.isOpen());
        
        assertWrite(channel, new byte[]{10,11,12,13});
        assertWrite(channel, new byte[]{14,15,16,17});
        assertWrite(channel, new byte[]{18,19,20});
        assertWrite(channel, new byte[]{21,22,23});
        assertWrite(channel, new byte[]{24,25,26});
        assertWrite(channel, new byte[]{27,28,29,30,31});
        assertWrite(channel, new byte[]{});
        assertWrite(channel, new byte[]{32,33});
        
		assertArrayEquals(new byte[]{10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,0,0,0,0,0,0,0,0}, os.apply(channel));
        
        assertRead(new byte[]{0,0,0}, channel);
        
        assertEquals(3, channel.write(ByteBuffer.wrap(new byte[]{41,42,43})) );
        
        assertArrayEquals(new byte[]{10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,0,0,0,41,42,43,0,0}, os.apply(channel));
        
        channel.close();
        assertFalse(channel.isOpen());
    }
	
	@TestContract
    public void basicWrite_LE(Function<byte[], ByteChannel> f) throws IOException {
		ByteChannel channel = f.apply(new byte[32]);
        assertTrue(channel.isOpen());
        
        assertWrite_LE(channel, new byte[]{10,11,12,13});
        assertWrite_LE(channel, new byte[]{14,15,16,17});
        assertWrite_LE(channel, new byte[]{18,19,20});
        assertWrite_LE(channel, new byte[]{21,22,23});
        assertWrite_LE(channel, new byte[]{24,25,26});
        assertWrite_LE(channel, new byte[]{27,28,29,30,31});
        assertWrite_LE(channel, new byte[]{});
        assertWrite_LE(channel, new byte[]{32,33});
        
		assertArrayEquals(new byte[]{11,10,13,12,15,14,17,16,19,18,21,20,23,22,25,24,27,26,29,28,31,30,33,32,0,0,0,0,0,0,0,0}, os.apply(channel));
        
        assertRead(new byte[]{0,0,0}, channel);
        
        assertWrite_LE(channel, new byte[]{41,42,43});
        
        assertArrayEquals(new byte[]{11,10,13,12,15,14,17,16,19,18,21,20,23,22,25,24,27,26,29,28,31,30,33,32,0,0,41,0,43,42,0,0}, os.apply(channel));
        
        channel.close();
        assertFalse(channel.isOpen());
    }

    
    @TestContract
    public void basicWriteRandom(Function<byte[], SeekableByteChannel> f) throws IOException {
		SeekableByteChannel channel = f.apply(new byte[16]);
        assertTrue(channel.isOpen());
        
        assertEquals(3, channel.write(ByteBuffer.wrap(new byte[]{11,12,13})) );
        assertEquals(2, channel.write(ByteBuffer.wrap(new byte[]{14,15})) );
        
        assertArrayEquals(new byte[]{11,12,13,14,15,0,0,0,0,0,0,0,0,0,0,0}, os.apply(channel));
        
        assertRead(new byte[]{0,0,0}, channel);
        
        assertEquals(3, channel.write(ByteBuffer.wrap(new byte[]{21,22,23})) );
        
        assertArrayEquals(new byte[]{11,12,13,14,15,0,0,0,21,22,23,0,0,0,0,0}, os.apply(channel));
        
        channel.position(3);
        assertRead(new byte[]{14,15,0}, channel);
        
        assertEquals(4, channel.write(ByteBuffer.wrap(new byte[]{31,32,33,34})) );
        
        assertArrayEquals(new byte[]{11,12,13,14,15,0,31,32,33,34,23,0,0,0,0,0}, os.apply(channel));
        
        channel.position(8);
        assertRead(new byte[]{33,34,23,0}, channel);
        
        assertArrayEquals(new byte[]{11,12,13,14,15,0,31,32,33,34,23,0,0,0,0,0}, os.apply(channel));
        
        channel.close();
        assertFalse(channel.isOpen());
    }
    
    @TestContract
    public void basicWriteTruncateEOF(Function<byte[], SeekableByteChannel> f) throws IOException {
        SeekableByteChannel channel = f.apply(new byte[16]);

        assertEquals(16, channel.truncate(32).size());
        assertEquals(12, channel.truncate(12).size());
        assertEquals(12, channel.truncate(48).size());
        
        long s = channel.size() - 4;
        channel.position(s);
        assertEquals(4, channel.write(ByteBuffer.wrap(new byte[]{6,7,8,9, 31,32})));
        
        assertThrows(EOFException.class, ()->{
            channel.write(ByteBuffer.wrap(new byte[]{31,32}));
        });
        
        assertEquals(s, channel.position( channel.position()-4).position() );
        assertEquals(channel.size(),  channel.position(channel.size()).position());
        
        channel.position( channel.position() - 4);
        
        ByteBuffer buffer1 = ByteBuffer.allocate(5);
        assertEquals(4, channel.read(buffer1));
        assertArrayEquals(new byte[]{6,7,8,9,0}, buffer1.array());
        
        ByteBuffer buffer2 = ByteBuffer.allocate(5);
        assertEquals(-1, channel.read(buffer2));
        
        assertArrayEquals(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 6, 7, 8, 9, 0, 0, 0, 0}, os.apply(channel));
    }
	
	@TestContract
    public void basicWriteTruncate(Function<byte[], SeekableByteChannel> f) throws IOException {
        SeekableByteChannel channel = f.apply(new byte[16]);

        assertEquals(16, channel.truncate(32).size());
        assertEquals(12, channel.truncate(12).size());
        assertEquals(12, channel.truncate(48).size());
        
        long s = channel.size() - 4;
        channel.position(s);
        assertEquals(4, channel.write(ByteBuffer.wrap(new byte[]{6,7,8,9, 31,32})));
        
        assertThrows(EOFException.class, ()->{
            channel.write(ByteBuffer.wrap(new byte[]{31,32}));
        });
        
        assertEquals(s, channel.position( channel.position()-4).position() );
        assertEquals(channel.size(),  channel.position(channel.size()).position());
        
        channel.position( channel.position() - 4);
        
        ByteBuffer buffer1 = ByteBuffer.allocate(5);
        assertEquals(4, channel.read(buffer1));
        assertArrayEquals(new byte[]{6,7,8,9,0}, buffer1.array());
        
        ByteBuffer buffer2 = ByteBuffer.allocate(5);
        assertEquals(-1, channel.read(buffer2));
        
        assertArrayEquals(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 6, 7, 8, 9, 0, 0, 0, 0}, os.apply(channel));
    }

    private static void assertRead(byte[] expected, ByteChannel channel) throws IOException {
        ByteBuffer buffer = ByteBuffer
			.allocate(expected.length)
			.order(ByteOrder.BIG_ENDIAN);
        assertEquals(expected.length, channel.read(buffer));
        assertArrayEquals(expected, buffer.array());
    }
	
	private static void assertRead_LE(byte[] expected, ByteChannel channel) throws IOException {
        ByteBuffer buffer = ByteBuffer
			.allocate(expected.length)
			.order(ByteOrder.LITTLE_ENDIAN);
        assertEquals(expected.length, channel.read(buffer));
        assertArrayEquals(expected, buffer.array());
    }
	
	private static void assertWrite(ByteChannel channel, byte[] values) throws IOException {
		assertEquals(values.length, channel.write(ByteBuffer.wrap(values).order(ByteOrder.BIG_ENDIAN)) );
	}
	
	private static void assertWrite_LE(ByteChannel channel, byte[] values) throws IOException {
		assertEquals(values.length, channel.write(ByteBuffer.wrap(values).order(ByteOrder.LITTLE_ENDIAN)) );
	}

}
