from pyflo.functions.hash import double_sha256
from pyflo.functions.tools import bytes_from_hex

b58_digits = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
base32charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
base32charset_upcase = "QPZRY9X8GF2TVDW0S3JN54KHCE6MUA7L"

int_base32_map = dict()
base32_int_map = dict()

for n, i in enumerate(base32charset):
    int_base32_map[i] = n
    base32_int_map[n] = ord(i)
for n, i in enumerate(base32charset_upcase):
    int_base32_map[i] = n


def rebasebits(data, frombits, tobits, pad=True):
    """General power-of-2 base conversion."""
    acc = 0
    bits = 0
    ret = bytearray()
    append = ret.append
    maxv = (1 << tobits) - 1
    max_acc = (1 << (frombits + tobits - 1)) - 1
    for value in data:
        if value < 0 or (value >> frombits):
            raise ValueError("invalid bytes")
        acc = ((acc << frombits) | value) & max_acc
        bits += frombits
        while bits >= tobits:
            bits -= tobits
            append((acc >> bits) & maxv)
    if pad:
        if bits:
            append((acc << (tobits - bits)) & maxv)
    elif bits >= frombits or ((acc << (tobits - bits)) & maxv):
        raise ValueError("invalid padding")
    return ret


def rebase_5_to_8(data, pad = True):
    return rebasebits(data, 5, 8, pad)


def rebase_8_to_5(data, pad = True):
    return rebasebits(data, 8, 5, pad)


def rebase_32_to_5(data):
    if isinstance(data, bytes):
        data = data.decode()
    b = bytearray()
    append = b.append
    try:
        [append(int_base32_map[i]) for i in data]
    except:
        raise Exception("Non base32 characters")
    return b


def rebase_5_to_32(data, bytes = True):
    r = bytearray()
    append = r.append
    [append(base32_int_map[i]) for i in data]
    return r.decode() if not bytes else r


def bech32_polymod(values):
    """Internal function that computes the Bech32 checksum."""
    generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
    chk = 1
    for value in values:
        top = chk >> 25
        chk = (chk & 0x1ffffff) << 5 ^ value
        for i in range(5):
            chk ^= generator[i] if ((top >> i) & 1) else 0
    return chk ^ 1


def encode_base58(b):
    """Encode bytes to a base58-encoded string"""
    # Convert big-endian bytes to integer

    n= int('0x0%s' % b.hex(), 16)

    # Divide that integer into bas58
    res = []
    append = res.append
    while n > 0:
        n, r = divmod(n, 58)
        append(b58_digits[r])
    res = ''.join(res[::-1])
    # Encode leading zeros as base58 zeros
    czero = 0
    pad = 0
    for c in b:
        if c == czero:
            pad += 1
        else:
            break
    return b58_digits[0] * pad + res


def decode_base58(s):
    """Decode a base58-encoding string, returning bytes"""
    if not s:
        return b''
    # Convert the string to an integer
    n = 0
    for c in s:
        n *= 58
        if c not in b58_digits:
            raise Exception('Character %r is not a valid base58 character' % c)
        digit = b58_digits.index(c)
        n += digit
    # Convert the integer to bytes
    h = '%x' % n
    if len(h) % 2:
        h = '0%s' % h
    res = bytes_from_hex(h)
    # Add padding back.
    pad = 0
    for c in s[:-1]:
        if c == b58_digits[0]:
            pad += 1
        else:
            break
    return b''.join((b'\x00' * pad, res))


def encode_base58_with_checksum(b):
    return encode_base58(b"%s%s" % (b, double_sha256(b)[:4]))


def decode_base58_with_checksum(s):
    b = decode_base58(s)
    if double_sha256(b[:-4])[:4] != b[-4:]:
        raise Exception("invalid checksum")
    return b[:-4]
