# coding: utf-8

"""
This module contains common `ctypes` utils.
"""

import ctypes
import logging
import sys

log = logging.getLogger("can.ctypesutil")

__all__ = ["CLibrary", "HANDLE", "PHANDLE", "HRESULT"]

try:
    _LibBase = ctypes.WinDLL
except AttributeError:
    _LibBase = ctypes.CDLL


class LibraryMixin:
    def map_symbol(self, func_name, restype=None, argtypes=(), errcheck=None):
        """
        Map and return a symbol (function) from a C library. A reference to the
        mapped symbol is also held in the instance

        :param str func_name:
            symbol_name
        :param ctypes.c_* restype:
            function result type (i.e. ctypes.c_ulong...), defaults to void
        :param tuple(ctypes.c_* ... ) argtypes:
            argument types, defaults to no args
        :param callable errcheck:
            optional error checking function, see ctypes docs for _FuncPtr
        """
        if argtypes:
            prototype = self.function_type(restype, *argtypes)
        else:
            prototype = self.function_type(restype)
        try:
            symbol = prototype((func_name, self))
        except AttributeError:
            raise ImportError(
                "Could not map function '{}' from library {}".format(
                    func_name, self._name
                )
            )

        setattr(symbol, "_name", func_name)
        log.debug(
            f'Wrapped function "{func_name}", result type: {type(restype)}, error_check {errcheck}'
        )

        if errcheck:
            symbol.errcheck = errcheck

        setattr(self, func_name, symbol)
        return symbol


class CLibrary_Win32(_LibBase, LibraryMixin):
    " Basic ctypes.WinDLL derived class + LibraryMixin "

    def __init__(self, library_or_path):
        if isinstance(library_or_path, str):
            super().__init__(library_or_path)
        else:
            super().__init__(library_or_path._name, library_or_path._handle)

    @property
    def function_type(self):
        return ctypes.WINFUNCTYPE


class CLibrary_Unix(ctypes.CDLL, LibraryMixin):
    " Basic ctypes.CDLL derived class + LibraryMixin "

    def __init__(self, library_or_path):
        if isinstance(library_or_path, str):
            super().__init__(library_or_path)
        else:
            super().__init__(library_or_path._name, library_or_path._handle)

    @property
    def function_type(self):
        return ctypes.CFUNCTYPE


if sys.platform == "win32":
    CLibrary = CLibrary_Win32
    HRESULT = ctypes.HRESULT
else:
    CLibrary = CLibrary_Unix
    if sys.platform == "cygwin":
        # Define HRESULT for cygwin
        class HRESULT(ctypes.c_long):
            pass


# Common win32 definitions
class HANDLE(ctypes.c_void_p):
    pass


PHANDLE = ctypes.POINTER(HANDLE)
