import os
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 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"\xfd1234").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)


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'\xfd\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')
