from io import BytesIO, IOBase
import unittest

from byteb4rb1e.utils.io import ChunksIO


class TestGetChunkSize(unittest.TestCase):

    def test_default(self):
        sample = int.to_bytes(100) + b'\r\n'

        self.assertEqual(
            ChunksIO.get_chunk_size(BytesIO(sample), ChunksIO.max_chunk_size),
            (3, 100)
        )

    def test_oversized(self):
        """any 4-digit integer, exceeds the byte size definition of 512
        """
        chunk_size = 512
        sample = b''.join([int.to_bytes(1) for _ in range(4)]) + b'\r\n'

        with self.assertRaises(ValueError) as result:
            ChunksIO.get_chunk_size(BytesIO(sample), max_size=chunk_size)

        self.assertTrue('unable to reach terminator' in str(result.exception))

    def test_missing_terminator(self):
        chunk_size = 512
        sample = b''.join([int.to_bytes(9) for _ in range(chunk_size)])

        with self.assertRaises(ValueError) as result:
            ChunksIO.get_chunk_size(BytesIO(sample), max_size=chunk_size)

        self.assertTrue('unable to reach terminator' in str(result.exception))

    def test_missing_byte_size(self):
        chunk_size = 512
        sample = b'\r\n'

        with self.assertRaises(ValueError) as result:
            ChunksIO.get_chunk_size(BytesIO(sample), max_size=chunk_size)

        self.assertTrue(
            'without having parsed any byte size' in str(result.exception)
        )


class TestRead(unittest.TestCase):
    def test_default(self):
        """
        """
        chunk_data = 'Foobar'.encode('utf-8')
        chunk = int.to_bytes(len(chunk_data), byteorder="big") + \
                b'\r\n' + \
                chunk_data

        self.assertEqual(ChunksIO(BytesIO(chunk)).read(), b'Foobar')

    def test_perfect_multiple(self):
        """read operations match sizes of chunks

          chunk 1    chunk 2
        |----------|---------|
        ^
        cursor is here
        >------- --|
                   ^
        first requested read ends here
                   >---------|
                             ^
        second requested read ends here
        """
        chunk1_data = 'Foobar'.encode('utf-8')
        chunk1 = int.to_bytes(len(chunk1_data), byteorder="big") + \
                b'\r\n' + \
                chunk1_data

        chunk2_data = 'RTFM'.encode('utf-8')
        chunk2 = int.to_bytes(len(chunk2_data), byteorder="big") + \
                b'\r\n' + \
                chunk2_data

        handler = ChunksIO(BytesIO(chunk1 + chunk2))

        self.assertEqual(handler.read(6), b'Foobar')
        self.assertEqual(handler.read(4), b'RTFM')

    def test_imperfect_multiple_first(self):
        """first read operation does not match the size of the current chunk

          chunk 1    chunk 2
        |----------|---------|
        ^
        cursor is here
        >--------------|
                       ^
        first requested read ends here
                       >-----|
                             ^
        second requested read ends here
        """
        chunk1_data = 'Foo'.encode('utf-8')
        chunk1 = int.to_bytes(len(chunk1_data), byteorder="big") + \
                b'\r\n' + \
                chunk1_data

        chunk2_data = 'barRTFM'.encode('utf-8')
        chunk2 = int.to_bytes(len(chunk2_data), byteorder="big") + \
                b'\r\n' + \
                chunk2_data

        handler = ChunksIO(BytesIO(chunk1 + chunk2))

        self.assertEqual(handler.read(6), b'Foobar')
        self.assertEqual(handler.read(4), b'RTFM')

    def test_imperfect_multiple_second(self):
        """first read operation does not match the size of the current chunk

          chunk 1    chunk 2
        |----------|---------|
        ^
        cursor is here
        >------|
               ^
        first requested read ends here
               >-------------|
                             ^
        second requested read ends here
        """
        chunk1_data = 'FoobarRT'.encode('utf-8')
        chunk1 = int.to_bytes(len(chunk1_data), byteorder="big") + \
                b'\r\n' + \
                chunk1_data

        chunk2_data = 'FM'.encode('utf-8')
        chunk2 = int.to_bytes(len(chunk2_data), byteorder="big") + \
                b'\r\n' + \
                chunk2_data

        handler = ChunksIO(BytesIO(chunk1 + chunk2))

        self.assertEqual(handler.read(6), b'Foobar')
        self.assertEqual(handler.read(4), b'RTFM')

    def test_properly_terminated(self):
        """a proper termination chunk is emitted, resulting in no further
        attempts to retrieved chunks, exposing the behavior of the underlying
        stream
        """
        chunk1_data = 'Foobar'.encode('utf-8')
        chunk1 = int.to_bytes(len(chunk1_data), byteorder="big") + \
                b'\r\n' + \
                chunk1_data

        chunk2_data = ''.encode('utf-8')
        chunk2 = int.to_bytes(len(chunk2_data), byteorder="big") + \
                b'\r\n' + \
                chunk2_data

        handler = ChunksIO(BytesIO(chunk1 + chunk2))

        self.assertEqual(handler.read(6), b'Foobar')
        self.assertEqual(handler.read(4), b'')
        self.assertEqual(handler.read(4), b'')

    def test_not_properly_terminated(self):
        """a proper termination chunk is emitted, resulting in no further
        attempts to retrieved chunks, exposing the behavior of the underlying
        stream
        """
        chunk_data = 'Foobar'.encode('utf-8')
        chunk = int.to_bytes(len(chunk_data), byteorder="big") + \
                b'\r\n' + \
                chunk_data

        handler = ChunksIO(BytesIO(chunk))

        self.assertEqual(handler.read(6), b'Foobar')

        with self.assertRaises(ValueError) as context:
            handler.read(4)

        self.assertTrue('unable to reach terminator' in str(context.exception))

