from pypy.interpreter.baseobjspace import W_Root
from pypy.interpreter.error import OperationError, oefmt
from pypy.interpreter.error import exception_from_saved_errno
from pypy.interpreter.gateway import interp2app, unwrap_spec, WrappedDefault
from pypy.interpreter.typedef import TypeDef, generic_new_descr, GetSetProperty
from rpython.rlib._rsocket_rffi import socketclose_no_errno
from rpython.rlib.rarithmetic import r_uint
from rpython.rtyper.lltypesystem import rffi, lltype
from rpython.rtyper.tool import rffi_platform
from rpython.translator.tool.cbuild import ExternalCompilationInfo
import sys


eci = ExternalCompilationInfo(
    includes = ["sys/types.h",
                "sys/event.h",
                "sys/time.h"],
)


class CConfig:
    _compilation_info_ = eci


if "openbsd" in sys.platform:
    IDENT_UINT = True
    CConfig.kevent = rffi_platform.Struct("struct kevent", [
        ("ident", rffi.UINT),
        ("filter", rffi.SHORT),
        ("flags", rffi.USHORT),
        ("fflags", rffi.UINT),
        ("data", rffi.INT),
        ("udata", rffi.VOIDP),
    ])
else:
    IDENT_UINT = False
    CConfig.kevent = rffi_platform.Struct("struct kevent", [
        ("ident", rffi.UINTPTR_T),
        ("filter", rffi.SHORT),
        ("flags", rffi.USHORT),
        ("fflags", rffi.UINT),
        ("data", rffi.INTPTR_T),
        ("udata", rffi.VOIDP),
    ])


CConfig.timespec = rffi_platform.Struct("struct timespec", [
    ("tv_sec", rffi.TIME_T),
    ("tv_nsec", rffi.LONG),
])


symbol_map = {
    "KQ_FILTER_READ": "EVFILT_READ",
    "KQ_FILTER_WRITE": "EVFILT_WRITE",
    "KQ_FILTER_AIO": "EVFILT_AIO",
    "KQ_FILTER_VNODE": "EVFILT_VNODE",
    "KQ_FILTER_PROC": "EVFILT_PROC",
#    "KQ_FILTER_NETDEV": None, # deprecated on FreeBSD .. no longer defined
    "KQ_FILTER_SIGNAL": "EVFILT_SIGNAL",
    "KQ_FILTER_TIMER": "EVFILT_TIMER",
    "KQ_EV_ADD": "EV_ADD",
    "KQ_EV_DELETE": "EV_DELETE",
    "KQ_EV_ENABLE": "EV_ENABLE",
    "KQ_EV_DISABLE": "EV_DISABLE",
    "KQ_EV_ONESHOT": "EV_ONESHOT",
    "KQ_EV_CLEAR": "EV_CLEAR",
#    "KQ_EV_SYSFLAGS": None, # Python docs says "internal event" .. not defined on FreeBSD
#    "KQ_EV_FLAG1": None, # Python docs says "internal event" .. not defined on FreeBSD
    "KQ_EV_EOF": "EV_EOF",
    "KQ_EV_ERROR": "EV_ERROR"
}

for symbol in symbol_map.values():
    setattr(CConfig, symbol, rffi_platform.DefinedConstantInteger(symbol))

cconfig = rffi_platform.configure(CConfig)

kevent = cconfig["kevent"]
timespec = cconfig["timespec"]

for symbol in symbol_map:
    globals()[symbol] = cconfig[symbol_map[symbol]]


syscall_kqueue = rffi.llexternal(
    "kqueue",
    [],
    rffi.INT,
    compilation_info=eci,
    save_err=rffi.RFFI_SAVE_ERRNO
)

syscall_kevent = rffi.llexternal(
    "kevent",
    [rffi.INT,
     lltype.Ptr(rffi.CArray(kevent)),
     rffi.INT,
     lltype.Ptr(rffi.CArray(kevent)),
     rffi.INT,
     lltype.Ptr(timespec)
    ],
    rffi.INT,
    compilation_info=eci,
    save_err=rffi.RFFI_SAVE_ERRNO
)


class W_Kqueue(W_Root):
    def __init__(self, space, kqfd):
        self.kqfd = kqfd

    def descr__new__(space, w_subtype):
        kqfd = syscall_kqueue()
        if kqfd < 0:
            raise exception_from_saved_errno(space, space.w_IOError)
        return space.wrap(W_Kqueue(space, kqfd))

    @unwrap_spec(fd=int)
    def descr_fromfd(space, w_cls, fd):
        return space.wrap(W_Kqueue(space, fd))

    def __del__(self):
        self.close()

    def get_closed(self):
        return self.kqfd < 0

    def close(self):
        if not self.get_closed():
            kqfd = self.kqfd
            self.kqfd = -1
            socketclose_no_errno(kqfd)

    def check_closed(self, space):
        if self.get_closed():
            raise OperationError(space.w_ValueError, space.wrap("I/O operation on closed kqueue fd"))

    def descr_get_closed(self, space):
        return space.wrap(self.get_closed())

    def descr_fileno(self, space):
        self.check_closed(space)
        return space.wrap(self.kqfd)

    def descr_close(self, space):
        self.close()

    @unwrap_spec(max_events=int, w_timeout = WrappedDefault(None))
    def descr_control(self, space, w_changelist, max_events, w_timeout):

        self.check_closed(space)

        if max_events < 0:
            raise oefmt(space.w_ValueError,
                        "Length of eventlist must be 0 or positive, got %d",
                        max_events)

        if space.is_w(w_changelist, space.w_None):
            changelist_len = 0
        else:
            changelist_len = space.len_w(w_changelist)

        with lltype.scoped_alloc(rffi.CArray(kevent), changelist_len) as changelist:
            with lltype.scoped_alloc(rffi.CArray(kevent), max_events) as eventlist:
                with lltype.scoped_alloc(timespec) as timeout:

                    if not space.is_w(w_timeout, space.w_None):
                        _timeout = space.float_w(w_timeout)
                        if _timeout < 0:
                            raise oefmt(space.w_ValueError,
                                        "Timeout must be None or >= 0, got %s",
                                        str(_timeout))
                        sec = int(_timeout)
                        nsec = int(1e9 * (_timeout - sec))
                        rffi.setintfield(timeout, 'c_tv_sec', sec)
                        rffi.setintfield(timeout, 'c_tv_nsec', nsec)
                        ptimeout = timeout
                    else:
                        ptimeout = lltype.nullptr(timespec)

                    if not space.is_w(w_changelist, space.w_None):
                        i = 0
                        for w_ev in space.listview(w_changelist):
                            ev = space.interp_w(W_Kevent, w_ev)
                            changelist[i].c_ident = ev.ident
                            changelist[i].c_filter = ev.filter
                            changelist[i].c_flags = ev.flags
                            changelist[i].c_fflags = ev.fflags
                            changelist[i].c_data = ev.data
                            changelist[i].c_udata = ev.udata
                            i += 1
                        pchangelist = changelist
                    else:
                        pchangelist = lltype.nullptr(rffi.CArray(kevent))

                    nfds = syscall_kevent(self.kqfd,
                                          pchangelist,
                                          changelist_len,
                                          eventlist,
                                          max_events,
                                          ptimeout)
                    if nfds < 0:
                        raise exception_from_saved_errno(space, space.w_OSError)
                    else:
                        elist_w = [None] * nfds
                        for i in xrange(nfds):

                            evt = eventlist[i]

                            w_event = W_Kevent(space)
                            w_event.ident = evt.c_ident
                            w_event.filter = evt.c_filter
                            w_event.flags = evt.c_flags
                            w_event.fflags = evt.c_fflags
                            w_event.data = evt.c_data
                            w_event.udata = evt.c_udata

                            elist_w[i] = w_event

                    return space.newlist(elist_w)


W_Kqueue.typedef = TypeDef("select.kqueue",
    __new__ = interp2app(W_Kqueue.descr__new__.im_func),
    fromfd = interp2app(W_Kqueue.descr_fromfd.im_func, as_classmethod=True),

    closed = GetSetProperty(W_Kqueue.descr_get_closed),
    fileno = interp2app(W_Kqueue.descr_fileno),

    close = interp2app(W_Kqueue.descr_close),
    control = interp2app(W_Kqueue.descr_control),
)
W_Kqueue.typedef.acceptable_as_base_class = False


class W_Kevent(W_Root):
    def __init__(self, space):
        self.ident = rffi.cast(kevent.c_ident, 0)
        self.filter = rffi.cast(kevent.c_filter, 0)
        self.flags = rffi.cast(kevent.c_flags, 0)
        self.fflags = rffi.cast(kevent.c_fflags, 0)
        self.data = rffi.cast(kevent.c_data, 0)
        self.udata = lltype.nullptr(rffi.VOIDP.TO)

    @unwrap_spec(filter=int, flags='c_uint', fflags='c_uint', data=int, udata=r_uint)
    def descr__init__(self, space, w_ident, filter=KQ_FILTER_READ, flags=KQ_EV_ADD, fflags=0, data=0, udata=r_uint(0)):
        if space.isinstance_w(w_ident, space.w_long):
            ident = space.uint_w(w_ident)
        else:
            ident = r_uint(space.c_filedescriptor_w(w_ident))

        self.ident = rffi.cast(kevent.c_ident, ident)
        self.filter = rffi.cast(kevent.c_filter, filter)
        self.flags = rffi.cast(kevent.c_flags, flags)
        self.fflags = rffi.cast(kevent.c_fflags, fflags)
        self.data = rffi.cast(kevent.c_data, data)
        self.udata = rffi.cast(rffi.VOIDP, udata)

    def _compare_all_fields(self, other, op):
        if IDENT_UINT:
            l_ident = rffi.cast(lltype.Unsigned, self.ident)
            r_ident = rffi.cast(lltype.Unsigned, other.ident)
        else:
            l_ident = self.ident
            r_ident = other.ident
        l_filter = rffi.cast(lltype.Signed, self.filter)
        r_filter = rffi.cast(lltype.Signed, other.filter)
        l_flags = rffi.cast(lltype.Unsigned, self.flags)
        r_flags = rffi.cast(lltype.Unsigned, other.flags)
        l_fflags = rffi.cast(lltype.Unsigned, self.fflags)
        r_fflags = rffi.cast(lltype.Unsigned, other.fflags)
        if IDENT_UINT:
            l_data = rffi.cast(lltype.Signed, self.data)
            r_data = rffi.cast(lltype.Signed, other.data)
        else:
            l_data = self.data
            r_data = other.data
        l_udata = rffi.cast(lltype.Unsigned, self.udata)
        r_udata = rffi.cast(lltype.Unsigned, other.udata)

        if op == "eq":
            return l_ident == r_ident and \
                   l_filter == r_filter and \
                   l_flags == r_flags and \
                   l_fflags == r_fflags and \
                   l_data == r_data and \
                   l_udata == r_udata
        elif op == "lt":
            return (l_ident < r_ident) or \
                   (l_ident == r_ident and l_filter < r_filter) or \
                   (l_ident == r_ident and l_filter == r_filter and l_flags < r_flags) or \
                   (l_ident == r_ident and l_filter == r_filter and l_flags == r_flags and l_fflags < r_fflags) or \
                   (l_ident == r_ident and l_filter == r_filter and l_flags == r_flags and l_fflags == r_fflags and l_data < r_data) or \
                   (l_ident == r_ident and l_filter == r_filter and l_flags == r_flags and l_fflags == r_fflags and l_data == r_data and l_udata < r_udata)
        elif op == "gt":
            return (l_ident > r_ident) or \
                   (l_ident == r_ident and l_filter > r_filter) or \
                   (l_ident == r_ident and l_filter == r_filter and l_flags > r_flags) or \
                   (l_ident == r_ident and l_filter == r_filter and l_flags == r_flags and l_fflags > r_fflags) or \
                   (l_ident == r_ident and l_filter == r_filter and l_flags == r_flags and l_fflags == r_fflags and l_data > r_data) or \
                   (l_ident == r_ident and l_filter == r_filter and l_flags == r_flags and l_fflags == r_fflags and l_data == r_data and l_udata > r_udata)
        else:
            assert False

    def compare_all_fields(self, space, other, op):
        if not space.interp_w(W_Kevent, other):
            if op == "eq":
                return False
            elif op == "ne":
                return True
            else:
                raise OperationError(space.w_TypeError, space.wrap('cannot compare kevent to incompatible type'))
        return self._compare_all_fields(space.interp_w(W_Kevent, other), op)

    def descr__eq__(self, space, w_other):
        return space.wrap(self.compare_all_fields(space, w_other, "eq"))

    def descr__ne__(self, space, w_other):
        return space.wrap(not self.compare_all_fields(space, w_other, "eq"))

    def descr__le__(self, space, w_other):
        return space.wrap(not self.compare_all_fields(space, w_other, "gt"))

    def descr__lt__(self, space, w_other):
        return space.wrap(self.compare_all_fields(space, w_other, "lt"))

    def descr__ge__(self, space, w_other):
        return space.wrap(not self.compare_all_fields(space, w_other, "lt"))

    def descr__gt__(self, space, w_other):
        return space.wrap(self.compare_all_fields(space, w_other, "gt"))

    def descr_get_ident(self, space):
        return space.wrap(self.ident)

    def descr_get_filter(self, space):
        return space.wrap(self.filter)

    def descr_get_flags(self, space):
        return space.wrap(self.flags)

    def descr_get_fflags(self, space):
        return space.wrap(self.fflags)

    def descr_get_data(self, space):
        return space.wrap(self.data)

    def descr_get_udata(self, space):
        return space.wrap(rffi.cast(rffi.UINTPTR_T, self.udata))


W_Kevent.typedef = TypeDef("select.kevent",
    __new__ = generic_new_descr(W_Kevent),
    __init__ = interp2app(W_Kevent.descr__init__),
    __eq__ = interp2app(W_Kevent.descr__eq__),
    __ne__ = interp2app(W_Kevent.descr__ne__),
    __le__ = interp2app(W_Kevent.descr__le__),
    __lt__ = interp2app(W_Kevent.descr__lt__),
    __ge__ = interp2app(W_Kevent.descr__ge__),
    __gt__ = interp2app(W_Kevent.descr__gt__),

    ident = GetSetProperty(W_Kevent.descr_get_ident),
    filter = GetSetProperty(W_Kevent.descr_get_filter),
    flags = GetSetProperty(W_Kevent.descr_get_flags),
    fflags = GetSetProperty(W_Kevent.descr_get_fflags),
    data = GetSetProperty(W_Kevent.descr_get_data),
    udata = GetSetProperty(W_Kevent.descr_get_udata),
)
W_Kevent.typedef.acceptable_as_base_class = False
