"""Mapping between slot / operator names.

This defines the internal constants CPython uses to map magic methods to slots
of PyTypeObject structures, and also other constants, like compare operator
mappings.
"""

import dataclasses
from typing import List, Optional

TYPEOBJECT_PREFIX = "tp_"
NUMBER_PREFIX = "nb_"
SEQUENCE_PREFIX = "sq_"
MAPPING_PREFIX = "mp_"


@dataclasses.dataclass
class Slot:
  """A "slot" describes a Python operator.

  In particular, it describes how a magic method (E.g. "__add__") relates to the
  opcode ("BINARY_ADD") and the C function pointer ("nb_add").

  Attributes:
    python_name: The name of the Python method. E.g. "__add__".
    c_name: The name of the C function pointer. Only use the base name, e.g.
      for tp_as_number->nb_add, use nb_add.
    function_type: Type of the C function
    index: If multiple python methods share the same function pointer
      (e.g. __add__ and __radd__), this is 0 or 1.
    opcode: The name of the opcode that CPython uses to call this
      function. This is only filled in for operators (e.g. BINARY_SUBSCR),
      but not for operations (e.g. STORE_SUBSCR).
    python_version: "2", "3", or (default) "*".
    symbol: Only filled in for operators. The corresponding symbol, e.g., "+"
      for __add__, if one exists; otherwise, a human-friendly description, e.g.,
      "item retrieval" for __getitem__.
  """

  python_name: str
  c_name: str
  function_type: str
  index: Optional[int] = None
  opcode: Optional[str] = None
  python_version: Optional[str] = "*"
  symbol: Optional[str] = None


SLOTS: List[Slot] = [
    # typeobject
    Slot("__new__", "tp_new", "new"),
    Slot("__init__", "tp_init", "init"),
    Slot("__str__", "tp_print", "print"),
    Slot("__repr__", "tp_repr", "repr",
         opcode="UNARY_CONVERT"),

    Slot("__hash__", "tp_hash", "hash"),
    Slot("__call__", "tp_call", "call"),

    # Note: In CPython, if tp_getattro exists, tp_getattr is never called.
    Slot("__getattribute__", "tp_getattro", "getattro"),
    Slot("__getattr__", "tp_getattro", "getattro"),
    Slot("__setattr__", "tp_setattro", "setattro"),
    Slot("__delattr__", "tp_setattro", "setattro"),

    # for Py_TPFLAGS_HAVE_ITER:
    Slot("__iter__", "tp_iter", "unary"),
    Slot("next", "tp_iternext", "next", python_version="2"),
    Slot("__next__", "tp_iternext", "next", python_version="3"),

    # for Py_TPFLAGS_HAVE_CLASS:
    Slot("__get__", "tp_descr_get", "descr_get"),
    Slot("__set__", "tp_descr_set", "descr_set"),
    Slot("__delete__", "tp_descr_set", "descr_delete"),
    Slot("__del__", "tp_del", "destructor"),

    # all typically done by __richcompare__
    Slot("__cmp__", "tp_compare", "cmp",
         python_version="2"),  # "tp_reserved" in Python 3
    Slot("__lt__", "tp_richcompare", "richcmpfunc"),
    Slot("__le__", "tp_richcompare", "richcmpfunc"),
    Slot("__eq__", "tp_richcompare", "richcmpfunc"),
    Slot("__ne__", "tp_richcompare", "richcmpfunc"),
    Slot("__gt__", "tp_richcompare", "richcmpfunc"),
    Slot("__ge__", "tp_richcompare", "richcmpfunc"),

    Slot("__richcompare__", "tp_richcompare", "richcmpfunc"),

    # number methods:
    Slot("__add__", "nb_add", "binary_nb", index=0,
         opcode="BINARY_ADD", symbol="+"),
    Slot("__radd__", "nb_add", "binary_nb", index=1),
    Slot("__sub__", "nb_subtract", "binary_nb", index=0,
         opcode="BINARY_SUBTRACT", symbol="-"),
    Slot("__rsub__", "nb_subtract", "binary_nb", index=1),
    Slot("__mul__", "nb_multiply", "binary_nb", index=0, symbol="*"),
    Slot("__rmul__", "nb_multiply", "binary_nb", index=1),
    Slot("__div__", "nb_divide", "binary_nb", index=0,
         opcode="BINARY_DIVIDE", symbol="/"),
    Slot("__rdiv__", "nb_divide", "binary_nb", index=1),
    Slot("__mod__", "nb_remainder", "binary_nb", index=0,
         opcode="BINARY_MODULO", symbol="%"),
    Slot("__rmod__", "nb_remainder", "binary_nb", index=1),
    Slot("__divmod__", "nb_divmod", "binary_nb", index=0),
    Slot("__rdivmod__", "nb_divmod", "binary_nb", index=1),
    Slot("__lshift__", "nb_lshift", "binary_nb", index=0,
         opcode="BINARY_LSHIFT", symbol="<<"),
    Slot("__rlshift__", "nb_lshift", "binary_nb", index=1),
    Slot("__rshift__", "nb_rshift", "binary_nb", index=0,
         opcode="BINARY_RSHIFT", symbol=">>"),
    Slot("__rrshift__", "nb_rshift", "binary_nb", index=1),
    Slot("__and__", "nb_and", "binary_nb", index=0,
         opcode="BINARY_AND", symbol="&"),
    Slot("__rand__", "nb_and", "binary_nb", index=1),
    Slot("__xor__", "nb_xor", "binary_nb", index=0,
         opcode="BINARY_XOR", symbol="^"),
    Slot("__rxor__", "nb_xor", "binary_nb", index=1),
    Slot("__or__", "nb_or", "binary_nb", index=0,
         opcode="BINARY_OR", symbol="|"),
    Slot("__ror__", "nb_or", "binary_nb", index=1),
    # needs Py_TPFLAGS_HAVE_CLASS:
    Slot("__floordiv__", "nb_floor_divide", "binary_nb", index=0,
         opcode="BINARY_FLOOR_DIVIDE", symbol="//"),
    Slot("__rfloordiv__", "nb_floor_divide", "binary_nb", index=1),
    Slot("__truediv__", "nb_true_divide", "binary_nb", index=0,
         opcode="BINARY_TRUE_DIVIDE", symbol="/"),
    Slot("__rtruediv__", "nb_true_divide", "binary_nb", index=1),

    Slot("__pow__", "nb_power", "ternary", index=0,
         opcode="BINARY_POWER", symbol="**"),
    Slot("__rpow__", "nb_power", "ternary", index=1),  # needs wrap_tenary_nb

    Slot("__neg__", "nb_negative", "unary",
         opcode="UNARY_NEGATIVE", symbol="-"),
    Slot("__pos__", "nb_positive", "unary",
         opcode="UNARY_POSITIVE", symbol="+"),
    Slot("__abs__", "nb_absolute", "unary"),
    Slot("__nonzero__", "nb_nonzero", "inquiry"),  # inverse of UNARY_NOT opcode
    Slot("__invert__", "nb_invert", "unary",
         opcode="UNARY_INVERT", symbol="~"),
    Slot("__coerce__", "nb_coerce", "coercion"),  # not needed
    Slot("__int__", "nb_int", "unary"),  # expects exact int as return
    Slot("__long__", "nb_long", "unary"),  # expects exact long as return
    Slot("__float__", "nb_float", "unary"),  # expects exact float as return
    Slot("__oct__", "nb_oct", "unary"),
    Slot("__hex__", "nb_hex", "unary"),

    # Added in 2.0.  These are probably largely useless.
    # (For list concatenation, use sl_inplace_concat)
    Slot("__iadd__", "nb_inplace_add", "binary",
         opcode="INPLACE_ADD", symbol="+="),
    Slot("__isub__", "nb_inplace_subtract", "binary",
         opcode="INPLACE_SUBTRACT", symbol="-="),
    Slot("__imul__", "nb_inplace_multiply", "binary",
         opcode="INPLACE_MULTIPLY", symbol="*="),
    Slot("__idiv__", "nb_inplace_divide", "binary",
         opcode="INPLACE_DIVIDE", symbol="/="),
    Slot("__imod__", "nb_inplace_remainder", "binary",
         opcode="INPLACE_MODULO", symbol="%="),
    Slot("__ipow__", "nb_inplace_power", "ternary",
         opcode="INPLACE_POWER", symbol="**="),
    Slot("__ilshift__", "nb_inplace_lshift", "binary",
         opcode="INPLACE_LSHIFT", symbol="<<="),
    Slot("__irshift__", "nb_inplace_rshift", "binary",
         opcode="INPLACE_RSHIFT", symbol=">>="),
    Slot("__iand__", "nb_inplace_and", "binary",
         opcode="INPLACE_AND", symbol="&="),
    Slot("__ixor__", "nb_inplace_xor", "binary",
         opcode="INPLACE_XOR", symbol="^="),
    Slot("__ior__", "nb_inplace_or", "binary",
         opcode="INPLACE_OR", symbol="|="),
    Slot("__ifloordiv__", "nb_inplace_floor_divide", "binary",
         opcode="INPLACE_FLOOR_DIVIDE", symbol="//="),
    Slot("__itruediv__", "nb_inplace_true_divide", "binary",
         opcode="INPLACE_TRUE_DIVIDE", symbol="/="),

    # matrix methods:
    # Added in 3.5
    Slot("__matmul__", "nb_matrix_multiply", "binary_nb", index=0,
         opcode="BINARY_MATRIX_MULTIPLY", symbol="@"),
    Slot("__rmatmul__", "nb_matrix_multiply", "binary_nb", index=1),
    Slot("__imatmul__", "nb_inplace_matrix_multiply", "binary",
         opcode="INPLACE_MATRIX_MULTIPLY", symbol="@="),

    # Added in 2.5. Used whenever i acts as a sequence index (a[i])
    Slot("__index__", "nb_index", "unary"),  # needs int/long return

    # mapping
    # __getitem__: Python first tries mp_subscript, then sq_item
    # __len__: Python first tries sq_length, then mp_length
    # __delitem__: Reuses __setitem__ slot.
    Slot("__getitem__", "mp_subscript", "binary",
         opcode="BINARY_SUBSCR", symbol="item retrieval"),

    # In CPython, these two share a slot:
    Slot("__delitem__", "mp_ass_subscript", "objobjargproc",
         symbol="item deletion"),
    Slot("__setitem__", "mp_ass_subscript", "objobjargproc",
         symbol="item assignment"),

    Slot("__len__", "mp_length", "len"),

    # sequence
    Slot("__contains__", "sq_contains", "objobjproc", symbol="'in'"),

    # These sequence methods are duplicates of number or mapping methods.
    # For example, in the C API, "add" can be implemented either by sq_concat,
    # or by np_add.  Python will try both. The opcode mapping is identical
    # between the two. So e.g. the implementation of the BINARY_SUBSCR opcode in
    # Python/ceval.c will try both sq_item and mp_subscript, which is why this
    # opcode appears twice in our list.
    Slot("__add__", "sq_concat", "binary",
         opcode="BINARY_ADD", symbol="+"),
    Slot("__mul__", "sq_repeat", "indexargfunc",
         opcode="BINARY_MULTIPLY", symbol="*"),
    Slot("__iadd__", "sq_inplace_concat", "binary",
         opcode="INPLACE_ADD", symbol="+="),
    Slot("__imul__", "sq_inplace_repeat", "indexargfunc",
         opcode="INPLACE_MUL", symbol="*="),
    Slot("__getitem__", "sq_item", "sq_item",
         opcode="BINARY_SUBSCR", symbol="item retrieval"),
    Slot("__setitem__", "sq_ass_slice", "sq_ass_item",
         symbol="item assignment"),
    Slot("__delitem__", "sq_ass_item", "sq_delitem", symbol="item deletion"),

    # slices are passed as explicit slice objects to mp_subscript.
    Slot("__getslice__", "sq_slice", "sq_slice", symbol="slicing"),
    Slot("__setslice__", "sq_ass_slice", "ssizessizeobjarg"),
    Slot("__delslice__", "sq_ass_slice", "delslice"),
]


CMP_LT = 0
CMP_LE = 1
CMP_EQ = 2
CMP_NE = 3
CMP_GT = 4
CMP_GE = 5
CMP_IN = 6
CMP_NOT_IN = 7
CMP_IS = 8
CMP_IS_NOT = 9
CMP_EXC_MATCH = 10


CMP_ALWAYS_SUPPORTED = frozenset({
    CMP_EQ, CMP_NE, CMP_IS, CMP_IS_NOT
})


EQ, NE, LT, LE, GT, GE = "==", "!=", "<", "<=", ">", ">="
COMPARES = {
    EQ: lambda x, y: x == y,
    NE: lambda x, y: x != y,
    LT: lambda x, y: x < y,
    LE: lambda x, y: x <= y,
    GT: lambda x, y: x > y,
    GE: lambda x, y: x >= y,
}


SYMBOL_MAPPING = {slot.python_name: slot.symbol
                  for slot in SLOTS if slot.symbol}


def _ReverseNameMapping():
  """__add__ -> __radd__, __mul__ -> __rmul__ etc."""
  c_name_to_reverse = {slot.c_name: slot.python_name
                       for slot in SLOTS
                       if slot.index == 1}
  return {slot.python_name: c_name_to_reverse[slot.c_name]
          for slot in SLOTS if slot.index == 0}


REVERSE_NAME_MAPPING = _ReverseNameMapping()
