# SPDX-License-Identifier: MIT and GPL-3.0-only
# Sourced from library python-ioctl
# https://github.com/olavmrk/python-ioctl
import ctypes
import platform


class _IoctlGeneric(object):
    _IOC_NRBITS = 8
    _IOC_TYPEBITS = 8
    _IOC_SIZEBITS = 14
    _IOC_DIRBITS = 2
    _IOC_NONE = 0
    _IOC_WRITE = 1
    _IOC_READ = 2

    @classmethod
    def ioc(cls, direction, request_type, request_nr, size):
        _IOC_NRSHIFT = 0
        _IOC_TYPESHIFT = _IOC_NRSHIFT + cls._IOC_NRBITS
        _IOC_SIZESHIFT = _IOC_TYPESHIFT + cls._IOC_TYPEBITS
        _IOC_DIRSHIFT = _IOC_SIZESHIFT + cls._IOC_SIZEBITS
        return (
            (direction << _IOC_DIRSHIFT)
            | (request_type << _IOC_TYPESHIFT)
            | (request_nr << _IOC_NRSHIFT)
            | (size << _IOC_SIZESHIFT)
        )


class _IoctlAlpha(_IoctlGeneric):
    _IOC_NRBITS = 8
    _IOC_TYPEBITS = 8
    _IOC_SIZEBITS = 13
    _IOC_DIRBITS = 3
    _IOC_NONE = 1
    _IOC_READ = 2
    _IOC_WRITE = 4


class _IoctlMips(_IoctlGeneric):
    _IOC_SIZEBITS = 13
    _IOC_DIRBITS = 3
    _IOC_NONE = 1
    _IOC_READ = 2
    _IOC_WRITE = 4


class _IoctlParisc(_IoctlGeneric):
    _IOC_NONE = 0
    _IOC_WRITE = 2
    _IOC_READ = 1


class _IoctlPowerPC(_IoctlGeneric):
    _IOC_SIZEBITS = 13
    _IOC_DIRBITS = 3
    _IOC_NONE = 1
    _IOC_READ = 2
    _IOC_WRITE = 4


class _IoctlSparc(_IoctlGeneric):
    _IOC_NRBITS = 8
    _IOC_TYPEBITS = 8
    _IOC_SIZEBITS = 13
    _IOC_DIRBITS = 3
    _IOC_NONE = 1
    _IOC_READ = 2
    _IOC_WRITE = 4


_machine_ioctl_map = {
    "alpha": _IoctlAlpha,
    "mips": _IoctlMips,
    "mips64": _IoctlMips,
    "parisc": _IoctlParisc,
    "parisc64": _IoctlParisc,
    "ppc": _IoctlPowerPC,
    "ppcle": _IoctlPowerPC,
    "ppc64": _IoctlPowerPC,
    "ppc64le": _IoctlPowerPC,
    "sparc": _IoctlSparc,
    "sparc64": _IoctlSparc,
}


def _machine_ioctl_calculator():
    machine = platform.machine()
    return _machine_ioctl_map.get(machine, _IoctlGeneric)


def _ioc_type_size(size):
    if isinstance(size, type) and issubclass(size, ctypes._SimpleCData):
        return ctypes.sizeof(size)
    elif isinstance(size, int):
        return size
    else:
        raise TypeError(
            "Invalid type for size: {size_type}".format(
                size_type=size.__class__.__name__
            )
        )


def _ioc_request_type(request_type):
    if isinstance(request_type, int):
        return request_type
    if isinstance(request_type, str):
        if len(request_type) > 1:
            raise ValueError("request_type string too long.")
        elif len(request_type) == 0:
            raise ValueError("request_type cannot be an empty string.")
        return ord(request_type)
    else:
        raise ValueError(
            "request_type must be an integer or a string, but was: {request_type_type}".format(
                request_type_type=request_type.__class__.__name__
            )
        )


def _IOC(direction, request_type, request_nr, size):
    """Python implementation of the ``_IOC(...)`` macro from Linux.

    This is a portable implementation of the ``_IOC(...)`` macro from Linux.
    It takes a set of parameters, and calculates a ioctl request number based on those parameters.

    :param direction: Direction of data transfer in this ioctl. This can be one of:

                      * ``None``: No data transfer.
                      * ``'r'``: Read-only (input) data.
                      * ``'w'``: Write-only (output) data.
                      * ``'rw'``: Read-write (input and output) data.
    :param request_type: The ioctl request type. This can be specified as either a string ``'R'`` or an integer ``123``.
    :param request_nr: The ioctl request number. This is an integer.
    :param size: The number of data bytes transferred in this ioctl.
    :return: The calculated ioctl request number.
    """

    calc = _machine_ioctl_calculator()

    if direction is None:
        direction = calc._IOC_NONE
    elif direction == "r":
        direction = calc._IOC_READ
    elif direction == "w":
        direction = calc._IOC_WRITE
    elif direction == "rw":
        direction = calc._IOC_READ | calc._IOC_WRITE
    else:
        raise ValueError("direction must be None, 'r', 'w' or 'rw'.")

    request_type = _ioc_request_type(request_type)
    return calc.ioc(direction, request_type, request_nr, size)


def _IO(request_type, request_nr):
    """Python implementation of the ``_IO(...)`` macro from Linux.

    This is a portable implementation of the ``_IO(...)`` macro from Linux.
    The ``_IO(...)`` macro calculates a ioctl request number for ioctl request that do not transfer any data.

    :param request_type: The ioctl request type. This can be specified as either a string ``'R'`` or an integer ``123``.
    :param request_nr: The ioctl request number. This is an integer.
    :return: The calculated ioctl request number.
    """

    calc = _machine_ioctl_calculator()
    request_type = _ioc_request_type(request_type)
    return calc.ioc(calc._IOC_NONE, request_type, request_nr, 0)


def _IOR(request_type, request_nr, size):
    """Python implementation of the ``_IOR(...)`` macro from Linux.

    This is a portable implementation of the ``_IOR(...)`` macro from Linux.
    The `_IOR(...)`` macro calculates a ioctl request number for ioctl request that only pass read-only (input) data.

    :param request_type: The ioctl request type. This can be specified as either a string ``'R'`` or an integer ``123``.
    :param request_nr: The ioctl request number. This is an integer.
    :param size: The size of the associated data. This can either be an integer or a ctypes type.
    :return: The calculated ioctl request number.
    """

    calc = _machine_ioctl_calculator()
    request_type = _ioc_request_type(request_type)
    size = _ioc_type_size(size)
    return calc.ioc(calc._IOC_READ, request_type, request_nr, size)


def _IOW(request_type, request_nr, size):
    """Python implementation of the ``_IOW(...)`` macro from Linux.

    This is a portable implementation of the ``_IOW(...)`` macro from Linux.
    The ``_IOW(...)`` macro calculates a ioctl request number for ioctl request that only pass write-only (output) data.

    :param request_type: The ioctl request type. This can be specified as either a string ``'R'`` or an integer ``123``.
    :param request_nr: The ioctl request number. This is an integer.
    :param size: The size of the associated data. This can either be an integer or a ctypes type.
    :return: The calculated ioctl request number.
    """

    calc = _machine_ioctl_calculator()
    request_type = _ioc_request_type(request_type)
    size = _ioc_type_size(size)
    return calc.ioc(calc._IOC_WRITE, request_type, request_nr, size)


def _IOWR(request_type, request_nr, size):
    """Python implementation of the ``_IOWR(...)`` macro from Linux.

    This is a portable implementation of the ``_IOWR(...)`` macro from Linux.
    The ``_IOWR(...)`` macro calculates a ioctl request number for ioctl request that use the data for both reading (input) and writing (output).

    :param request_type: The ioctl request type. This can be specified as either a string ``'R'`` or an integer ``123``.
    :param request_nr: The ioctl request number. This is an integer.
    :param size: The size of the associated data. This can either be an integer or a ctypes type.
    :return: The calculated ioctl request number.
    """

    calc = _machine_ioctl_calculator()
    request_type = _ioc_request_type(request_type)
    size = _ioc_type_size(size)
    return calc.ioc(calc._IOC_READ | calc._IOC_WRITE, request_type, request_nr, size)


# struct input_mask {
# 	__u32 type;
# 	__u32 codes_size;
# 	__u64 codes_ptr;
# };
EVIOCGMASK = _IOR("E", 0x92, 4 + 4 + 8)
EVIOCSMASK = _IOW("E", 0x93, 4 + 4 + 8)
# char * is 8
UINPUT_IOCTL_BASE = "U"
UI_SET_UNIQ_STR = lambda l: _IOC("w", UINPUT_IOCTL_BASE, 112, l)
UI_GET_SYSNAME = lambda l: _IOC("r", UINPUT_IOCTL_BASE, 44, l)

# Revokes access to an evdev device
EVIOCREVOKEALL = _IOW("E", 0x94, 4)
JSIOCREVOKEALL = _IOW("j", 0x94, 4)

# Grab clean ioctl
EVIOCGRABCLEAN = _IOW("E", 0x95, 4)

# Hidraw Descriptors
HIDIOCGRDESCSIZE = _IOR("H", 0x01, 4)
HIDIOCGRDESC = _IOR("H", 0x02, 4096 + 4)

__all__ = (
    "_IOC",
    "_IO",
    "_IOR",
    "_IOW",
    "_IOWR",
    "EVIOCSMASK",
    "EVIOCGMASK",
    "EVIOCREVOKEALL",
    "JSIOCREVOKEALL",
    "UI_SET_UNIQ_STR",
)
