from pyflo.functions.tools import s2rh, bytes_from_hex, int_from_bytes
from pyflo.functions.hash import double_sha256

def merkle_root(tx_hash_list, hex=True):
    """
    Calculate merkle root from transaction hash list

    :param tx_hash_list: list of transaction hashes in bytes or HEX encoded string.
    :param hex:  (optional) If set to True return result in HEX format, by default is True.
    :return: merkle root in bytes or HEX encoded string corresponding hex flag.
    """
    tx_hash_list = [h if isinstance(h, bytes) else s2rh(h) for h in tx_hash_list]
    if len(tx_hash_list) == 1:
        return tx_hash_list[0]
    while True:
        new_hash_list = list()
        append = new_hash_list.append
        while tx_hash_list:
            h1 = tx_hash_list.pop(0)
            try:
                h2 = tx_hash_list.pop(0)
            except:
                h2 = h1
            append(double_sha256(h1 + h2))
        if len(new_hash_list) > 1:
            tx_hash_list = new_hash_list
        else:
            return new_hash_list[0] if not hex else new_hash_list[0].hex()


def merkle_branches(tx_hash_list, hex=True):
    """
    Calculate merkle branches for coinbase transacton

    :param tx_hash_list: list of transaction hashes in bytes or HEX encoded string.
    :param hex:  (optional) If set to True return result in HEX format, by default is True.
    :return: list of merkle branches in bytes or HEX encoded string corresponding hex flag.
    """
    tx_hash_list = [h if isinstance(h, bytes) else s2rh(h) for h in tx_hash_list]
    branches = []
    if len(tx_hash_list) == 1:
        return []
    tx_hash_list.pop(0)
    branches_append = branches.append
    while True:
        branches_append(tx_hash_list.pop(0))
        new_hash_list = list()
        new_hash_list_append = new_hash_list.append
        while tx_hash_list:
            h1 = tx_hash_list.pop(0)
            try:
                h2 = tx_hash_list.pop(0)
            except:
                h2 = h1
            new_hash_list_append(double_sha256(h1 + h2))
        if len(new_hash_list) > 1:
            tx_hash_list = new_hash_list
        else:
            if new_hash_list:
                branches_append(new_hash_list.pop(0))
            return branches if not hex else [h.hex() for h in branches]


def merkleroot_from_branches(merkle_branches, coinbase_hash, hex=True):
    """
    Calculate merkle root from merkle branches and coinbase transacton hash

    :param merkle_branches: list merkle branches in bytes or HEX encoded string.
    :param coinbase_hash: list coinbase transaction hash in bytes or HEX encoded string.
    :param hex:  (optional) If set to True return result in HEX format, by default is True.
    :return: merkle root in bytes or HEX encoded string corresponding hex flag.
    """
    merkle_root = coinbase_hash if not isinstance(coinbase_hash, str) else bytes_from_hex(coinbase_hash)
    for h in merkle_branches:
        if type(h) == str:
            h = bytes_from_hex(h)
        merkle_root = double_sha256(merkle_root + h)
    return merkle_root if not hex else merkle_root.hex()


#  Difficulty


def bits_to_target(bits):
    """
    Calculate target from bits

    :param bits: HEX string, bytes string or integer representation of bits.
    :return: integer.
    """
    if type(bits) == str:
        bits = bytes_from_hex(bits)
    if type(bits) == bytes:
        return int_from_bytes(bits[1:], 'big') * (2 ** (8 * (bits[0] - 3)))
    else:
        shift = bits >> 24
        target = (bits & 0xffffff) * (1 << (8 * (shift - 3)))
        return target


def target_to_difficulty(target):
    """
    Calculate difficulty from target

    :param target: integer.
    :return: float.
    """
    return 0x00000000FFFF0000000000000000000000000000000000000000000000000000 / target


def bits_to_difficulty(bits):
    """
    Calculate difficulty from bits

    :param bits: HEX string, bytes string or integer representation of bits.
    :return: integer.
    """
    return target_to_difficulty(bits_to_target(bits))


def difficulty_to_target(difficulty):
    """
    Calculate target from difficulty

    :param target: integer.
    :return: float.
    """
    return int(0x00000000FFFF0000000000000000000000000000000000000000000000000000 / difficulty)


