"""This nodule defines helper functions to deal with native calls."""

import hashlib
import logging
from typing import List, Union

from ethereum.utils import ecrecover_to_pub
from py_ecc.secp256k1 import N as secp256k1n
import py_ecc.optimized_bn128 as bn128
from rlp.utils import ALL_BYTES

from mythril.laser.ethereum.state.calldata import BaseCalldata, ConcreteCalldata
from mythril.laser.ethereum.util import extract_copy, extract32
from ethereum.utils import (
    sha3,
    big_endian_to_int,
    safe_ord,
    zpad,
    int_to_big_endian,
    encode_int32,
)
from ethereum.specials import validate_point

log = logging.getLogger(__name__)


class NativeContractException(Exception):
    """An exception denoting an error during a native call."""

    pass


def ecrecover(data: List[int]) -> List[int]:
    """

    :param data:
    :return:
    """
    # TODO: Add type hints
    try:
        bytes_data = bytearray(data)
        v = extract32(bytes_data, 32)
        r = extract32(bytes_data, 64)
        s = extract32(bytes_data, 96)
    except TypeError:
        raise NativeContractException

    message = b"".join([ALL_BYTES[x] for x in bytes_data[0:32]])
    if r >= secp256k1n or s >= secp256k1n or v < 27 or v > 28:
        return []
    try:
        pub = ecrecover_to_pub(message, v, r, s)
    except Exception as e:
        log.debug("An error has occured while extracting public key: " + str(e))
        return []
    o = [0] * 12 + [x for x in sha3(pub)[-20:]]
    return list(bytearray(o))


def sha256(data: List[int]) -> List[int]:
    """

    :param data:
    :return:
    """
    try:
        bytes_data = bytes(data)
    except TypeError:
        raise NativeContractException
    return list(bytearray(hashlib.sha256(bytes_data).digest()))


def ripemd160(data: List[int]) -> List[int]:
    """

    :param data:
    :return:
    """
    try:
        bytes_data = bytes(data)
    except TypeError:
        raise NativeContractException
    digest = hashlib.new("ripemd160", bytes_data).digest()
    padded = 12 * [0] + list(digest)
    return list(bytearray(bytes(padded)))


def identity(data: List[int]) -> List[int]:
    """

    :param data:
    :return:
    """
    # Group up into an array of 32 byte words instead
    # of an array of bytes. If saved to memory, 32 byte
    # words are currently needed, but a correct memory
    # implementation would be byte indexed for the most
    # part.
    return data


def mod_exp(data: List[int]) -> List[int]:
    """
    TODO: Some symbolic parts can be handled here
    Modular Exponentiation
    :param data: Data with <length_of_BASE> <length_of_EXPONENT> <length_of_MODULUS> <BASE> <EXPONENT> <MODULUS>
    :return: modular exponentiation
    """
    bytes_data = bytearray(data)
    baselen = extract32(bytes_data, 0)
    explen = extract32(bytes_data, 32)
    modlen = extract32(bytes_data, 64)
    if baselen == 0:
        return [0] * modlen
    if modlen == 0:
        return []

    first_exp_bytes = extract32(bytes_data, 96 + baselen) >> (8 * max(32 - explen, 0))
    while first_exp_bytes:
        first_exp_bytes >>= 1

    base = bytearray(baselen)
    extract_copy(bytes_data, base, 0, 96, baselen)
    exp = bytearray(explen)
    extract_copy(bytes_data, exp, 0, 96 + baselen, explen)
    mod = bytearray(modlen)
    extract_copy(bytes_data, mod, 0, 96 + baselen + explen, modlen)