##############################################################################
#
#                        Crossbar.io Fabric
#     Copyright (C) Crossbar.io Technologies GmbH. All rights reserved.
#
##############################################################################

import pprint

import flatbuffers

from zlmdb import table, MapBytes32FlatBuffers

from .common import pack_uint256, unpack_uint256
from .gen.xbr import TokenTransfer as TokenTransferGen
from .gen.xbr import PaymentChannel as PaymentChannelGen


class _TokenTransferGen(TokenTransferGen.TokenTransfer):
    """
    Expand methods on the class code generated by flatc.

    FIXME: comes up with a PR for flatc to generated this stuff automatically.
    """

    @classmethod
    def GetRootAsTokenTransfer(cls, buf, offset):
        n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
        x = _TokenTransferGen()
        x.Init(buf, n + offset)
        return x

    def TxHashAsBytes(self):
        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
        if o != 0:
            _off = self._tab.Vector(o)
            _len = self._tab.VectorLen(o)
            return memoryview(self._tab.Bytes)[_off:_off+_len]
        return None

    def BlockHashAsBytes(self):
        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
        if o != 0:
            _off = self._tab.Vector(o)
            _len = self._tab.VectorLen(o)
            return memoryview(self._tab.Bytes)[_off:_off+_len]
        return None

    def FromAddressAsBytes(self):
        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
        if o != 0:
            _off = self._tab.Vector(o)
            _len = self._tab.VectorLen(o)
            return memoryview(self._tab.Bytes)[_off:_off+_len]
        return None

    def ToAddressAsBytes(self):
        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
        if o != 0:
            _off = self._tab.Vector(o)
            _len = self._tab.VectorLen(o)
            return memoryview(self._tab.Bytes)[_off:_off+_len]
        return None

    def ValueAsBytes(self):
        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
        if o != 0:
            _off = self._tab.Vector(o)
            _len = self._tab.VectorLen(o)
            return memoryview(self._tab.Bytes)[_off:_off+_len]
        return None


class TokenTransfer(object):
    """
    ``IERC20.TokenTransfer`` event database object.
    """

    def __init__(self, from_fbs=None):
        self._from_fbs = from_fbs

        self._tx_hash = None
        self._block_hash = None
        self._from_address = None
        self._to_address = None
        self._value = None

    def marshal(self):
        obj = {
            'tx_hash': self._tx_hash,
            'block_hash': self._block_hash,
            'from_address': self._from_address,
            'to_address': self._to_address,
            'value': self._value,
        }
        return obj

    def __str__(self):
        return '\n{}\n'.format(pprint.pformat(self.marshal()))

    @property
    def tx_hash(self):
        if self._tx_hash is None and self._from_fbs:
            if self._from_fbs.TxHashLength():
                self._tx_hash = self._from_fbs.TxHashAsBytes()
        return self._tx_hash

    @tx_hash.setter
    def tx_hash(self, value):
        assert value is None or type(value) == bytes
        self._tx_hash = value

    @property
    def block_hash(self):
        if self._block_hash is None and self._from_fbs:
            if self._from_fbs.TxHashLength():
                self._block_hash = self._from_fbs.BlockHashAsBytes()
        return self._block_hash

    @block_hash.setter
    def block_hash(self, value):
        assert value is None or type(value) == bytes
        self._block_hash = value

    @property
    def from_address(self):
        if self._from_address is None and self._from_fbs:
            if self._from_fbs.TxHashLength():
                self._from_address = self._from_fbs.FromAddressAsBytes()
        return self._from_address

    @from_address.setter
    def from_address(self, value):
        assert value is None or type(value) == bytes
        self._from_address = value

    @property
    def to_address(self):
        if self._to_address is None and self._from_fbs:
            if self._from_fbs.TxHashLength():
                self._to_address = self._from_fbs.ToAddressAsBytes()
        return self._to_address

    @to_address.setter
    def to_address(self, value):
        assert value is None or type(value) == bytes
        self._to_address = value

    @property
    def value(self):
        if self._value is None and self._from_fbs:
            if self._from_fbs.TxHashLength():
                _value = self._from_fbs.ValueAsBytes()
                self._value = unpack_uint256(bytes(_value))
        return self._value

    @value.setter
    def value(self, value):
        assert value is None or type(value) == int
        self._value = value

    @staticmethod
    def cast(buf):
        return TokenTransfer(_TokenTransferGen.GetRootAsTokenTransfer(buf, 0))

    def build(self, builder):

        tx_hash = self.tx_hash
        if tx_hash:
            tx_hash = builder.CreateString(tx_hash)

        block_hash = self.block_hash
        if block_hash:
            block_hash = builder.CreateString(block_hash)

        from_address = self.from_address
        if from_address:
            from_address = builder.CreateString(from_address)

        to_address = self.to_address
        if to_address:
            to_address = builder.CreateString(to_address)

        value = self.value
        if value:
            value = builder.CreateString(pack_uint256(value))

        TokenTransferGen.TokenTransferStart(builder)

        if tx_hash:
            TokenTransferGen.TokenTransferAddTxHash(builder, tx_hash)

        if block_hash:
            TokenTransferGen.TokenTransferAddBlockHash(builder, block_hash)

        if from_address:
            TokenTransferGen.TokenTransferAddFromAddress(builder, from_address)

        if to_address:
            TokenTransferGen.TokenTransferAddToAddress(builder, to_address)

        if value:
            TokenTransferGen.TokenTransferAddValue(builder, value)

        final = TokenTransferGen.TokenTransferEnd(builder)

        return final


@table('7dcfffd2-4859-45d6-b9cd-ba7930a7a107', build=TokenTransfer.build, cast=TokenTransfer.cast)
class TokenTransfers(MapBytes32FlatBuffers):
    """
    Persisted token transfers archive.

    Map :class:`zlmdb.MapBytes32FlatBuffers` from ``transaction_hash`` to :class:`crossbarfx.cfxdb.xbr.TokenTransfer`
    """


class _PaymentChannelGen(PaymentChannelGen.PaymentChannel):
    """
    Expand methods on the class code generated by flatc.

    FIXME: comes up with a PR for flatc to generated this stuff automatically.
    """

    @classmethod
    def GetRootAsPaymentChannel(cls, buf, offset):
        n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
        x = _PaymentChannelGen()
        x.Init(buf, n + offset)
        return x

    def ChannelAsBytes(self):
        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
        if o != 0:
            _off = self._tab.Vector(o)
            _len = self._tab.VectorLen(o)
            return memoryview(self._tab.Bytes)[_off:_off+_len]
        return None

    def MarketAsBytes(self):
        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
        if o != 0:
            _off = self._tab.Vector(o)
            _len = self._tab.VectorLen(o)
            return memoryview(self._tab.Bytes)[_off:_off+_len]
        return None

    def SenderAsBytes(self):
        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
        if o != 0:
            _off = self._tab.Vector(o)
            _len = self._tab.VectorLen(o)
            return memoryview(self._tab.Bytes)[_off:_off+_len]
        return None

    def DelegateAsBytes(self):
        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
        if o != 0:
            _off = self._tab.Vector(o)
            _len = self._tab.VectorLen(o)
            return memoryview(self._tab.Bytes)[_off:_off+_len]
        return None

    def RecipientAsBytes(self):
        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
        if o != 0:
            _off = self._tab.Vector(o)
            _len = self._tab.VectorLen(o)
            return memoryview(self._tab.Bytes)[_off:_off+_len]
        return None

    def AmountAsBytes(self):
        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14))
        if o != 0:
            _off = self._tab.Vector(o)
            _len = self._tab.VectorLen(o)
            return memoryview(self._tab.Bytes)[_off:_off+_len]
        return None

    def OpenAtAsBytes(self):
        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20))
        if o != 0:
            _off = self._tab.Vector(o)
            _len = self._tab.VectorLen(o)
            return memoryview(self._tab.Bytes)[_off:_off+_len]
        return None

    def ClosingAtAsBytes(self):
        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22))
        if o != 0:
            _off = self._tab.Vector(o)
            _len = self._tab.VectorLen(o)
            return memoryview(self._tab.Bytes)[_off:_off+_len]
        return None

    def ClosedAtAsBytes(self):
        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24))
        if o != 0:
            _off = self._tab.Vector(o)
            _len = self._tab.VectorLen(o)
            return memoryview(self._tab.Bytes)[_off:_off+_len]
        return None


class PaymentChannel(object):
    """
    ``XBRPaymentChannel`` record/event database object.
    """

    def __init__(self, from_fbs=None):
        self._from_fbs = from_fbs

        self._channel = None
        self._market = None
        self._sender = None
        self._delegate = None
        self._recipient = None
        self._amount = None
        self._timeout = None
        self._state = None
        self._open_at = None
        self._closing_at = None
        self._closed_at = None

    def marshal(self):
        obj = {
            'channel': self._channel,
            'market': self._market,
            'sender': self._sender,
            'delegate': self._delegate,
            'recipient': self._recipient,
            'amount': self._amount,
            'timeout': self._timeout,
            'state': self._state,
            'open_at': self._open_at,
            'closing_at': self._closing_at,
            'closed_at': self._closed_at,
        }
        return obj

    def __str__(self):
        return '\n{}\n'.format(pprint.pformat(self.marshal()))

    @property
    def channel(self):
        """
        The UUID of the payment channel.

        :return: The UUID (16 bytes).
        :rtype: bytes
        """
        if self._channel is None and self._from_fbs:
            if self._from_fbs.ChannelLength():
                self._channel = self._from_fbs.ChannelAsBytes()
        return self._channel

    @channel.setter
    def channel(self, value):
        assert value is None or type(value) == bytes
        self._channel = value

    @property
    def market(self):
        if self._market is None and self._from_fbs:
            if self._from_fbs.MarketLength():
                self._market = self._from_fbs.MarketAsBytes()
        return self._market

    @market.setter
    def market(self, value):
        assert value is None or type(value) == bytes
        self._market = value

    @property
    def sender(self):
        if self._sender is None and self._from_fbs:
            if self._from_fbs.SenderLength():
                self._sender = self._from_fbs.SenderAsBytes()
        return self._sender

    @sender.setter
    def sender(self, value):
        assert value is None or type(value) == bytes
        self._sender = value

    @property
    def delegate(self):
        if self._delegate is None and self._from_fbs:
            if self._from_fbs.DelegateLength():
                self._delegate = self._from_fbs.DelegateAsBytes()
        return self._delegate

    @delegate.setter
    def delegate(self, value):
        assert value is None or type(value) == bytes
        self._delegate = value

    @property
    def recipient(self):
        if self._recipient is None and self._from_fbs:
            if self._from_fbs.RecipientLength():
                self._recipient = self._from_fbs.RecipientAsBytes()
        return self._recipient

    @recipient.setter
    def recipient(self, value):
        assert value is None or type(value) == bytes
        self._recipient = value

    @property
    def amount(self):
        if self._amount is None and self._from_fbs:
            if self._from_fbs.AmountLength():
                _amount = self._from_fbs.AmountAsBytes()
                self._amount = unpack_uint256(bytes(_amount))
        return self._amount

    @amount.setter
    def amount(self, value):
        assert value is None or type(value) == int
        self._amount = value

    @property
    def timeout(self):
        if self._timeout is None and self._from_fbs:
            self._timeout = self._from_fbs.Timeout()
        return self._timeout

    @timeout.setter
    def timeout(self, value):
        assert type(value) == int
        self._timeout = value

    @property
    def state(self):
        if self._state is None and self._from_fbs:
            self._state = self._from_fbs.State()
        return self._state

    @state.setter
    def state(self, value):
        assert type(value) == int
        self._state = value

    @property
    def open_at(self):
        if self._open_at is None and self._from_fbs:
            if self._from_fbs.OpenAtLength():
                _open_at = self._from_fbs.OpenAtAsBytes()
                self._open_at = unpack_uint256(bytes(_open_at))
        return self._open_at

    @open_at.setter
    def open_at(self, value):
        assert value is None or type(value) == int
        self._open_at = value

    @property
    def closing_at(self):
        if self._closing_at is None and self._from_fbs:
            if self._from_fbs.ClosingAtLength():
                _closing_at = self._from_fbs.ClosingAtAsBytes()
                self._closing_at = unpack_uint256(bytes(_closing_at))
        return self._closing_at

    @closing_at.setter
    def closing_at(self, value):
        assert value is None or type(value) == int
        self._closing_at = value

    @property
    def closed_at(self):
        if self._closed_at is None and self._from_fbs:
            if self._from_fbs.ClosedAtLength():
                _closed_at = self._from_fbs.ClosedAtAsBytes()
                self._closed_at = unpack_uint256(bytes(_closed_at))
        return self._closed_at

    @closed_at.setter
    def closed_at(self, value):
        assert value is None or type(value) == int
        self._closed_at = value

    @staticmethod
    def cast(buf):
        return PaymentChannel(_PaymentChannelGen.GetRootAsPaymentChannel(buf, 0))

    def build(self, builder):

        channel = self.channel
        if channel:
            channel = builder.CreateString(channel)

        market = self.market
        if market:
            market = builder.CreateString(market)

        sender = self.sender
        if sender:
            sender = builder.CreateString(sender)

        delegate = self.delegate
        if delegate:
            delegate = builder.CreateString(delegate)

        recipient = self.recipient
        if recipient:
            recipient = builder.CreateString(recipient)

        amount = self.amount
        if amount:
            amount = builder.CreateString(pack_uint256(amount))

        open_at = self.open_at
        if open_at:
            open_at = builder.CreateString(pack_uint256(open_at))

        closing_at = self.closing_at
        if closing_at:
            closing_at = builder.CreateString(pack_uint256(closing_at))

        closed_at = self.closed_at
        if closed_at:
            closed_at = builder.CreateString(pack_uint256(closed_at))

        PaymentChannelGen.PaymentChannelStart(builder)

        if channel:
            PaymentChannelGen.PaymentChannelAddChannel(builder, channel)

        if market:
            PaymentChannelGen.PaymentChannelAddMarket(builder, market)

        if sender:
            PaymentChannelGen.PaymentChannelAddSender(builder, sender)

        if delegate:
            PaymentChannelGen.PaymentChannelAddDelegate(builder, delegate)

        if recipient:
            PaymentChannelGen.PaymentChannelAddRecipient(builder, recipient)

        if amount:
            PaymentChannelGen.PaymentChannelAddAmount(builder, amount)

        PaymentChannelGen.PaymentChannelAddTimeout(builder, self.timeout)
        PaymentChannelGen.PaymentChannelAddState(builder, self.state)

        if open_at:
            PaymentChannelGen.PaymentChannelAddOpenAt(builder, open_at)

        if closing_at:
            PaymentChannelGen.PaymentChannelAddClosingAt(builder, closing_at)

        if closed_at:
            PaymentChannelGen.PaymentChannelAddClosedAt(builder, closed_at)

        final = PaymentChannelGen.PaymentChannelEnd(builder)

        return final


@table('b3d01946-85ae-49f3-ad96-b78194eb82fe', build=PaymentChannel.build, cast=PaymentChannel.cast)
class PaymentChannels(MapBytes32FlatBuffers):
    """
    XBR payment channels.

    Map :class:`zlmdb.MapBytes32FlatBuffers` from ``channel_id`` to :class:`crossbarfx.cfxdb.xbr.PaymentChannel`
    """


class Schema(object):
    """
    CFC edge database schema for ZLMDB.
    """

    def __init__(self, db):
        self.db = db

    # token_transfers: TokenTransfer
    token_transfers = None
    """
    Token transfers archive.
    """

    # payment_channels: PaymentChannel
    payment_channels = None
    """
    Payment channels.
    """

    @staticmethod
    def attach(db):
        """
        Factory to create a schema from attaching to a database. The schema tables
        will be automatically mapped as persistant maps and attached to the
        database slots.

        :param db: zlmdb.Database
        :return: object of Schema
        """
        print('Attaching database schema to database {db} ..'.format(db=db))

        schema = Schema(db)

        schema.token_transfers = db.attach_table(TokenTransfers)
        schema.payment_channels = db.attach_table(PaymentChannels)

        print('Successfully attached schema {schema} to database {db}.'.format(schema=schema, db=db))

        return schema
