from typing import Type

import claripy
from archinfo import Arch

from .constants import *

_name_ctr = 0

def sym_ptr(arch: Type[Arch], name: str | None=None) -> claripy.BVS:
    """
    Generates a fresh symbolic pointer for the input architecture.

    :param Type[Arch] arch: The architecture the pointer is for. For example in x64, the paramater passed should be archinfo.ArchAMD64, and a 64 bit symbolic pointer will be returned.
    :param str | None name: A human readable name for the symbolic pointer. If None is passed an autogenerated symbolic_ptr_i name is used.
    :return: A fresh symbolic bitvector whose size is appropriate for the input architecture.
    :rtype: claripy.BVS
    """
    global _name_ctr
    if name is None:
        name = "symbolic_ptr_{}".format(_name_ctr)
        _name_ctr += 1
    return claripy.BVS(name, arch.bits)

def sym_ptr_constraints(symbolic_ptr: claripy.ast.bits, concrete_addr: int, can_be_null: bool=True) -> claripy.ast.bool:
    """
    Generates claripy expressions for constraining the input symbolic pointer to a specific concrete value.

    :param claripy.ast.bits symbolic_ptr: The symbolic pointer to constrain.
    :param int concrete_addr: The concrete address which the symbolic pointer should be equal to.
    :param bool can_be_null: If this value is True, the returned constraints will contain a disjunction which allows the symbolic pointer to be NULL.
    :return: A claripy proposition constraining the symbolic pointer.
    :rtype: claripy.ast.bool
    """
    if can_be_null:
        return (symbolic_ptr == NULL_PTR) | (symbolic_ptr == concrete_addr)
    else:
        return (symbolic_ptr == concrete_addr)

# Given an integer encoded in num_bits two's complement form, returns the corresponding Python integer
def from_twos_comp(val: int, num_bits: int) -> int:
    """
    Converts an integer from two's complement form back to an integer, assuming the value is stored in num_bits space.

    :param int val: The two's complement integer to convert. This number must be non-negative.
    :param int num_bits: The number of bits used to store the number.
    :return: A signed Python representation of the integer.
    :rtype: int
    """
    if (val & (1 << (num_bits - 1))) != 0:
        val = val - (1 << num_bits)
    return val

# Given a Python integer, converts that integer to a two's complement form
def to_twos_comp(val: int, num_bits: int) -> int:
    """
    Converts an integer value to two's complement representation, assuming the value is stored in num_bits space.

    :param int val: The integer value to convert.
    :param int num_bits: The number of bits used to store the integer.
    :return: A two's complement representation of the value. This value is non-negative.
    :rtype: int
    """
    if val < 0:
        val = val % (1 << num_bits)
    else:
        val = val % (1 << (num_bits - 1))
    return val