from decimal import (
    Decimal,
)
import hashlib

from vyper import (
    ast as vy_ast,
)
from vyper.exceptions import (
    CompilerPanic,
    ConstancyViolation,
    InvalidLiteral,
    StructureException,
    TypeMismatch,
)
from vyper.opcodes import (
    version_check,
)
from vyper.parser.expr import (
    Expr,
)
from vyper.parser.keccak256_helper import (
    keccak256_helper,
)
from vyper.parser.parser_utils import (
    LLLnode,
    add_variable_offset,
    byte_array_to_num,
    get_length,
    get_number_as_fraction,
    getpos,
    make_byte_array_copier,
    make_byte_slice_copier,
    unwrap_location,
)
from vyper.signatures.function_signature import (
    VariableRecord,
)
from vyper.types import (
    BaseType,
    ByteArrayLike,
    ByteArrayType,
    ListType,
    StringType,
    TupleType,
    are_units_compatible,
    get_size_of_type,
    is_base_type,
)
from vyper.types.convert import (
    convert,
)
from vyper.utils import (
    DECIMAL_DIVISOR,
    RLP_DECODER_ADDRESS,
    MemoryPositions,
    SizeLimits,
    bytes_to_int,
    fourbytes_to_int,
    keccak256,
)

from .signatures import (
    Optional,
    signature,
)

SHA256_ADDRESS = 2
SHA256_BASE_GAS = 60
SHA256_PER_WORD_GAS = 12


def enforce_units(typ, obj, expected):
    if not are_units_compatible(typ, expected):
        raise TypeMismatch("Invalid units", obj)


def get_keyword(expr, keyword):
    for kw in expr.keywords:
        if kw.arg == keyword:
            return kw.value
    # This should never happen, as kwargs['value'] will KeyError first.
    # Leaving exception for other use cases.
    raise Exception(f"Keyword {keyword} not found")  # pragma: no cover

# like `assert foo`, but doesn't check constancy.
# currently no option for reason string (easy to add, just need to refactor
# vyper.parser.stmt so we can use _assert_reason).
@signature('bool')
def assert_modifiable(expr, args, kwargs, context):
    # cf. vyper.parser.stmt.parse_assert
    return LLLnode.from_list(['assert', args[0]], typ=None, pos=getpos(expr))


@signature('decimal')
def floor(expr, args, kwargs, context):
    return LLLnode.from_list(
        [
            'if',
            ['slt', args[0], 0],
            ['sdiv', ['sub', args[0], DECIMAL_DIVISOR - 1], DECIMAL_DIVISOR],
            ['sdiv', args[0], DECIMAL_DIVISOR]
        ],
        typ=BaseType('int128', args[0].typ.unit, args[0].typ.positional),
        pos=getpos(expr)
    )


@signature('decimal')
def ceil(expr, args, kwards, context):
    return LLLnode.from_list(
        [
            'if',
            ['slt', args[0], 0],
            ['sdiv', args[0], DECIMAL_DIVISOR],
            ['sdiv', ['add', args[0], DECIMAL_DIVISOR - 1], DECIMAL_DIVISOR]
        ],
        typ=BaseType('int128', args[0].typ.unit, args[0].typ.positional),
        pos=getpos(expr)
    )


@signature(('uint256', 'int128', 'decimal'))
def as_unitless_number(expr, args, kwargs, context):
    return LLLnode(
        value=args[0].value,
        args=args[0].args,
        typ=BaseType(args[0].typ.typ, {}),
        pos=getpos(expr),
    )


def _convert(expr, context):
    return convert(expr, context)


@signature(('bytes32', 'bytes', 'string'), 'int128', 'int128')
def _slice(expr, args, kwargs, context):

    sub, start, length = args
    if not are_units_compatible(start.typ, BaseType('int128')):
        raise TypeMismatch("Type for slice start index must be a unitless number", expr)
    # Expression representing the length of the slice
    if not are_units_compatible(length.typ, BaseType('int128')):
        raise TypeMismatch("Type for slice length must be a unitless number", expr)

    if is_base_type(sub.typ, 'bytes32'):
        if (start.typ.is_literal and length.typ.is_literal) and \
           not (0 <= start.value + length.value <= 32):
            raise InvalidLiteral(
                'Invalid start / length values needs to be between 0 and 32.',
                expr,
            )
        sub_typ_maxlen = 32
    else:
        sub_typ_maxlen = sub.typ.maxlen

    # Get returntype string or bytes
    if isinstance(args[0].typ, ByteArrayType) or is_base_type(sub.typ, 'bytes32'):
        ReturnType = ByteArrayType
    else:
        ReturnType = StringType

    # Node representing the position of the output in memory
    np = context.new_placeholder(ReturnType(maxlen=sub_typ_maxlen + 32))
    placeholder_node = LLLnode.from_list(np, typ=sub.typ, location='memory')
    placeholder_plus_32_node = LLLnode.from_list(np + 32, typ=sub.typ, location='memory')
    # Copies over bytearray data
    if sub.location == 'storage':
        adj_sub = LLLnode.from_list(
            ['add', ['sha3_32', sub], ['add', ['div', '_start', 32], 1]],
            typ=sub.typ,
            location=sub.location,
        )
    else:
        adj_sub = LLLnode.from_list(
            ['add', sub, ['add', ['sub', '_start', ['mod', '_start', 32]], 32]],
            typ=sub.typ,
            location=sub.location,
        )

    if is_base_type(sub.typ, 'bytes32'):
        adj_sub = LLLnode.from_list(
            sub.args[0], typ=sub.typ, location="memory"
        )

    copier = make_byte_slice_copier(
        placeholder_plus_32_node,
        adj_sub,
        ['add', '_length', 32],
        sub_typ_maxlen,
        pos=getpos(expr),
    )
    # New maximum length in the type of the result
    newmaxlen = length.value if not len(length.args) else sub_typ_maxlen
    if is_base_type(sub.typ, 'bytes32'):
        maxlen = 32
    else:
        maxlen = ['mload', Expr(sub, context=context).lll_node]  # Retrieve length of the bytes.

    out = [
        'with', '_start', start, [
            'with', '_length', length, [
                'with', '_opos', ['add', placeholder_node, ['mod', '_start', 32]], [
                    'seq',
                    ['assert', ['le', ['add', '_start', '_length'], maxlen]],
                    copier,
                    ['mstore', '_opos', '_length'],
                    '_opos'
                ],
            ],
        ],
    ]
    return LLLnode.from_list(out, typ=ReturnType(newmaxlen), location='memory', pos=getpos(expr))


@signature(('bytes', 'string'))
def _len(expr, args, kwargs, context):
    return get_length(args[0])


def concat(expr, context):
    args = [Expr(arg, context).lll_node for arg in expr.args]
    if len(args) < 2:
        raise StructureException("Concat expects at least two arguments", expr)

    prev_type = ''
    for _, (expr_arg, arg) in enumerate(zip(expr.args, args)):
        if not isinstance(arg.typ, ByteArrayLike) and not is_base_type(arg.typ, 'bytes32'):
            raise TypeMismatch("Concat expects string, bytes or bytes32 objects", expr_arg)

        current_type = (
            'bytes'
            if isinstance(arg.typ, ByteArrayType) or is_base_type(arg.typ, 'bytes32')
            else 'string'
        )
        if prev_type and current_type != prev_type:
            raise TypeMismatch(
                (
                    "Concat expects consistant use of string or byte types, "
                    "user either bytes or string."
                ),
                expr_arg,
            )
        prev_type = current_type

    if current_type == 'string':
        ReturnType = StringType
    else:
        ReturnType = ByteArrayType

    # Maximum length of the output
    total_maxlen = sum([
        arg.typ.maxlen if isinstance(arg.typ, ByteArrayLike) else 32 for arg in args
    ])
    # Node representing the position of the output in memory
    placeholder = context.new_placeholder(ReturnType(total_maxlen))
    # Object representing the output
    seq = []
    # For each argument we are concatenating...
    for arg in args:
        # Start pasting into a position the starts at zero, and keeps
        # incrementing as we concatenate arguments
        placeholder_node = LLLnode.from_list(
            ['add', placeholder, '_poz'],
            typ=ReturnType(total_maxlen),
            location='memory',
        )
        placeholder_node_plus_32 = LLLnode.from_list(
            ['add', ['add', placeholder, '_poz'], 32],
            typ=ReturnType(total_maxlen),
            location='memory',
        )
        if isinstance(arg.typ, ReturnType):
            # Ignore empty strings
            if arg.typ.maxlen == 0:
                continue
            # Get the length of the current argument
            if arg.location == "memory":
                length = LLLnode.from_list(['mload', '_arg'], typ=BaseType('int128'))
                argstart = LLLnode.from_list(
                    ['add', '_arg', 32],
                    typ=arg.typ,
                    location=arg.location,
                )
            elif arg.location == "storage":
                length = LLLnode.from_list(['sload', ['sha3_32', '_arg']], typ=BaseType('int128'))
                argstart = LLLnode.from_list(
                    ['add', ['sha3_32', '_arg'], 1],
                    typ=arg.typ,
                    location=arg.location,
                )
            # Make a copier to copy over data from that argument
            seq.append([
                'with', '_arg', arg, [
                    'seq',
                    make_byte_slice_copier(
                        placeholder_node_plus_32,
                        argstart,
                        length,
                        arg.typ.maxlen, pos=getpos(expr),
                    ),
                    # Change the position to start at the correct
                    # place to paste the next value
                    ['set', '_poz', ['add', '_poz', length]],
                ],
            ])
        else:
            seq.append([
                'seq',
                ['mstore', ['add', placeholder_node, 32], unwrap_location(arg)],
                ['set', '_poz', ['add', '_poz', 32]],
            ])
    # The position, after all arguments are processing, equals the total
    # length. Paste this in to make the output a proper bytearray
    seq.append(['mstore', placeholder, '_poz'])
    # Memory location of the output
    seq.append(placeholder)
    return LLLnode.from_list(
        ['with', '_poz', 0, ['seq'] + seq],
        typ=ReturnType(total_maxlen),
        location='memory',
        pos=getpos(expr),
        annotation='concat',
    )


@signature(('bytes_literal', 'str_literal', 'bytes', 'string', 'bytes32'))
def _sha3(expr, args, kwargs, context):
    raise StructureException("sha3 function has been deprecated in favor of keccak256")


@signature(('bytes_literal', 'str_literal', 'bytes', 'string', 'bytes32'))
def _keccak256(expr, args, kwargs, context):
    return keccak256_helper(expr, args, kwargs, context)


def _make_sha256_call(inp_start, inp_len, out_start, out_len):
    return [
        'assert', [
            'call',
            ['gas'],  # gas
            SHA256_ADDRESS,  # address
            0,  # value
            inp_start,
            inp_len,
            out_start,
            out_len
        ]
    ]


@signature(('bytes_literal', 'str_literal', 'bytes', 'string', 'bytes32'))
def sha256(expr, args, kwargs, context):
    sub = args[0]
    # Literal input
    if isinstance(sub, bytes):
        return LLLnode.from_list(
            bytes_to_int(hashlib.sha256(sub).digest()),
            typ=BaseType('bytes32'),
            pos=getpos(expr)
        )
    # bytes32 input
    elif is_base_type(sub.typ, 'bytes32'):
        return LLLnode.from_list(
            [
                'seq',
                ['mstore', MemoryPositions.FREE_VAR_SPACE, sub],
                _make_sha256_call(
                    inp_start=MemoryPositions.FREE_VAR_SPACE,
                    inp_len=32,
                    out_start=MemoryPositions.FREE_VAR_SPACE,
                    out_len=32
                ),
                ['mload', MemoryPositions.FREE_VAR_SPACE]  # push value onto stack
            ],
            typ=BaseType('bytes32'),
            pos=getpos(expr),
            add_gas_estimate=SHA256_BASE_GAS + 1 * SHA256_PER_WORD_GAS
        )
    # bytearay-like input
    if sub.location == "storage":
        # Copy storage to memory
        placeholder = context.new_placeholder(sub.typ)
        placeholder_node = LLLnode.from_list(placeholder, typ=sub.typ, location='memory')
        copier = make_byte_array_copier(
            placeholder_node,
            LLLnode.from_list('_sub', typ=sub.typ, location=sub.location),
        )
        return LLLnode.from_list(
            [
                'with', '_sub', sub, [
                    'seq',
                    copier,
                    _make_sha256_call(
                        inp_start=['add', placeholder, 32],
                        inp_len=['mload', placeholder],
                        out_start=MemoryPositions.FREE_VAR_SPACE,
                        out_len=32
                    ),
                    ['mload', MemoryPositions.FREE_VAR_SPACE]
                ],
            ],
            typ=BaseType('bytes32'),
            pos=getpos(expr),
            add_gas_estimate=SHA256_BASE_GAS + sub.typ.maxlen * SHA256_PER_WORD_GAS
        )
    elif sub.location == "memory":
        return LLLnode.from_list(
            [
                'with', '_sub', sub, [
                    'seq',
                    _make_sha256_call(
                        inp_start=['add', '_sub', 32],
                        inp_len=['mload', '_sub'],
                        out_start=MemoryPositions.FREE_VAR_SPACE,
                        out_len=32
                    ),
                    ['mload', MemoryPositions.FREE_VAR_SPACE]
                ]
            ],
            typ=BaseType('bytes32'),
            pos=getpos(expr),
            add_gas_estimate=SHA256_BASE_GAS + sub.typ.maxlen * SHA256_PER_WORD_GAS
        )
    else:
        # This should never happen, but just left here for future compiler-writers.
        raise Exception(f"Unsupported location: {sub.location}")  # pragma: no test


@signature('str_literal', 'name_literal')
def method_id(expr, args, kwargs, context):
    if b' ' in args[0]:
        raise TypeMismatch('Invalid function signature no spaces allowed.')
    method_id = fourbytes_to_int(keccak256(args[0])[:4])
    if args[1] == 'bytes32':
        return LLLnode(method_id, typ=BaseType('bytes32'), pos=getpos(expr))
    elif args[1] == 'bytes[4]':
        placeholder = LLLnode.from_list(context.new_placeholder(ByteArrayType(4)))
        return LLLnode.from_list(
            ['seq',
                ['mstore', ['add', placeholder, 4], method_id],
                ['mstore', placeholder, 4], placeholder],
            typ=ByteArrayType(4), location='memory', pos=getpos(expr))
    else:
        raise StructureException('Can only produce bytes32 or bytes[4] as outputs')


@signature('bytes32', 'uint256', 'uint256', 'uint256')
def ecrecover(expr, args, kwargs, context):
    placeholder_node = LLLnode.from_list(
        context.new_placeholder(ByteArrayType(128)), typ=ByteArrayType(128), location='memory'
    )
    return LLLnode.from_list([
        'seq',
        ['mstore', placeholder_node, args[0]],
        ['mstore', ['add', placeholder_node, 32], args[1]],
        ['mstore', ['add', placeholder_node, 64], args[2]],
        ['mstore', ['add', placeholder_node, 96], args[3]],
        ['pop', ['call', ['gas'], 1, 0, placeholder_node, 128, MemoryPositions.FREE_VAR_SPACE, 32]],
        ['mload', MemoryPositions.FREE_VAR_SPACE],
    ], typ=BaseType('address'), pos=getpos(expr))


def avo(arg, ind, pos):
    return unwrap_location(add_variable_offset(arg, LLLnode.from_list(ind, 'int128'), pos=pos))


@signature('uint256[2]', 'uint256[2]')
def ecadd(expr, args, kwargs, context):
    placeholder_node = LLLnode.from_list(
        context.new_placeholder(ByteArrayType(128)), typ=ByteArrayType(128), location='memory'
    )
    pos = getpos(expr)
    o = LLLnode.from_list([
        'seq',
        ['mstore', placeholder_node, avo(args[0], 0, pos)],
        ['mstore', ['add', placeholder_node, 32], avo(args[0], 1, pos)],
        ['mstore', ['add', placeholder_node, 64], avo(args[1], 0, pos)],
        ['mstore', ['add', placeholder_node, 96], avo(args[1], 1, pos)],
        ['assert', ['call', ['gas'], 6, 0, placeholder_node, 128, placeholder_node, 64]],
        placeholder_node,
    ], typ=ListType(BaseType('uint256'), 2), pos=getpos(expr), location='memory')
    return o


@signature('uint256[2]', 'uint256')
def ecmul(expr, args, kwargs, context):
    placeholder_node = LLLnode.from_list(
        context.new_placeholder(ByteArrayType(128)), typ=ByteArrayType(128), location='memory'
    )
    pos = getpos(expr)
    o = LLLnode.from_list([
        'seq',
        ['mstore', placeholder_node, avo(args[0], 0, pos)],
        ['mstore', ['add', placeholder_node, 32], avo(args[0], 1, pos)],
        ['mstore', ['add', placeholder_node, 64], args[1]],
        ['assert', ['call', ['gas'], 7, 0, placeholder_node, 96, placeholder_node, 64]],
        placeholder_node,
    ], typ=ListType(BaseType('uint256'), 2), pos=pos, location='memory')
    return o


def _memory_element_getter(index):
    return LLLnode.from_list(
        ['mload', ['add', '_sub', ['add', 32, ['mul', 32, index]]]],
        typ=BaseType('int128'),
    )


def _storage_element_getter(index):
    return LLLnode.from_list(
        ['sload', ['add', ['sha3_32', '_sub'], ['add', 1, index]]],
        typ=BaseType('int128'),
    )


@signature('bytes', 'int128', type=Optional('name_literal', 'bytes32'))
def extract32(expr, args, kwargs, context):
    sub, index = args
    ret_type = kwargs['type']
    # Get length and specific element
    if sub.location == "memory":
        lengetter = LLLnode.from_list(['mload', '_sub'], typ=BaseType('int128'))
        elementgetter = _memory_element_getter
    elif sub.location == "storage":
        lengetter = LLLnode.from_list(['sload', ['sha3_32', '_sub']], typ=BaseType('int128'))
        elementgetter = _storage_element_getter
    # TODO: unclosed if/elif clause.  Undefined behavior if `sub.location`
    # isn't one of `memory`/`storage`

    # Special case: index known to be a multiple of 32
    if isinstance(index.value, int) and not index.value % 32:
        o = LLLnode.from_list(
            [
                'with', '_sub', sub,
                elementgetter(['div', ['clamp', 0, index, ['sub', lengetter, 32]], 32])
            ],
            typ=BaseType(ret_type),
            annotation='extracting 32 bytes',
        )
    # General case
    else:
        o = LLLnode.from_list([
            'with', '_sub', sub, [
                'with', '_len', lengetter, [
                    'with', '_index', ['clamp', 0, index, ['sub', '_len', 32]], [
                        'with', '_mi32', ['mod', '_index', 32], [
                            'with', '_di32', ['div', '_index', 32],
                            [
                                'if',
                                '_mi32',
                                [
                                    'add',
                                    ['mul', elementgetter('_di32'), ['exp', 256, '_mi32']],
                                    [
                                        'div',
                                        elementgetter(['add', '_di32', 1]),
                                        ['exp', 256, ['sub', 32, '_mi32']],
                                    ],
                                ],
                                elementgetter('_di32'),
                            ],
                        ],
                    ],
                ],
            ],
        ], typ=BaseType(ret_type), pos=getpos(expr), annotation='extracting 32 bytes')
    if ret_type == 'int128':
        return LLLnode.from_list(
            ['clamp', ['mload', MemoryPositions.MINNUM], o, ['mload', MemoryPositions.MAXNUM]],
            typ=BaseType('int128'),
            pos=getpos(expr),
        )
    elif ret_type == 'address':
        return LLLnode.from_list(
            ['uclamplt', o, ['mload', MemoryPositions.ADDRSIZE]],
            typ=BaseType(ret_type),
            pos=getpos(expr),
        )
    else:
        return o


@signature(('num_literal', 'int128', 'uint256', 'decimal'), 'str_literal')
def as_wei_value(expr, args, kwargs, context):
    # Denominations
    wei_denominations = {
        ("wei", ): 1,
        ("femtoether", "kwei", "babbage"): 10**3,
        ("picoether", "mwei", "lovelace"): 10**6,
        ("nanoether", "gwei", "shannon"): 10**9,
        ("microether", "szabo", ): 10**12,
        ("milliether", "finney", ): 10**15,
        ("ether", ): 10**18,
        ("kether", "grand"): 10**21,
    }

    value, denom_name = args[0], args[1].decode()

    denom_divisor = next((v for k, v in wei_denominations.items() if denom_name in k), False)
    if not denom_divisor:
        raise InvalidLiteral(
            f"Invalid denomination: {denom_name}, valid denominations are: "
            f"{','.join(x[0] for x in wei_denominations)}",
            expr.args[1]
        )

    # Compute the amount of wei and return that value
    if isinstance(value, (int, Decimal)):
        expr_args_0 = expr.args[0]
        # On constant reference fetch value node of constant assignment.
        if context.constants.ast_is_constant(expr.args[0]):
            expr_args_0 = context.constants._constants_ast[expr.args[0].id]
        numstring, num, den = get_number_as_fraction(expr_args_0, context)
        if denom_divisor % den:
            max_len = len(str(denom_divisor))-1
            raise InvalidLiteral(
                f"Wei value of denomination '{denom_name}' has maximum {max_len} decimal places",
                expr.args[0]
            )
        sub = num * denom_divisor // den
    elif value.typ.is_literal:
        if value.value <= 0:
            raise InvalidLiteral("Negative wei value not allowed", expr)
        sub = ['mul', value.value, denom_divisor]
    elif value.typ.typ == 'uint256':
        sub = ['mul', value, denom_divisor]
    else:
        sub = ['div', ['mul', value, denom_divisor], DECIMAL_DIVISOR]

    return LLLnode.from_list(
        sub,
        typ=BaseType('uint256', {'wei': 1}),
        location=None,
        pos=getpos(expr),
    )


zero_value = LLLnode.from_list(0, typ=BaseType('uint256', {'wei': 1}))
false_value = LLLnode.from_list(0, typ=BaseType('bool', is_literal=True))


@signature(
    'address',
    'bytes',
    outsize=Optional('num_literal', 0),
    gas=Optional('uint256', 'gas'),
    value=Optional('uint256', zero_value),
    delegate_call=Optional('bool', false_value),
)
def raw_call(expr, args, kwargs, context):
    to, data = args
    gas, value, outsize, delegate_call = (
        kwargs['gas'],
        kwargs['value'],
        kwargs['outsize'],
        kwargs['delegate_call'],
    )
    if delegate_call.typ.is_literal is False:
        raise TypeMismatch(
            'The delegate_call parameter has to be a static/literal boolean value.'
        )
    if context.is_constant():
        raise ConstancyViolation(
            f"Cannot make calls from {context.pp_constancy()}",
            expr,
        )
    if value != zero_value:
        enforce_units(
            value.typ,
            get_keyword(expr, 'value'),
            BaseType('uint256', {'wei': 1}),
        )
    placeholder = context.new_placeholder(data.typ)
    placeholder_node = LLLnode.from_list(placeholder, typ=data.typ, location='memory')
    copier = make_byte_array_copier(placeholder_node, data, pos=getpos(expr))
    output_placeholder = context.new_placeholder(ByteArrayType(outsize))
    output_node = LLLnode.from_list(
        output_placeholder,
        typ=ByteArrayType(outsize),
        location='memory',
    )

    # build LLL for call or delegatecall
    common_call_lll = [
        ['add', placeholder_node, 32],
        ['mload', placeholder_node],
        # if there is no return value, the return offset can be 0
        ['add', output_node, 32] if outsize else 0,
        outsize
    ]

    if delegate_call.value == 1:
        call_lll = ['delegatecall', gas, to] + common_call_lll
    else:
        call_lll = ['call', gas, to, value] + common_call_lll

    # build sequence LLL
    if outsize:
        # only copy the return value to memory if outsize > 0
        seq = ['seq', copier, ['assert', call_lll], ['mstore', output_node, outsize], output_node]
        typ = ByteArrayType(outsize)
    else:
        seq = ['seq', copier, ['assert', call_lll]]
        typ = None

    return LLLnode.from_list(seq, typ=typ, location="memory", pos=getpos(expr))


@signature('address', 'uint256')
def send(expr, args, kwargs, context):
    to, value = args
    if context.is_constant():
        raise ConstancyViolation(
            f"Cannot send ether inside {context.pp_constancy()}!",
            expr,
        )
    enforce_units(value.typ, expr.args[1], BaseType('uint256', {'wei': 1}))
    return LLLnode.from_list(
        ['assert', ['call', 0, to, value, 0, 0, 0, 0]],
        typ=None,
        pos=getpos(expr),
    )


@signature('address')
def selfdestruct(expr, args, kwargs, context):
    if context.is_constant():
        raise ConstancyViolation(
            f"Cannot {expr.func.id} inside {context.pp_constancy()}!",
            expr.func,
        )
    return LLLnode.from_list(['selfdestruct', args[0]], typ=None, pos=getpos(expr))


@signature(('uint256'))
def blockhash(expr, args, kwargs, contact):
    return LLLnode.from_list(
        ['blockhash', ['uclamplt', ['clampge', args[0], ['sub', ['number'], 256]], 'number']],
        typ=BaseType('bytes32'),
        pos=getpos(expr),
    )


@signature('bytes', '*')
def _RLPlist(expr, args, kwargs, context):
    # Second argument must be a list of types
    if not isinstance(args[1], vy_ast.List):
        raise TypeMismatch("Expecting list of types for second argument", args[1])
    if len(args[1].elts) == 0:
        raise TypeMismatch("RLP list must have at least one item", expr)
    if len(args[1].elts) > 32:
        raise TypeMismatch("RLP list must have at most 32 items", expr)
    # Get the output format
    _format = []
    for arg in args[1].elts:
        if isinstance(arg, vy_ast.Name) and arg.id == "bytes":
            subtyp = ByteArrayType(args[0].typ.maxlen)
        else:
            subtyp = context.parse_type(arg, 'memory')
            if not isinstance(subtyp, BaseType):
                raise TypeMismatch("RLP lists only accept BaseTypes and byte arrays", arg)
            if not is_base_type(subtyp, ('int128', 'uint256', 'bytes32', 'address', 'bool')):
                raise TypeMismatch(f"Unsupported base type: {subtyp.typ}", arg)
        _format.append(subtyp)
    output_type = TupleType(_format)
    output_placeholder_type = ByteArrayType(
        (2 * len(_format) + 1 + get_size_of_type(output_type)) * 32,
    )
    output_placeholder = context.new_placeholder(output_placeholder_type)
    output_node = LLLnode.from_list(
        output_placeholder,
        typ=output_placeholder_type,
        location='memory',
    )
    # Create a decoder for each element in the tuple
    decoder = []
    for i, typ in enumerate(_format):
        # Decoder for bytes32
        if is_base_type(typ, 'bytes32'):
            decoder.append(LLLnode.from_list(
                [
                    'seq',
                    [
                        'assert',
                        [
                            'eq',
                            [
                                'mload',
                                [
                                    'add',
                                    output_node,
                                    ['mload', ['add', output_node, 32 * i]],
                                ],
                            ],
                            32,
                        ],
                    ],
                    [
                        'mload',
                        [
                            'add',
                            32,
                            [
                                'add',
                                output_node,
                                ['mload', ['add', output_node, 32 * i]],
                            ],
                        ],
                    ],
                ],
                typ,
                annotation='getting and checking bytes32 item',
            ))
        # Decoder for address
        elif is_base_type(typ, 'address'):
            decoder.append(LLLnode.from_list(
                [
                    'seq',
                    [
                        'assert',
                        [
                            'eq',
                            [
                                'mload',
                                [
                                    'add',
                                    output_node,
                                    ['mload', ['add', output_node, 32 * i]],
                                ],
                            ],
                            20,
                        ]
                    ],
                    [
                        'mod',
                        [
                            'mload',
                            [
                                'add',
                                20,
                                ['add', output_node, ['mload', ['add', output_node, 32 * i]]],
                            ],
                        ],
                        ['mload', MemoryPositions.ADDRSIZE],
                    ]
                ],
                typ,
                annotation='getting and checking address item',
            ))
        # Decoder for bytes
        elif isinstance(typ, ByteArrayType):
            decoder.append(LLLnode.from_list(
                [
                    'add',
                    output_node,
                    ['mload', ['add', output_node, 32 * i]],
                ],
                typ,
                location='memory',
                annotation='getting byte array',
            ))
        # Decoder for num and uint256
        elif is_base_type(typ, ('int128', 'uint256')):
            bytez = LLLnode.from_list(
                [
                    'add',
                    output_node,
                    ['mload', ['add', output_node, 32 * i]],
                ],
                typ,
                location='memory',
                annotation=f'getting and checking {typ.typ}',
            )
            decoder.append(byte_array_to_num(bytez, expr, typ.typ))
        # Decoder for bools
        elif is_base_type(typ, ('bool')):
            # This is basically a really clever way to test for a
            # length-prefixed one or zero. We take the 32 bytes starting one
            # byte *after* the start of the length declaration; this includes
            # the last 31 bytes of the length and the first byte of the value.
            # 0 corresponds to length 0, first byte 0, and 257 corresponds to
            # length 1, first byte \x01
            decoder.append(LLLnode.from_list(
                [
                    'with', '_ans', [
                        'mload',
                        [
                            'add',
                            1,
                            ['add', output_node, ['mload', ['add', output_node, 32 * i]]]
                        ],
                    ],
                    [
                        'seq',
                        ['assert', ['or', ['eq', '_ans', 0], ['eq', '_ans', 257]]],
                        ['div', '_ans', 257],
                    ],
                ],
                typ,
                annotation='getting and checking bool',
            ))
        else:
            # Should never reach because of top level base level check.
            raise Exception("Type not yet supported")  # pragma: no cover
    # Copy the input data to memory
    if args[0].location == "memory":
        variable_pointer = args[0]
    elif args[0].location == "storage":
        placeholder = context.new_placeholder(args[0].typ)
        placeholder_node = LLLnode.from_list(placeholder, typ=args[0].typ, location='memory')
        copier = make_byte_array_copier(
            placeholder_node,
            LLLnode.from_list('_ptr', typ=args[0].typ, location=args[0].location),
        )
        variable_pointer = ['with', '_ptr', args[0], ['seq', copier, placeholder_node]]
    else:
        # Should never reach because of top level base level check.
        raise Exception("Location not yet supported")  # pragma: no cover
    # Decode the input data
    initial_setter = LLLnode.from_list(
        ['seq',
            ['with', '_sub', variable_pointer,
                ['pop', ['call',
                         1500 + 400 * len(_format) + 10 * len(args),
                         LLLnode.from_list(RLP_DECODER_ADDRESS, annotation='RLP decoder'),
                         0,
                         ['add', '_sub', 32],
                         ['mload', '_sub'],
                         output_node,
                         64 * len(_format) + 32 + 32 * get_size_of_type(output_type)]]],
            ['assert', ['eq', ['mload', output_node], 32 * len(_format) + 32]]],
        typ=None)
    # Shove the input data decoder in front of the first variable decoder
    decoder[0] = LLLnode.from_list(
        ['seq', initial_setter, decoder[0]],
        typ=decoder[0].typ,
        location=decoder[0].location,
    )
    return LLLnode.from_list(
        ["multi"] + decoder,
        typ=output_type,
        location='memory',
        pos=getpos(expr),
    )


@signature('*', ('bytes32', 'bytes'))
def raw_log(expr, args, kwargs, context):
    if not isinstance(args[0], vy_ast.List) or len(args[0].elts) > 4:
        raise StructureException("Expecting a list of 0-4 topics as first argument", args[0])
    topics = []
    for elt in args[0].elts:
        arg = Expr.parse_value_expr(elt, context)
        if not is_base_type(arg.typ, 'bytes32'):
            raise TypeMismatch("Expecting a bytes32 argument as topic", elt)
        topics.append(arg)
    if args[1].typ == BaseType('bytes32'):
        placeholder = context.new_placeholder(BaseType('bytes32'))
        return LLLnode.from_list(
            ['seq',
                ['mstore', placeholder, unwrap_location(args[1])],
                [
                    "log" + str(len(topics)),
                    placeholder,
                    32,
                ] + topics], typ=None, pos=getpos(expr))
    if args[1].location == "memory":
        return LLLnode.from_list([
            "with", "_arr", args[1], [
                "log" + str(len(topics)),
                ["add", "_arr", 32],
                ["mload", "_arr"],
            ] + topics
        ], typ=None, pos=getpos(expr))
    placeholder = context.new_placeholder(args[1].typ)
    placeholder_node = LLLnode.from_list(placeholder, typ=args[1].typ, location='memory')
    copier = make_byte_array_copier(
        placeholder_node,
        LLLnode.from_list('_sub', typ=args[1].typ, location=args[1].location),
        pos=getpos(expr),
    )
    return LLLnode.from_list(
        [
            "with", "_sub", args[1],
            [
                "seq",
                copier,
                [
                    "log" + str(len(topics)),
                    ["add", placeholder_node, 32],
                    ["mload", placeholder_node],
                ] + topics
            ],
        ],
        typ=None,
        pos=getpos(expr),
    )


@signature('uint256', 'uint256')
def bitwise_and(expr, args, kwargs, context):
    return LLLnode.from_list(['and', args[0], args[1]], typ=BaseType('uint256'), pos=getpos(expr))


@signature('uint256', 'uint256')
def bitwise_or(expr, args, kwargs, context):
    return LLLnode.from_list(['or', args[0], args[1]], typ=BaseType('uint256'), pos=getpos(expr))


@signature('uint256', 'uint256')
def bitwise_xor(expr, args, kwargs, context):
    return LLLnode.from_list(['xor', args[0], args[1]], typ=BaseType('uint256'), pos=getpos(expr))


@signature('uint256', 'uint256', 'uint256')
def uint256_addmod(expr, args, kwargs, context):
    return LLLnode.from_list(
        [
            'seq',
            ['assert', args[2]],
            ['addmod', args[0], args[1], args[2]],
        ],
        typ=BaseType('uint256'),
        pos=getpos(expr),
    )


@signature('uint256', 'uint256', 'uint256')
def uint256_mulmod(expr, args, kwargs, context):
    return LLLnode.from_list(
        [
            'seq',
            ['assert', args[2]],
            ['mulmod', args[0], args[1], args[2]],
        ],
        typ=BaseType('uint256'),
        pos=getpos(expr),
    )


@signature('uint256')
def bitwise_not(expr, args, kwargs, context):
    return LLLnode.from_list(['not', args[0]], typ=BaseType('uint256'), pos=getpos(expr))


@signature('uint256', 'int128')
def shift(expr, args, kwargs, context):

    if args[1].typ.is_literal:
        shift_abs = abs(args[1].value)
    else:
        shift_abs = ['sub', 0, '_s']

    if version_check(begin="constantinople"):
        left_shift = ['shl', '_s', '_v']
        right_shift = ['shr', shift_abs, '_v']
    else:
        # If second argument is positive, left-shift so multiply by a power of two
        # If it is negative, divide by a power of two
        # node that if the abs of the second argument >= 256, then in the EVM
        # 2**(second arg) = 0, and multiplying OR dividing by 0 gives 0
        left_shift = ['mul', '_v', ['exp', 2, '_s']]
        right_shift = ['div', '_v', ['exp', 2, shift_abs]]

    if not args[1].typ.is_literal:
        node_list = ['if', ['slt', '_s', 0], right_shift, left_shift]
    elif args[1].value >= 0:
        node_list = left_shift
    else:
        node_list = right_shift

    return LLLnode.from_list(
        [
            'with', '_v', args[0], [
                'with', '_s', args[1],
                    node_list,
            ],
        ],
        typ=BaseType('uint256'),
        pos=getpos(expr),
    )


def get_create_forwarder_to_bytecode():
    from vyper.compile_lll import (
        assembly_to_evm,
        num_to_bytearray
    )
    code_a = [
        'PUSH1', 0x33,
        'PUSH1', 0x0c,
        'PUSH1', 0x00,
        'CODECOPY',
        'PUSH1', 0x33,
        'PUSH1', 0x00,
        'RETURN',
        'CALLDATASIZE',
        'PUSH1', 0x00,
        'PUSH1', 0x00,
        'CALLDATACOPY',
        'PUSH2', num_to_bytearray(0x1000),
        'PUSH1', 0x00,
        'CALLDATASIZE',
        'PUSH1', 0x00,
        'PUSH20',  # [address to delegate to]
    ]
    code_b = [
        'GAS',
        'DELEGATECALL',
        'PUSH1', 0x2c,  # jumpdest of whole program.
        'JUMPI',
        'PUSH1', 0x0,
        'DUP1',
        'REVERT',
        'JUMPDEST',
        'PUSH2', num_to_bytearray(0x1000),
        'PUSH1', 0x00,
        'RETURN'
    ]
    return assembly_to_evm(code_a)[0] + (b'\x00' * 20) + assembly_to_evm(code_b)[0]


@signature('address', value=Optional('uint256', zero_value))
def create_forwarder_to(expr, args, kwargs, context):

    value = kwargs['value']
    if value != zero_value:
        enforce_units(value.typ, get_keyword(expr, 'value'),
                      BaseType('uint256', {'wei': 1}))
    if context.is_constant():
        raise ConstancyViolation(
            f"Cannot make calls from {context.pp_constancy()}",
            expr,
        )
    placeholder = context.new_placeholder(ByteArrayType(96))

    kode = get_create_forwarder_to_bytecode()
    high = bytes_to_int(kode[:32])
    low = bytes_to_int((kode + b'\x00' * 32)[47:79])

    return LLLnode.from_list(
        [
            'seq',
            ['mstore', placeholder, high],
            ['mstore', ['add', placeholder, 27], ['mul', args[0], 2**96]],
            ['mstore', ['add', placeholder, 47], low],
            ['clamp_nonzero', ['create', value, placeholder, 96]],
        ],
        typ=BaseType('address'),
        pos=getpos(expr),
        add_gas_estimate=11000,
    )


@signature(('int128', 'decimal', 'uint256'), ('int128', 'decimal', 'uint256'))
def _min(expr, args, kwargs, context):
    return minmax(expr, args, kwargs, context, 'gt')


@signature(('int128', 'decimal', 'uint256'), ('int128', 'decimal', 'uint256'))
def _max(expr, args, kwargs, context):
    return minmax(expr, args, kwargs, context, 'lt')


def minmax(expr, args, kwargs, context, comparator):
    def _can_compare_with_uint256(operand):
        if operand.typ.typ == 'uint256':
            return True
        elif operand.typ.typ == 'int128' and operand.typ.is_literal and SizeLimits.in_bounds('uint256', operand.value):  # noqa: E501
            return True
        return False

    left, right = args[0], args[1]
    if not are_units_compatible(left.typ, right.typ) and not are_units_compatible(right.typ, left.typ):  # noqa: E501
        raise TypeMismatch("Units must be compatible", expr)
    if left.typ.typ == right.typ.typ:
        if left.typ.typ != 'uint256':
            # if comparing like types that are not uint256, use SLT or SGT
            comparator = f's{comparator}'
        o = ['if', [comparator, '_l', '_r'], '_r', '_l']
        otyp = left.typ
        otyp.is_literal = False
    elif _can_compare_with_uint256(left) and _can_compare_with_uint256(right):
        o = ['if', [comparator, '_l', '_r'], '_r', '_l']
        if right.typ.typ == 'uint256':
            otyp = right.typ
        else:
            otyp = left.typ
        otyp.is_literal = False
    else:
        raise TypeMismatch(
            f"Minmax types incompatible: {left.typ.typ} {right.typ.typ}"
        )
    return LLLnode.from_list(
        ['with', '_l', left, ['with', '_r', right, o]],
        typ=otyp,
        pos=getpos(expr),
    )


@signature('decimal')
def sqrt(expr, args, kwargs, context):
    from vyper.functions.utils import (
        generate_inline_function,
    )
    arg = args[0]
    sqrt_code = """
assert x >= 0.0
z: decimal = 0.0

if x == 0.0:
    z = 0.0
else:
    z = x / 2.0 + 0.5
    y: decimal = x

    for i in range(256):
        if z == y:
            break
        y = z
        z = (x / z + z) / 2.0
    """

    x_type = BaseType('decimal')
    placeholder_copy = ['pass']
    # Steal current position if variable is already allocated.
    if arg.value == 'mload':
        new_var_pos = arg.args[0]
    # Other locations need to be copied.
    else:
        new_var_pos = context.new_placeholder(x_type)
        placeholder_copy = ['mstore', new_var_pos, arg]
    # Create input variables.
    variables = {
        'x': VariableRecord(
            name='x',
            pos=new_var_pos,
            typ=x_type,
            mutable=False
        )
    }
    # Generate inline LLL.
    new_ctx, sqrt_lll = generate_inline_function(
        code=sqrt_code,
        variables=variables,
        memory_allocator=context.memory_allocator
    )
    return LLLnode.from_list(
        [
            'seq_unchecked',
            placeholder_copy,  # load x variable
            sqrt_lll,
            ['mload', new_ctx.vars['z'].pos]  # unload z variable into the stack,
        ],
        typ=BaseType('decimal'),
        pos=getpos(expr),
    )


def _clear():
    raise CompilerPanic(
        "This function should never be called! `clear()` is currently handled "
        "differently than other functions as it self modifies its input argument "
        "statement. Please see `_clear()` in `stmt.py`"
    )


DISPATCH_TABLE = {
    'floor': floor,
    'ceil': ceil,
    'as_unitless_number': as_unitless_number,
    'convert': _convert,
    'slice': _slice,
    'len': _len,
    'concat': concat,
    'sha3': _sha3,
    'sha256': sha256,
    'method_id': method_id,
    'keccak256': _keccak256,
    'ecrecover': ecrecover,
    'ecadd': ecadd,
    'ecmul': ecmul,
    'extract32': extract32,
    'as_wei_value': as_wei_value,
    'raw_call': raw_call,
    'RLPList': _RLPlist,
    'blockhash': blockhash,
    'bitwise_and': bitwise_and,
    'bitwise_or': bitwise_or,
    'bitwise_xor': bitwise_xor,
    'bitwise_not': bitwise_not,
    'uint256_addmod': uint256_addmod,
    'uint256_mulmod': uint256_mulmod,
    'sqrt': sqrt,
    'shift': shift,
    'create_forwarder_to': create_forwarder_to,
    'min': _min,
    'max': _max,
}

STMT_DISPATCH_TABLE = {
    'assert_modifiable': assert_modifiable,
    'clear': _clear,
    'send': send,
    'selfdestruct': selfdestruct,
    'raw_call': raw_call,
    'raw_log': raw_log,
    'create_forwarder_to': create_forwarder_to,
}

BUILTIN_FUNCTIONS = {**STMT_DISPATCH_TABLE, **DISPATCH_TABLE}.keys()
