# pylint:disable=abstract-method,line-too-long,missing-class-docstring,wrong-import-position,too-many-positional-arguments
from __future__ import annotations

import contextlib
import copy
import re
import logging
from typing import Literal, Any, cast, overload
from collections import OrderedDict, defaultdict, ChainMap
from collections.abc import Iterable
from collections.abc import MutableMapping

from archinfo import Endness, Arch
import claripy
import cxxheaderparser.simple
import cxxheaderparser.errors
import cxxheaderparser.types
import pycparser
from pycparser import c_ast

from angr.errors import AngrTypeError
from angr.sim_state import SimState

StoreType = int | claripy.ast.BV

l = logging.getLogger(name=__name__)

# pycparser hack to parse type expressions
errorlog = logging.getLogger(name=__name__ + ".yacc")
errorlog.setLevel(logging.ERROR)


class SimType:
    """
    SimType exists to track type information for SimProcedures.
    """

    _fields: tuple[str, ...] = ()
    _args: tuple[str, ...] = ("label",)
    _arch: Arch | None
    _size: int | None = None
    _can_refine_int: bool = False
    _base_name: str
    _ident: str = "simtype"
    base: bool = True

    def __init__(self, label=None):
        """
        :param label: the type label.
        """
        self.label = label
        self._arch = None

    @staticmethod
    def _simtype_eq(self_type: SimType, other: SimType, avoid: dict[str, set[SimType]] | None) -> bool:
        if self_type is other:
            return True
        if avoid is not None and self_type in avoid["self"] and other in avoid["other"]:
            return True
        return self_type.__eq__(other, avoid=avoid)  # pylint:disable=unnecessary-dunder-call

    def __eq__(self, other, avoid=None):
        if type(self) is not type(other):
            return False

        for attr in self._fields:
            if attr == "size" and self._arch is None and other._arch is None:
                continue
            attr_self = getattr(self, attr)
            attr_other = getattr(other, attr)
            if isinstance(attr_self, SimType):
                if not SimType._simtype_eq(attr_self, attr_other, avoid):
                    return False
            elif isinstance(attr_self, (list, tuple)) and isinstance(attr_other, (list, tuple)):
                if len(attr_self) != len(attr_other):
                    return False
                for a, b in zip(attr_self, attr_other):
                    if isinstance(a, SimType) and isinstance(b, SimType):
                        if SimType._simtype_eq(a, b, avoid) is False:
                            return False
                    else:
                        if a != b:
                            return False
            else:
                if attr_self != attr_other:
                    return False

        return True

    def __ne__(self, other):
        # wow many efficient
        return not self == other

    def __hash__(self):
        # very hashing algorithm many secure wow
        out = hash(type(self))
        for attr in self._fields:
            out ^= hash(getattr(self, attr))
        return out

    def _refine_dir(self):  # pylint: disable=no-self-use
        return []

    def _refine(self, view, k):  # pylint: disable=unused-argument,no-self-use
        raise KeyError(f"{k} is not a valid refinement")

    @property
    def size(self) -> int | None:
        """
        The size of the type in bits, or None if no size is computable.
        """
        return self._size

    @property
    def alignment(self):
        """
        The alignment of the type in bytes.
        """
        if self._arch is None:
            raise ValueError("Can't tell my alignment without an arch!")
        if self.size is None:
            l.debug("The size of the type %r is unknown; assuming word size of the arch.", self)
            return self._arch.bytes
        return self.size // self._arch.byte_width

    def with_arch(self, arch: Arch | None):
        if arch is None:
            return self
        if self._arch is not None and self._arch == arch:
            return self
        return self._with_arch(arch)

    def _with_arch(self, arch):
        cp = copy.copy(self)
        cp._arch = arch
        return cp

    def _init_str(self):
        return f"NotImplemented({self.__class__.__name__})"

    def c_repr(
        self, name=None, full=0, memo=None, indent: int | None = 0, name_parens: bool = True
    ):  # pylint: disable=unused-argument
        if name is None:
            return repr(self)
        return f"{str(self) if self.label is None else self.label} {name}"

    def copy(self):
        raise NotImplementedError

    def extract(self, state: SimState, addr, concrete: bool = False) -> Any:
        raise NotImplementedError

    def store(self, state: SimState, addr, value: Any):
        raise NotImplementedError

    def extract_claripy(self, bits) -> Any:
        """
        Given a bitvector `bits` which was loaded from memory in a big-endian fashion, return a more appropriate or
        structured representation of the data.

        A type must have an arch associated in order to use this method.
        """
        raise NotImplementedError(f"extract_claripy is not implemented for {self}")

    def to_json(self, fields: Iterable[str] | None = None, memo: dict[str, SimTypeRef] | None = None) -> dict[str, Any]:
        """
        Serialize the type class to a JSON-compatible dictionary.
        """

        if memo is None:
            memo = {}

        nullable_fields = {"label"}
        if fields is None:
            fields = self._args

        d: dict[str, Any] = {"_t": self._ident}
        for field in fields:
            value = getattr(self, field)
            if isinstance(value, SimType):
                d[field] = value.to_json(memo=memo)
            elif isinstance(value, (list, tuple)):
                d[field] = [v.to_json(memo=memo) if isinstance(v, SimType) else v for v in value]
            elif isinstance(value, dict):
                d[field] = {k: v.to_json(memo=memo) if isinstance(v, SimType) else v for k, v in value.items()}
            else:
                if field in nullable_fields and value is None:
                    continue
                d[field] = value
        return d

    @staticmethod
    def from_json(d: dict[str, Any]):
        """
        Deserialize a type class from a JSON-compatible dictionary.
        """

        assert "_t" in d
        cls = IDENT_TO_CLS.get(d["_t"], None)  # pylint: disable=redefined-outer-name
        assert cls is not None, f"Unknown SimType class identifier {d['_t']}"
        if getattr(cls, "from_json", SimType.from_json) is not SimType.from_json:
            return cls.from_json(d)

        kwargs = {}
        for field in cls._args:
            if field not in d:
                continue
            value = d[field]
            if isinstance(value, dict):
                if "_t" in value:
                    value = SimType.from_json(value)
                else:
                    new_value = {}
                    for k, v in value.items():
                        if isinstance(v, dict) and "_t" in v:
                            new_value[k] = SimType.from_json(v)
                        else:
                            new_value[k] = v
                    value = new_value
            elif isinstance(value, list):
                new_value = []
                for v in value:
                    if isinstance(v, dict) and "_t" in v:
                        new_value.append(SimType.from_json(v))
                    else:
                        new_value.append(v)
                value = new_value
            kwargs[field] = value
        return cls(**kwargs)


class TypeRef(SimType):
    """
    A TypeRef is a reference to a type with a name. This allows for interactivity in type analysis, by storing a type
    and having the option to update it later and have all references to it automatically update as well.
    """

    _args = ("name", "ty")
    _ident = "tref"

    def __init__(self, name, ty):
        super().__init__()

        self.type = ty
        self._name = name

    @property
    def type(self):
        return self._type

    @property
    def ty(self):
        return self.type

    @type.setter
    def type(self, val):
        self._type = val
        self._arch = val._arch

    @property
    def name(self):
        """
        This is a read-only property because it is desirable to store typerefs in a mapping from name to type, and we
        want the mapping to be in the loop for any updates.
        """
        return self._name

    def __eq__(self, other, avoid=None):
        return type(other) is TypeRef and self.type == other.type

    def __hash__(self):
        return hash(self.type)

    def __repr__(self):
        return self.name

    @property
    def size(self):
        return self.type.size

    @property
    def alignment(self):
        return self.type.alignment

    def with_arch(self, arch):
        self.type = self.type.with_arch(arch)
        self._arch = arch
        return self

    def c_repr(
        self, name=None, full=0, memo=None, indent=0, name_parens: bool = True
    ):  # pylint: disable=unused-argument
        if not full:
            if name is not None:
                return f"{self.name} {name}"
            return self.name
        return self.type.c_repr(name=name, full=full, memo=memo, indent=indent)

    def copy(self):
        raise NotImplementedError("copy() for TypeRef is ill-defined. What do you want this to do?")


class NamedTypeMixin:
    """
    SimType classes with this mixin in the class hierarchy allows setting custom class names. A typical use case is
    to represent same or similar type classes with different qualified names, such as "std::basic_string" vs
    "std::__cxx11::basic_string". In such cases, .name stores the qualified name, and .unqualified_name() returns the
    unqualified name of the type.
    """

    def __init__(self, *args, name: str | None = None, **kwargs):
        super().__init__(*args, **kwargs)
        self._name = name

    @property
    def name(self) -> str:
        if self._name is None:
            self._name = repr(self)
        return self._name

    @name.setter
    def name(self, v):
        self._name = v

    def unqualified_name(self, lang: str = "c++") -> str:
        if lang == "c++":
            splitter = "::"
            n = self.name.split(splitter)
            return n[-1]
        raise NotImplementedError(f"Unsupported language {lang}.")


class SimTypeBottom(SimType):
    """
    SimTypeBottom basically represents a type error.
    """

    _base_name = "bot"
    _ident = "bot"

    def __repr__(self):
        return self.label or "BOT"

    def c_repr(
        self, name=None, full=0, memo=None, indent=0, name_parens: bool = True
    ):  # pylint: disable=unused-argument
        if name is None:
            return "int" if self.label is None else self.label
        return f'{"int" if self.label is None else self.label} {name}'

    def _init_str(self):
        return "{}({})".format(self.__class__.__name__, (f'label="{self.label}"') if self.label else "")

    def copy(self):
        return SimTypeBottom(self.label)


class SimTypeTop(SimType):
    """
    SimTypeTop represents any type (mostly used with a pointer for void*).
    """

    _fields = ("size",)
    _args = ("size", "label")
    _ident = "top"

    def __init__(self, size: int | None = None, label=None):
        SimType.__init__(self, label)
        self._size = size

    def __repr__(self):
        return "TOP"

    def copy(self):
        return SimTypeTop(size=self.size, label=self.label)


class SimTypeReg(SimType):
    """
    SimTypeReg is the base type for all types that are register-sized.
    """

    _fields = ("size",)
    _args = ("size", "label")
    _ident = "reg"

    def __init__(self, size: int | None, label=None):
        """
        :param label: the type label.
        :param size: the size of the type (e.g. 32bit, 8bit, etc.).
        """
        SimType.__init__(self, label=label)
        self._size = size

    def __repr__(self):
        return f"reg{self.size}_t"

    def store(self, state, addr, value: StoreType):
        if self.size is None:
            raise TypeError("Need a size to store")
        store_endness = state.arch.memory_endness
        with contextlib.suppress(AttributeError):
            value = value.ast  # type: ignore
        if isinstance(value, claripy.ast.Bits):  # pylint:disable=isinstance-second-argument-not-valid-type
            if value.size() != self.size:  # type: ignore
                raise ValueError("size of expression is wrong size for type")
        elif isinstance(value, int):
            value = claripy.BVV(value, self.size)
        elif isinstance(value, bytes):
            store_endness = "Iend_BE"
        else:
            raise TypeError(f"unrecognized expression type for SimType {type(self).__name__}")

        state.memory.store(addr, value, endness=store_endness)

    def copy(self):
        return self.__class__(self.size, label=self.label)


class SimTypeNum(SimType):
    """
    SimTypeNum is a numeric type of arbitrary length
    """

    _fields = (*SimType._fields, "signed", "size")
    _args = ("size", "signed", "label")
    _ident = "num"

    def __init__(self, size: int, signed=True, label=None):
        """
        :param size:        The size of the integer, in bits
        :param signed:      Whether the integer is signed or not
        :param label:       A label for the type
        """
        super().__init__(label)
        self._size = size
        self.signed = signed

    @property
    def size(self) -> int:
        assert self._size is not None
        return self._size

    def __repr__(self):
        return "{}int{}_t".format("" if self.signed else "u", self.size)

    @overload
    def extract(self, state, addr, concrete: Literal[False] = ...) -> claripy.ast.BV: ...

    @overload
    def extract(self, state, addr, concrete: Literal[True]) -> int: ...

    def extract(self, state, addr, concrete=False):
        out = state.memory.load(addr, self.size // state.arch.byte_width, endness=state.arch.memory_endness)
        if not concrete:
            return out
        n = state.solver.eval(out)
        if self.signed and n >= 1 << (self.size - 1):
            n -= 1 << (self.size)
        return n

    def store(self, state, addr, value: StoreType):
        store_endness = state.arch.memory_endness

        if isinstance(value, claripy.ast.Bits):  # pylint:disable=isinstance-second-argument-not-valid-type
            if value.size() != self.size:  # type: ignore
                raise ValueError("size of expression is wrong size for type")
        elif isinstance(value, int) and self.size is not None:
            value = claripy.BVV(value, self.size)
        elif isinstance(value, bytes):
            store_endness = "Iend_BE"
        else:
            raise TypeError(f"unrecognized expression type for SimType {type(self).__name__}")

        state.memory.store(addr, value, endness=store_endness)

    def copy(self):
        return SimTypeNum(self.size, signed=self.signed, label=self.label)


class SimTypeInt(SimTypeReg):
    """
    SimTypeInt is a type that specifies a signed or unsigned C integer.
    """

    _fields = (*tuple(x for x in SimTypeReg._fields if x != "size"), "signed")
    _args = ("signed", "label")
    _base_name = "int"
    _ident = "int"

    def __init__(self, signed=True, label=None):
        """
        :param signed:  True if signed, False if unsigned
        :param label:   The type label
        """
        super().__init__(None, label=label)
        self.signed = signed

    def to_json(self, fields: Iterable[str] | None = None, memo: dict[str, SimTypeRef] | None = None) -> dict[str, Any]:
        if memo is None:
            memo = {}
        d = super().to_json(fields=fields, memo=memo)
        if "signed" in d and d["signed"] is True:
            del d["signed"]
        return d

    def c_repr(
        self, name=None, full=0, memo=None, indent=0, name_parens: bool = True
    ):  # pylint: disable=unused-argument
        out = self._base_name
        if not self.signed:
            out = "unsigned " + out
        if name is None:
            return out
        return f"{out} {name}"

    def __repr__(self):
        name = self._base_name
        if not self.signed:
            name = "unsigned " + name

        try:
            return f"{name} ({self.size} bits)"
        except ValueError:
            return name

    @property
    def size(self):
        if self._arch is None:
            raise ValueError("Can't tell my size without an arch!")
        try:
            return self._arch.sizeof[self._base_name]
        except KeyError as e:
            raise ValueError(f"Arch {self._arch.name} doesn't have its {self._base_name} type defined!") from e

    @overload
    def extract(self, state, addr, concrete: Literal[False] = ...) -> claripy.ast.BV: ...

    @overload
    def extract(self, state, addr, concrete: Literal[True]) -> int: ...

    def extract(self, state, addr, concrete=False):
        out = state.memory.load(addr, self.size // state.arch.byte_width, endness=state.arch.memory_endness)
        if not concrete:
            return out
        n = state.solver.eval(out)
        if self.signed and n >= 1 << (self.size - 1):
            n -= 1 << self.size
        return n

    def _init_str(self):
        return "{}(signed={}{})".format(
            self.__class__.__name__,
            self.signed,
            (f', label="{self.label}"') if self.label is not None else "",
        )

    def _refine_dir(self):
        return ["signed", "unsigned"]

    def _refine(self, view, k):
        if k == "signed":
            ty = copy.copy(self)
            ty.signed = True
        elif k == "unsigned":
            ty = copy.copy(self)
            ty.signed = False
        else:
            raise KeyError(k)
        return view._deeper(ty=ty)

    def copy(self):
        return self.__class__(signed=self.signed, label=self.label)


class SimTypeShort(SimTypeInt):
    _base_name = "short"
    _ident = "short"


class SimTypeLong(SimTypeInt):
    _base_name = "long"
    _ident = "long"


class SimTypeLongLong(SimTypeInt):
    _base_name = "long long"
    _ident = "llong"


class SimTypeFixedSizeInt(SimTypeInt):
    """
    The base class for all fixed-size (i.e., the size stays the same on all platforms) integer types. Do not
    instantiate this class directly.
    """

    _args = ("signed", "label")
    _base_name: str = "int"
    _ident = "intfixedsize"
    _fixed_size: int = 32

    def c_repr(
        self,
        name=None,
        full=0,
        memo=None,
        indent: int | None = 0,
        name_parens: bool = True,  # pylint:disable=unused-argument
    ):
        out = self._base_name
        if not self.signed:
            out = "u" + out
        if name is None:
            return out
        return f"{out} {name}"

    def __repr__(self) -> str:
        name = self._base_name
        if not self.signed:
            name = "u" + name

        try:
            return f"{name} ({self.size} bits)"
        except ValueError:
            return name

    @property
    def size(self) -> int:
        return self._fixed_size


class SimTypeInt128(SimTypeFixedSizeInt):
    _base_name = "int128_t"
    _ident = "int128"
    _fixed_size = 128


class SimTypeInt256(SimTypeFixedSizeInt):
    _base_name = "int256_t"
    _ident = "int256"
    _fixed_size = 256


class SimTypeInt512(SimTypeFixedSizeInt):
    _base_name = "int512_t"
    _ident = "int512"
    _fixed_size = 512


class SimTypeChar(SimTypeReg):
    """
    SimTypeChar is a type that specifies a character;
    this could be represented by a byte, but this is meant to be interpreted as a character.
    """

    _base_name = "char"
    _args = ("signed", "label")
    _ident = "char"

    def __init__(self, signed=True, label=None):
        """
        :param label: the type label.
        """
        # FIXME: Now the size of a char is state-dependent.
        super().__init__(8, label=label)
        self.signed = signed

    def __repr__(self) -> str:
        return "char"

    def store(self, state, addr, value: StoreType):
        # FIXME: This is a hack.
        self._size = state.arch.byte_width
        try:
            super().store(state, addr, value)
        except TypeError:
            if isinstance(value, bytes) and len(value) == 1:
                value = claripy.BVV(value[0], state.arch.byte_width)
                super().store(state, addr, value)
            else:
                raise

    @overload
    def extract(self, state, addr, concrete: Literal[False] = ...) -> claripy.ast.BV: ...

    @overload
    def extract(self, state, addr, concrete: Literal[True]) -> bytes: ...

    def extract(self, state, addr, concrete: bool = False) -> claripy.ast.BV | bytes:
        # FIXME: This is a hack.
        self._size = state.arch.byte_width

        out = state.memory.load(addr, 1, endness=state.arch.memory_endness)
        if concrete:
            return bytes(cast(list[int], [state.solver.eval(out)]))
        return out

    def _init_str(self):
        return "{}({})".format(
            self.__class__.__name__,
            (f'label="{self.label}"') if self.label is not None else "",
        )

    def copy(self):
        return self.__class__(signed=self.signed, label=self.label)


class SimTypeWideChar(SimTypeReg):
    """
    SimTypeWideChar is a type that specifies a wide character (a UTF-16 character).
    """

    _args = ("signed", "label", "endness")
    _base_name = "char"
    _ident = "wchar"

    def __init__(self, signed=True, label=None, endness: Endness = Endness.BE):
        """
        :param label: the type label.
        """
        SimTypeReg.__init__(self, 16, label=label)
        self.signed = signed
        self.endness = endness

    def __repr__(self):
        return "wchar"

    def store(self, state, addr, value: StoreType):
        try:
            super().store(state, addr, value)
        except TypeError:
            if isinstance(value, bytes) and len(value) == 2:
                inner = (
                    ((value[0] << state.arch.byte_width) | value[1])
                    if self.endness == Endness.BE
                    else ((value[1] << state.arch.byte_width) | value[0])
                )
                value = claripy.BVV(inner, state.arch.byte_width * 2)
                super().store(state, addr, value)
            else:
                raise

    def extract(self, state, addr, concrete=False) -> Any:
        out = state.memory.load(addr, 2)
        if concrete:
            data = state.solver.eval(out, cast_to=bytes)
            fmt_str = "utf-16be" if self.endness == Endness.BE else "utf-16le"
            try:
                return data.decode(fmt_str)
            except UnicodeDecodeError:
                return data
        return out

    def _init_str(self):
        return "{}({})".format(
            self.__class__.__name__,
            (f'label="{self.label}"') if self.label is not None else "",
        )

    def copy(self):
        return self.__class__(signed=self.signed, label=self.label, endness=self.endness)


class SimTypeBool(SimTypeReg):
    _args = ("signed", "label")
    _base_name = "bool"
    _ident = "bool"

    def __init__(self, signed=True, label=None):
        """
        :param label: the type label.
        """
        # FIXME: Now the size of a char is state-dependent.
        super().__init__(8, label=label)
        self.signed = signed

    def __repr__(self):
        return "bool"

    def store(self, state, addr, value: StoreType | bool):
        if isinstance(value, bool):
            value = int(value)
        return super().store(state, addr, value)

    @overload
    def extract(self, state, addr, concrete: Literal[False] = ...) -> claripy.ast.Bool: ...

    @overload
    def extract(self, state, addr, concrete: Literal[True]) -> bool: ...

    def extract(self, state, addr, concrete=False):
        ver = super().extract(state, addr, concrete)
        if concrete:
            return ver != b"\0"
        return ver != 0

    def _init_str(self):
        return f"{self.__class__.__name__}()"

    def copy(self):
        return self.__class__(signed=self.signed, label=self.label)


class SimTypeFd(SimTypeReg):
    """
    SimTypeFd is a type that specifies a file descriptor.
    """

    _fields = SimTypeReg._fields
    _args = ("label",)
    _ident = "fd"

    def __init__(self, label=None):
        """
        :param label: the type label
        """
        # file descriptors are always 32 bits, right?
        # TODO: That's so closed-minded!
        super().__init__(32, label=label)

    @property
    def size(self):
        return 32

    def __repr__(self):
        return "fd_t"

    def copy(self):
        return SimTypeFd(label=self.label)

    def _init_str(self):
        return "{}({})".format(
            self.__class__.__name__,
            (f'label="{self.label}"') if self.label is not None else "",
        )

    @overload
    def extract(self, state, addr, concrete: Literal[False] = ...) -> claripy.ast.BV: ...

    @overload
    def extract(self, state, addr, concrete: Literal[True]) -> int: ...

    def extract(self, state, addr, concrete=False):
        # TODO: EDG says this looks dangerously closed-minded. Just in case...
        assert self.size % state.arch.byte_width == 0

        out = state.memory.load(addr, self.size // state.arch.byte_width, endness=state.arch.memory_endness)
        if not concrete:
            return out
        return state.solver.eval(out)


class SimTypePointer(SimTypeReg):
    """
    SimTypePointer is a type that specifies a pointer to some other type.
    """

    _fields = (*tuple(x for x in SimTypeReg._fields if x != "size"), "pts_to")
    _args = ("pts_to", "label", "offset")
    _ident = "ptr"

    def __init__(self, pts_to, label=None, offset=0):
        """
        :param label:   The type label.
        :param pts_to:  The type to which this pointer points.
        """
        super().__init__(None, label=label)
        self.pts_to = pts_to
        self.signed = False
        self.offset = offset

    def to_json(self, fields: Iterable[str] | None = None, memo: dict[str, SimTypeRef] | None = None) -> dict[str, Any]:
        if memo is None:
            memo = {}
        d = super().to_json(fields=fields, memo=memo)
        if d["offset"] == 0:
            d.pop("offset")
        return d

    def __repr__(self):
        return f"{self.pts_to}*" if not self.label else self.label

    def c_repr(
        self, name=None, full=0, memo=None, indent=0, name_parens: bool = True
    ):  # pylint: disable=unused-argument
        # if pts_to is SimTypeBottom, we return a void*
        if self.label is not None and name is not None:
            return super().c_repr(name=name, full=full, memo=memo, indent=indent, name_parens=name_parens)
        if isinstance(self.pts_to, SimTypeBottom):
            out = "void*"
            if name is None:
                return out
            return f"{out} {name}"
        # if it points to an array, we do not need to add a *
        deref_chr = "*" if not isinstance(self.pts_to, SimTypeArray) else ""
        name_with_deref = deref_chr if name is None else f"{deref_chr}{name}"
        return self.pts_to.c_repr(name_with_deref, full, memo, indent)

    def make(self, pts_to):
        new = type(self)(pts_to)
        new._arch = self._arch
        return new

    @property
    def size(self):
        if self._arch is None:
            raise ValueError("Can't tell my size without an arch!")
        return self._arch.bits

    def _with_arch(self, arch):
        out = SimTypePointer(self.pts_to.with_arch(arch), self.label)
        out._arch = arch
        return out

    def _init_str(self):
        label_str = f', label="{self.label}"' if self.label is not None else ""
        return f"{self.__class__.__name__}({self.pts_to._init_str()}{label_str}, offset={self.offset})"

    def copy(self):
        return SimTypePointer(self.pts_to, label=self.label, offset=self.offset)

    @overload
    def extract(self, state, addr, concrete: Literal[False] = ...) -> claripy.ast.BV: ...

    @overload
    def extract(self, state, addr, concrete: Literal[True]) -> int: ...

    def extract(self, state, addr, concrete=False):
        # TODO: EDG says this looks dangerously closed-minded. Just in case...
        assert self.size % state.arch.byte_width == 0

        out = state.memory.load(addr, self.size // state.arch.byte_width, endness=state.arch.memory_endness)
        if not concrete:
            return out
        return state.solver.eval(out)


class SimTypeReference(SimTypeReg):
    """
    SimTypeReference is a type that specifies a reference to some other type.
    """

    _args = ("refs", "label")
    _ident = "ref"

    def __init__(self, refs, label=None):
        super().__init__(None, label=label)
        self.refs: SimType = refs

    def __repr__(self):
        return f"{self.refs}&"

    def c_repr(
        self, name=None, full=0, memo=None, indent=0, name_parens: bool = True
    ):  # pylint: disable=unused-argument
        name = "&" if name is None else f"&{name}"
        return self.refs.c_repr(name, full, memo, indent)

    def make(self, refs):
        new = type(self)(refs)
        new._arch = self._arch
        return new

    @property
    def size(self):
        if self._arch is None:
            raise ValueError("Can't tell my size without an arch!")
        return self._arch.bits

    def _with_arch(self, arch):
        out = SimTypeReference(self.refs.with_arch(arch), label=self.label)
        out._arch = arch
        return out

    def _init_str(self):
        return "{}({}{})".format(
            self.__class__.__name__,
            self.refs._init_str(),
            (f', label="{self.label}"') if self.label is not None else "",
        )

    def copy(self):
        return SimTypeReference(self.refs, label=self.label)

    @overload
    def extract(self, state, addr, concrete: Literal[False] = ...) -> claripy.ast.BV: ...

    @overload
    def extract(self, state, addr, concrete: Literal[True]) -> int: ...

    def extract(self, state, addr, concrete=False):
        # TODO: EDG says this looks dangerously closed-minded. Just in case...
        assert self.size % state.arch.byte_width == 0

        out = state.memory.load(addr, self.size // state.arch.byte_width, endness=state.arch.memory_endness)
        if not concrete:
            return out
        return state.solver.eval(out)


class SimTypeArray(SimType):
    """
    SimTypeArray is a type that specifies a series of data laid out in sequence.
    """

    _fields = ("elem_type", "length")
    _args = ("elem_type", "length", "label")
    _ident = "array"

    def __init__(self, elem_type, length=None, label=None):
        """
        :param label:       The type label.
        :param elem_type:   The type of each element in the array.
        :param length:      An expression of the length of the array, if known.
        """
        super().__init__(label=label)
        self.elem_type: SimType = elem_type
        self.length: int | None = length

    def __repr__(self):
        return "{}[{}]".format(self.elem_type, "" if self.length is None else self.length)

    def c_repr(
        self, name=None, full=0, memo=None, indent=0, name_parens: bool = True
    ):  # pylint: disable=unused-argument
        if name is None:
            return repr(self)

        name = "{}[{}]".format(name, self.length if self.length is not None else "")
        return self.elem_type.c_repr(name, full, memo, indent)

    @property
    def size(self):
        if self.length is None:
            return 0
        if self.elem_type.size is None:
            return None
        return self.elem_type.size * self.length

    @property
    def alignment(self):
        return self.elem_type.alignment

    def _with_arch(self, arch):
        out = SimTypeArray(self.elem_type.with_arch(arch), self.length, self.label)
        out._arch = arch
        return out

    def copy(self):
        return SimTypeArray(self.elem_type, length=self.length, label=self.label)

    _can_refine_int = True

    def _refine(self, view, k):
        return view._deeper(
            addr=view._addr + k * (self.elem_type.size // view.state.arch.byte_width), ty=self.elem_type
        )

    @overload
