import base64


bounceable_tag, non_bounceable_tag = b'\x11', b'\x51'
b64_abc = set(
    'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890+/')
b64_abc_urlsafe = set(
    'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_-')


def is_int(x):
    try:
        int(x)
        return True
    except:
        return False


def is_hex(x):
    try:
        int(x, 16)
        return True
    except:
        return False


def calcCRC(message):
    poly = 0x1021
    reg = 0
    message += b'\x00\x00'
    for byte in message:
        mask = 0x80
        while(mask > 0):
            reg <<= 1
            if byte & mask:
                reg += 1
            mask >>= 1
            if reg > 0xffff:
                reg &= 0xffff
                reg ^= poly
    return reg.to_bytes(2, "big")


def account_forms(raw_form, test_only=False):
    workchain, address = raw_form.split(":")
    workchain, address = int(workchain), int(address, 16)
    address = address.to_bytes(32, "big")
    workchain_tag = b'\xff' if workchain == - \
        1 else workchain.to_bytes(1, "big")
    btag = bounceable_tag
    nbtag = non_bounceable_tag
    # if test_only:
    #  btag = (btag[0] | 0x80).to_bytes(1,'big')
    #  nbtag = (nbtag[0] | 0x80).to_bytes(1,'big')
    preaddr_b = btag + workchain_tag + address
    preaddr_u = nbtag + workchain_tag + address
    b64_b = base64.b64encode(preaddr_b+calcCRC(preaddr_b)).decode('utf8')
    b64_u = base64.b64encode(preaddr_u+calcCRC(preaddr_u)).decode('utf8')
    b64_b_us = base64.urlsafe_b64encode(
        preaddr_b+calcCRC(preaddr_b)).decode('utf8')
    b64_u_us = base64.urlsafe_b64encode(
        preaddr_u+calcCRC(preaddr_u)).decode('utf8')
    return {'raw_form': raw_form,
            'bounceable': {'b64': b64_b, 'b64url': b64_b_us},
            'non_bounceable': {'b64': b64_u, 'b64url': b64_u_us},
            'given_type': 'raw_form',
            'test_only': test_only}


def read_friendly_address(address):
    urlsafe = False
    if set(address).issubset(b64_abc):
        address_bytes = base64.b64decode(address.encode('utf8'))
    elif set(address).issubset(b64_abc_urlsafe):
        urlsafe = True
        address_bytes = base64.urlsafe_b64decode(address.encode('utf8'))
    else:
        raise Exception("Not an address")
    if not calcCRC(address_bytes[:-2]) == address_bytes[-2:]:
        raise Exception("Wrong checksum")
    tag = address_bytes[0]
    if tag & 0x80:
        test_only = True
        tag = tag ^ 0x80
    else:
        test_only = False
    tag = tag.to_bytes(1, 'big')
    if tag == bounceable_tag:
        bounceable = True
    elif tag == non_bounceable_tag:
        bounceable = False
    else:
        raise Exception("Unknown tag")
    if address_bytes[1:2] == b'\xff':
        workchain = -1
    else:
        workchain = address_bytes[1]
    hx = hex(int.from_bytes(address_bytes[2:-2], "big"))[2:]
    hx = (64-len(hx))*"0"+hx
    raw_form = str(workchain)+":"+hx
    account = account_forms(raw_form, test_only)
    account['given_type'] = "friendly_" + \
        ("bounceable" if bounceable else "non_bounceable")
    return account


def detect_address(unknown_form):
    if is_hex(unknown_form):
        return account_forms("-1:"+unknown_form)
    elif (":" in unknown_form) and is_int(unknown_form.split(":")[0]) and is_hex(unknown_form.split(":")[1]):
        return account_forms(unknown_form)
    else:
        return read_friendly_address(unknown_form)


def prepare_address(unknown_form):
    address = detect_address(unknown_form)
    if 'non_bounceable' in address['given_type']:
        return address["non_bounceable"]["b64"]
    return address["bounceable"]["b64"]


def address_state(account_info):
    if isinstance(account_info.get("code", ""), int) or len(account_info.get("code", "")) == 0:
        if len(account_info.get("frozen_hash", "")) == 0:
            return "uninitialized"
        else:
            return "frozen"
    return "active"
