from abc import ABC
from ..utils import Address
from ..boc import Cell


class Contract(ABC):
    def __init__(self, **kwargs):
        self.options = kwargs
        self._address = Address(
            kwargs["address"]) if "address" in kwargs else None
        if "wc" not in kwargs:
            kwargs["wc"] = self._address.wc if self._address is not None else 0

    @property
    def address(self):
        if self._address is None:
            self._address = self.create_state_init()["address"]

        return self._address

    def create_state_init(self):
        code_cell = self.create_code_cell()
        data_cell = self.create_data_cell()
        state_init = self.__create_state_init(code_cell, data_cell)
        state_init_hash = state_init.bytes_hash()

        address = Address(
            str(self.options["wc"]) + ":" + state_init_hash.hex())

        return {
            "code": code_cell,
            "data": data_cell,
            "address": address,
            "state_init": state_init,
        }

    def create_code_cell(self):
        if "code" not in self.options or self.options["code"] is None:
            raise Exception("Contract: options.code is not defined")
        return self.options["code"]

    def create_data_cell(self):
        return Cell()

    @classmethod
    def create_external_message_header(cls, dest, src=None, import_fee=0):
        message = Cell()
        message.bits.write_uint(2, 2)
        message.bits.write_address(Address(src) if src else None)
        message.bits.write_address(Address(dest))
        message.bits.write_grams(import_fee)
        return message

    @classmethod
    def create_internal_message_header(cls, dest, grams=0, ihr_disabled=True,
                                       bounce=None, bounced=False, src=None,
                                       currency_collection=None, ihr_fees=0,
                                       fwd_fees=0, created_lt=0, created_at=0):
        message = Cell()
        message.bits.write_bit(0)
        message.bits.write_bit(ihr_disabled)

        if bounce is not None:
            message.bits.write_bit(bounce)
        else:
            message.bits.write_bit(Address(dest).is_bounceable)
        message.bits.write_bit(bounced)
        message.bits.write_address(Address(src) if src else None)
        message.bits.write_address(Address(dest))
        message.bits.write_grams(grams)
        if currency_collection:
            raise Exception("Currency collections are not implemented yet")

        message.bits.write_bit(bool(currency_collection))
        message.bits.write_grams(ihr_fees)
        message.bits.write_grams(fwd_fees)
        message.bits.write_uint(created_lt, 64)
        message.bits.write_uint(created_at, 32)
        return message

    @classmethod
    def create_common_msg_info(cls, header, state_init=None, body=None):
        common_msg_info = Cell()
        common_msg_info.write_cell(header)
        if state_init:
            common_msg_info.bits.write_bit(1)
            if common_msg_info.bits.get_free_bits() - 1 >= state_init.bits.get_used_bits():
                common_msg_info.bits.write_bit(0)
                common_msg_info.write_cell(state_init)
            else:
                common_msg_info.bits.write_bit(1)
                common_msg_info.write_cell(state_init)
        else:
            common_msg_info.bits.write_bit(0)

        if body:
            if common_msg_info.bits.get_free_bits() >= body.bits.get_used_bits():
                common_msg_info.bits.write_bit(0)
                common_msg_info.write_cell(body)
            else:
                common_msg_info.bits.write_bit(1)
                common_msg_info.write_cell(body)
        else:
            common_msg_info.bits.write_bit(0)

        return common_msg_info

    def __create_state_init(self, code, data, library=None, split_depth=None, ticktock=None):
        if library or split_depth or ticktock:
            raise Exception(
                "Library/SplitDepth/Ticktock in state init is not implemented")

        state_init = Cell()
        settings = bytes(''.join(['1' if i else '0' for i in [bool(split_depth), bool(
            ticktock), bool(code), bool(data), bool(library)]]), 'utf-8')
        state_init.bits.write_bit_array(settings)

        if code:
            state_init.refs.append(code)
        if data:
            state_init.refs.append(data)
        if library:
            state_init.refs.append(library)
        return state_init
