# Copyright (c) Meta Platforms, Inc. and affiliates.
# SPDX-License-Identifier: GPL-3.0-or-later

import ctypes
import enum
from enum import auto
import os

import _drgn

_drgn_pydll = ctypes.PyDLL(_drgn.__file__)
_drgn_cdll = ctypes.CDLL(_drgn.__file__)


class _drgn_error(ctypes.Structure):
    _fields_ = [
        ("code", ctypes.c_uint),
        ("errnum", ctypes.c_int),
        ("path", ctypes.c_char_p),
        ("msg", ctypes.c_char_p),
    ]


_drgn_pydll.set_drgn_error.restype = ctypes.c_void_p
_drgn_pydll.set_drgn_error.argtypes = [ctypes.POINTER(_drgn_error)]


def _check_err(err):
    if err:
        _drgn_pydll.set_drgn_error(err)


class _path_iterator_component(ctypes.Structure):
    _fields_ = [
        ("path", ctypes.c_char_p),
        ("len", ctypes.c_size_t),
    ]


class _path_iterator(ctypes.Structure):
    _fields_ = [
        ("components", ctypes.POINTER(_path_iterator_component)),
        ("num_components", ctypes.c_size_t),
        ("dot_dot", ctypes.c_size_t),
    ]


_drgn_cdll.drgn_test_path_iterator_next.restype = ctypes.c_bool
_drgn_cdll.drgn_test_path_iterator_next.argtypes = [
    ctypes.POINTER(_path_iterator),
    ctypes.POINTER(ctypes.POINTER(ctypes.c_char)),
    ctypes.POINTER(ctypes.c_size_t),
]


class PathIterator:
    def __init__(self, *paths):
        components = (_path_iterator_component * len(paths))()
        for i, path in enumerate(paths):
            path = os.fsencode(path)
            components[i].path = path
            components[i].len = len(path)
        self._it = _path_iterator(components, len(paths))

    def __iter__(self):
        return self

    def __next__(self):
        component = ctypes.POINTER(ctypes.c_char)()
        component_len = ctypes.c_size_t()
        if _drgn_cdll.drgn_test_path_iterator_next(
            ctypes.pointer(self._it),
            ctypes.pointer(component),
            ctypes.pointer(component_len),
        ):
            return os.fsdecode(ctypes.string_at(component, component_len.value))
        else:
            raise StopIteration()


_drgn_cdll.drgn_test_path_ends_with.restype = ctypes.c_bool
_drgn_cdll.drgn_test_path_ends_with.argtypes = [
    ctypes.POINTER(_path_iterator),
    ctypes.POINTER(_path_iterator),
]


def path_ends_with(path1: PathIterator, path2: PathIterator):
    return _drgn_cdll.drgn_test_path_ends_with(
        ctypes.pointer(path1._it), ctypes.pointer(path2._it)
    )


class _drgn_type(ctypes.Structure):
    pass


class _drgn_qualified_type(ctypes.Structure):
    _fields_ = [
        ("type", ctypes.POINTER(_drgn_type)),
        ("qualifiers", ctypes.c_uint),
    ]


class _drgn_token(ctypes.Structure):
    _fields_ = [
        ("kind", ctypes.c_int),
        ("value", ctypes.c_void_p),
        ("len", ctypes.c_size_t),
    ]


class _drgn_lexer(ctypes.Structure):
    _fields_ = [
        ("func", ctypes.c_void_p),
        ("p", ctypes.c_void_p),
        ("stack", ctypes.POINTER(_drgn_token)),
        ("stack_len", ctypes.c_size_t),
        ("stack_capacity", ctypes.c_size_t),
    ]


class _drgn_c_family_lexer(ctypes.Structure):
    _fields_ = [
        ("lexer", _drgn_lexer),
        ("cpp", ctypes.c_bool),
    ]


drgn_lexer_func = ctypes.CFUNCTYPE(
    ctypes.POINTER(_drgn_error),
    ctypes.POINTER(_drgn_lexer),
    ctypes.POINTER(_drgn_token),
)


_drgn_cdll.drgn_test_lexer_init.restype = None
_drgn_cdll.drgn_test_lexer_init.argtypes = [
    ctypes.POINTER(_drgn_lexer),
    ctypes.POINTER(drgn_lexer_func),
    ctypes.c_char_p,
]
_drgn_cdll.drgn_test_lexer_deinit.restype = None
_drgn_cdll.drgn_test_lexer_deinit.argtypes = [ctypes.POINTER(_drgn_lexer)]
_drgn_cdll.drgn_test_lexer_pop.restype = ctypes.POINTER(_drgn_error)
_drgn_cdll.drgn_test_lexer_pop.argtypes = [
    ctypes.POINTER(_drgn_lexer),
    ctypes.POINTER(_drgn_token),
]
_drgn_cdll.drgn_test_lexer_push.restype = ctypes.POINTER(_drgn_error)
_drgn_cdll.drgn_test_lexer_push.argtypes = [
    ctypes.POINTER(_drgn_lexer),
    ctypes.POINTER(_drgn_token),
]
_drgn_cdll.drgn_test_lexer_peek.restype = ctypes.POINTER(_drgn_error)
_drgn_cdll.drgn_test_lexer_peek.argtypes = [
    ctypes.POINTER(_drgn_lexer),
    ctypes.POINTER(_drgn_token),
]


drgn_c_family_lexer_func = drgn_lexer_func.in_dll(_drgn_cdll, "drgn_test_lexer_c")
drgn_test_lexer_func = drgn_lexer_func.in_dll(_drgn_cdll, "drgn_test_lexer_func")


class C_TOKEN(enum.IntEnum):
    EOF = -1
    VOID = auto()
    CHAR = auto()
    SHORT = auto()
    INT = auto()
    LONG = auto()
    SIGNED = auto()
    UNSIGNED = auto()
    BOOL = auto()
    FLOAT = auto()
    DOUBLE = auto()
    COMPLEX = auto()
    CONST = auto()
    RESTRICT = auto()
    VOLATILE = auto()
    ATOMIC = auto()
    STRUCT = auto()
    UNION = auto()
    CLASS = auto()
    ENUM = auto()
    LPAREN = auto()
    RPAREN = auto()
    LBRACKET = auto()
    RBRACKET = auto()
    ASTERISK = auto()
    DOT = auto()
    NUMBER = auto()
    IDENTIFIER = auto()


class Token:
    def __init__(self, token):
        self._token = token

    @property
    def kind(self):
        return self._token.kind

    @property
    def value(self):
        return ctypes.string_at(self._token.value, self._token.len).decode()

    def __repr__(self):
        return f"Token({self.kind}, {self.value!r})"


class Lexer:
    def __init__(self, func, str, cpp=False):
        self._c_family_lexer = _drgn_c_family_lexer()
        self._lexer = self._c_family_lexer.lexer
        self._func = func
        self._str = str.encode()
        self._c_family_lexer.cpp = cpp
        _drgn_cdll.drgn_test_lexer_init(
            ctypes.pointer(self._lexer), self._func, self._str
        )

    def __del__(self):
        _drgn_cdll.drgn_test_lexer_deinit(ctypes.pointer(self._lexer))

    def pop(self):
        token = _drgn_token()
        _check_err(
            _drgn_cdll.drgn_test_lexer_pop(
                ctypes.pointer(self._lexer), ctypes.pointer(token)
            )
        )
        return Token(token)

    def push(self, token):
        _check_err(
            _drgn_cdll.drgn_test_lexer_push(
                ctypes.pointer(self._lexer), ctypes.pointer(token._token)
            )
        )

    def peek(self):
        token = _drgn_token()
        _check_err(
            _drgn_cdll.drgn_test_lexer_peek(
                ctypes.pointer(self._lexer), ctypes.pointer(token)
            )
        )
        return Token(token)


_drgn_cdll.drgn_test_serialize_bits.restype = None
_drgn_cdll.drgn_test_serialize_bits.argtypes = [
    ctypes.c_void_p,
    ctypes.c_uint64,
    ctypes.c_uint64,
    ctypes.c_uint8,
    ctypes.c_bool,
]
_drgn_cdll.drgn_test_deserialize_bits.restype = ctypes.c_uint64
_drgn_cdll.drgn_test_deserialize_bits.argtypes = [
    ctypes.c_void_p,
    ctypes.c_uint64,
    ctypes.c_uint8,
    ctypes.c_bool,
]


def serialize_bits(buf, bit_offset, uvalue, bit_size, little_endian):
    assert (bit_offset + bit_size + 7) // 8 <= len(buf)
    c_buf = (ctypes.c_char * len(buf)).from_buffer(buf)
    return _drgn_cdll.drgn_test_serialize_bits(
        c_buf, bit_offset, uvalue, bit_size, little_endian
    )


def deserialize_bits(buf, bit_offset, bit_size, little_endian):
    assert (bit_offset + bit_size + 7) // 8 <= len(buf)
    c_buf = (ctypes.c_char * len(buf)).from_buffer_copy(buf)
    return _drgn_cdll.drgn_test_deserialize_bits(
        c_buf, bit_offset, bit_size, little_endian
    )
