import binascii
from io import BytesIO
from unittest import TestCase
from neocore.Fixed8 import Fixed8
from neocore.UInt160 import UInt160
from neocore.UInt256 import UInt256
from neocore.IO.Mixins import SerializableMixin
import neocore.IO.BinaryWriter as BinaryWriter
from neocore.IO.BinaryReader import BinaryReader


class TestObject(SerializableMixin):
    test_value = None

    def __init__(self, test_value=None):
        self.test_value = test_value

    def Serialize(self, writer):
        writer.WriteUInt32(self.test_value)

    def Deserialize(self, reader):
        self.test_value = reader.ReadUInt32()

    def ToArray(self):
        pass


class SerializableMixinTestCase(TestCase):
    def test_serializable_mixin(self):
        m = SerializableMixin()
        m.Serialize(None)
        m.Deserialize(None)
        m.ToArray()


def get_br(stream_data):
    return BinaryReader(BytesIO(stream_data))


def get_bw(stream_data):
    return BinaryWriter.BinaryWriter(BytesIO(stream_data))


class BinaryReaderTestCase(TestCase):
    def setUp(self):
        stream = BytesIO(b"\x41\x01\x02\x03\x04")
        self.br = BinaryReader(stream)

    def test_various(self):
        self.assertEqual(self.br.unpack("c"), b"A")

        b = self.br.ReadByte()
        self.assertEqual(b, 1)

        b = self.br.ReadByte(do_ord=False)
        self.assertEqual(b, b"\x02")

        bio0 = BytesIO(b"")
        br0 = BinaryReader(bio0)
        b = br0.ReadByte()
        self.assertEqual(b, 0)
        # print("===", b)

        b = self.br.ReadBool()
        self.assertEqual(b, True)

        self.assertEqual(get_br(b"\x41").ReadChar(), b"A")

        self.assertEqual(get_br(b"1234").ReadFloat(), 1.6688933612840628e-07)
        self.assertEqual(get_br(b"12345678").ReadDouble(), 6.821320051701325e-38)
        self.assertEqual(get_br(b"12").ReadInt8(), 49)
        self.assertEqual(get_br(b"12").ReadUInt8(), 49)
        self.assertEqual(get_br(b"12").ReadInt16(), 12849)
        self.assertEqual(get_br(b"12").ReadUInt16(), 12849)
        self.assertEqual(get_br(b"\xff234").ReadInt32(), 875770623)
        self.assertEqual(get_br(b"\xff234").ReadUInt32(), 875770623)
        self.assertEqual(get_br(b"12345678").ReadInt64(), 4050765991979987505)
        self.assertEqual(get_br(b"12345678").ReadUInt64(), 4050765991979987505)

        self.assertEqual(get_br(b"\xfd1234abc").ReadVarBytes(), b"34abc")
        self.assertEqual(get_br(b"\x03234").ReadString(), b"234")
        self.assertEqual(get_br(b"\x03123").ReadVarString(), b"123")
        self.assertEqual(get_br(b"abc").ReadFixedString(2), b"ab")

        x = get_br(b"123871987392873918723981723987189").ReadUInt256()
        self.assertEqual(str(x), "3831373839333237313839333237383139333738323933373839313738333231")

        x = get_br(b"123871987392873918723981723987189").ReadUInt160()
        self.assertEqual(str(x), "3237383139333738323933373839313738333231")

        x = get_br(b"\x01\x02\x0345678").ReadFixed8()
        self.assertEqual(str(x), "40507659919.768295")

        self.assertEqual(get_br(b"\x0212345567898765434567890987").ReadHashes(), ['3738393039383736353433343536373839383736353534333231', ''])

    def test_varint(self):
        self.assertEqual(get_br(b"").ReadVarInt(), 0)
        self.assertEqual(get_br(b"\xfd12").ReadVarInt(), 12849)
        self.assertEqual(get_br(b"\xfe1234").ReadVarInt(), 875770417)
        self.assertEqual(get_br(b"\xff12345678").ReadVarInt(), 4050765991979987505)

        with self.assertRaises(Exception):
            self.assertEqual(get_br(b"\xfd1234").ReadVarInt(max=12848), 12849)

    def test_Read2000256List(self):
        val = b"1" * 64
        x = get_br(val * 2000)
        res = x.Read2000256List()
        for item in res:
            self.assertEqual(item, val)

    def test_readserializable_success(self):
        stream = BytesIO(b"\x04\x01\x02\x03\x04")
        reader = BinaryReader(stream)
        test_object_list = reader.ReadSerializableArray('tests.test_io.TestObject')
        self.assertEqual(test_object_list[0].test_value, 0x4030201)

    def test_readserializable_fail(self):
        # fails because input stream is too short
        stream = BytesIO(b"\x04\x01\x02\x03")
        reader = BinaryReader(stream)
        test_object_list = reader.ReadSerializableArray('tests.test_io.TestObject')
        self.assertEqual(len(test_object_list), 0)

    def test_readvarint_fail(self):
        stream = BytesIO(b"")
        reader = BinaryReader(stream)
        result = reader.ReadVarInt()
        self.assertEqual(result, 0)


class BinaryWriterTestCase(TestCase):
    def test_various(self):
        self.assertEqual(BinaryWriter.swap32(123), 2063597568)
        self.assertEqual(BinaryWriter.swap32(2063597568), 123)

        self.assertEqual(BinaryWriter.convert_to_uint160(123), '00000000000001111011')
        self.assertEqual(BinaryWriter.convert_to_uint256(123), '00000000000000000000000001111011')

        def bw_writebyte(stream_data):
            stream = BytesIO()
            bw = BinaryWriter.BinaryWriter(stream)
            bw.WriteByte(stream_data)
            stream.seek(0)
            return stream

        self.assertEqual(bw_writebyte(b'\x00').read(1), b'\x00')
        self.assertEqual(bw_writebyte('1').read(1), b'1')
        self.assertEqual(bw_writebyte(1).read(1), b'\x01')

        def bw_setup():
            stream = BytesIO()
            bw = BinaryWriter.BinaryWriter(stream)
            return stream, bw

        stream, bw = bw_setup()
        bw.pack('c', b'\x41')
        stream.seek(0)
        self.assertEqual(stream.read(1), b'A')

        stream, bw = bw_setup()
        bw.WriteChar(b'\x41')
        stream.seek(0)
        self.assertEqual(stream.read(1), b'A')

        stream, bw = bw_setup()
        bw.WriteFloat(123.41)
        stream.seek(0)
        self.assertEqual(stream.readline(), b'\xec\xd1\xf6B')

        stream, bw = bw_setup()
        bw.WriteDouble(976)
        stream.seek(0)
        self.assertEqual(stream.readline(), b'\x00\x00\x00\x00\x00\x80\x8e@')

        stream, bw = bw_setup()
        bw.WriteUInt8(167)
        stream.seek(0)
        self.assertEqual(stream.readline(), b'\xa7')

        stream, bw = bw_setup()
        bw.WriteBool(True)
        stream.seek(0)
        self.assertEqual(stream.readline(), b'\x01')

        stream, bw = bw_setup()
        bw.WriteBool(False)
        stream.seek(0)
        self.assertEqual(stream.readline(), b'\x00')

        stream, bw = bw_setup()
        bw.WriteInt8(17)
        stream.seek(0)
        self.assertEqual(stream.readline(), b'\x11')

        stream, bw = bw_setup()
        bw.WriteInt16(12345)
        stream.seek(0)
        self.assertEqual(stream.readline(), b'90')

        stream, bw = bw_setup()
        bw.WriteUInt16(12345)
        stream.seek(0)
        self.assertEqual(stream.readline(), b'90')

        stream, bw = bw_setup()
        bw.WriteUInt16(32767 * 2)
        stream.seek(0)
        self.assertEqual(stream.readline(), b'\xfe\xff')

        stream, bw = bw_setup()
        bw.WriteInt32(32767 * 2 * 2)
        stream.seek(0)
        self.assertEqual(stream.readline(), b'\xfc\xff\x01\x00')

        stream, bw = bw_setup()
        bw.WriteUInt32(32767 * 2 * 2)
        stream.seek(0)
        self.assertEqual(stream.readline(), b'\xfc\xff\x01\x00')

        stream, bw = bw_setup()
        bw.WriteInt64(32767 * 2 * 2)
        stream.seek(0)
        self.assertEqual(stream.readline(), b'\xfc\xff\x01\x00\x00\x00\x00\x00')

        stream, bw = bw_setup()
        bw.WriteUInt64(32767 * 2 * 2)
        stream.seek(0)
        self.assertEqual(stream.readline(), b'\xfc\xff\x01\x00\x00\x00\x00\x00')

        stream, bw = bw_setup()
        bw.WriteUInt160(UInt160(b'12345678901234567890'))
        stream.seek(0)
        self.assertEqual(stream.readline(), b'\x124Vx\x90\x124Vx\x90')

        with self.assertRaises(Exception):
            bw.WriteUInt160(123)

        stream, bw = bw_setup()
        bw.WriteUInt256(UInt256(b'12345678901234567890123456789012'))
        stream.seek(0)
        self.assertEqual(stream.readline(), b'\x124Vx\x90\x124Vx\x90\x124Vx\x90\x12')

        with self.assertRaises(Exception):
            bw.WriteUInt256(123)

        with self.assertRaises(TypeError):
            bw.WriteVarInt("x")

        with self.assertRaises(Exception):
            bw.WriteVarInt(-1)

        stream, bw = bw_setup()
        bw.WriteVarInt(12)
        stream.seek(0)
        self.assertEqual(stream.readline(), b'\x0c')

        stream, bw = bw_setup()
        bw.WriteVarInt(0xff)
        stream.seek(0)
        self.assertEqual(stream.readline(), b'\xfd\xff\x00')

        stream, bw = bw_setup()
        bw.WriteVarInt(0xffffff)
        stream.seek(0)
        self.assertEqual(stream.readline(), b'\xfe\xff\xff\xff\x00')

        stream, bw = bw_setup()
        bw.WriteVarInt(0xFFFFFFFFFF)
        stream.seek(0)
        self.assertEqual(stream.readline(), b'\xff\xff\xff\xff\xff\xff\x00\x00\x00')

        stream, bw = bw_setup()
        bw.WriteVarBytes(b'123')
        stream.seek(0)
        self.assertEqual(stream.readline(), b'\x03123')

        stream, bw = bw_setup()
        bw.WriteFixedString("test", 10)
        stream.seek(0)
        self.assertEqual(stream.readline(), b'test\x00\x00\x00\x00\x00\x00')

        stream, bw = bw_setup()
        bw.WriteSerializableArray(None)
        stream.seek(0)
        self.assertEqual(stream.readline(), b'\x00')

        # stream, bw = bw_setup()
        # val = [b'x' * 64 for _ in range(2000)]
        # bw.Write2000256List(val)
        # stream.seek(0)
        # self.assertEqual(stream.readline(), b'\x00')

        stream, bw = bw_setup()
        bw.WriteHashes([b'12', b'45'])
        stream.seek(0)
        self.assertEqual(stream.readline(), b'\x02\x12E')

        stream, bw = bw_setup()
        bw.WriteFixed8(Fixed8(100))
        stream.seek(0)
        self.assertEqual(stream.readline(), b'd\x00\x00\x00\x00\x00\x00\x00')

        #
        stream, bw = bw_setup()
        test_value = "my_test_string"
        bw.WriteVarString(test_value)
        stream.seek(0)
        result = stream.readline()
        # note \x0e is the length of `test_value` that's appended in front
        self.assertEqual(b'\x0emy_test_string', result)

    def test_Write2000256List(self):
        item = b'aa' * 32
        my_list = [item] * 2000

        stream = BytesIO()
        bw = BinaryWriter.BinaryWriter(stream)
        bw.Write2000256List(my_list)

        stream.seek(0)
        for i in range(0, 2000):
            x = binascii.hexlify(stream.readline(32))
            self.assertEqual(item, x)

    def test_write_serializable_array(self):
        my_array = [TestObject(1), TestObject(2)]

        stream = BytesIO()
        bw = BinaryWriter.BinaryWriter(stream)
        bw.WriteSerializableArray(my_array)

        stream.seek(0)
        reader = BinaryReader(stream)
        test_object_list = reader.ReadSerializableArray('tests.test_io.TestObject')
        self.assertEqual(0x1, test_object_list[0].test_value)
        self.assertEqual(0x2, test_object_list[1].test_value)

    def test_writefixedstring_exception(self):
        stream = BytesIO()
        bw = BinaryWriter.BinaryWriter(stream)

        with self.assertRaises(Exception) as context:
            bw.WriteFixedString("abc", 2)
        self.assertTrue("string longer than fixed length" in str(context.exception))
