"""
Simple wrapper around the signalfd syscall.
"""
import errno
import os
import signal

from ._signalfd import ffi as _ffi
from ._signalfd import lib as _lib

__version__ = "0.4.0"

SFD_CLOEXEC = _lib.SFD_CLOEXEC
SFD_NONBLOCK = _lib.SFD_NONBLOCK

SIG_BLOCK = _lib.SIG_BLOCK
SIG_UNBLOCK = _lib.SIG_UNBLOCK
SIG_SETMASK = _lib.SIG_SETMASK

SIGINFO_SIZE = _ffi.sizeof('struct signalfd_siginfo')
ALL_SIGNALS = sorted(getattr(signal, s) for s in dir(signal) if s.startswith('SIG') and '_' not in s)


class UnknownError(Exception):
    pass


def signalfd(fd, signals, flags):
    if hasattr(fd, 'fileno'):
        fd = fd.fileno()

    if not isinstance(fd, int):
        raise TypeError('fd: must be an integer.')
    if not isinstance(flags, int):
        raise TypeError('flags: must be an integer.')

    sigmask = _ffi.new('sigset_t[1]')
    for sig in signals:
        _lib.sigaddset(sigmask, sig)

    result = _lib.signalfd(fd, sigmask, flags)

    if result < 0:
        err = _ffi.errno
        if err == errno.EBADF:
            raise ValueError("fd: not a valid file descriptor.")
        elif err == errno.EINVAL:
            if flags & (0xffffffff ^ (SFD_CLOEXEC | SFD_NONBLOCK)):
                raise ValueError("flags: mask contains invalid values.")
            else:
                raise ValueError("fd: not a signalfd.")
        elif err == errno.EMFILE:
            raise OSError("max system fd limit reached.")
        elif err == errno.ENFILE:
            raise OSError("max system fd limit reached.")
        elif err == errno.ENODEV:
            raise OSError("could not mount (internal) anonymous inode device.")
        elif err == errno.ENOMEM:
            raise MemoryError("insufficient kernel memory available.")
        else:
            raise UnknownError(err)

    return result


def sigprocmask(flags, signals):
    if not isinstance(flags, int):
        raise TypeError('flags: must be an integer.')

    new_sigmask = _ffi.new('sigset_t[1]')
    old_sigmask = _ffi.new('sigset_t[1]')

    _lib.sigemptyset(new_sigmask)
    for sig in signals:
        _lib.sigaddset(new_sigmask, sig)

    err = _lib.pthread_sigmask(flags, new_sigmask, old_sigmask)

    if err:
        if err == errno.EINVAL:
            raise ValueError("flags: invalid value (not one of SIG_BLOCK, SIG_UNBLOCK or SIG_SETMASK)")
        elif err == errno.EFAULT:
            raise ValueError("sigmask is not a valid sigset_t")
        else:
            raise UnknownError(err)

    return [s for s in ALL_SIGNALS if _lib.sigismember(old_sigmask, s)]


def read_siginfo(fh):
    info = _ffi.new('struct signalfd_siginfo *')
    buffer = _ffi.buffer(info)

    if hasattr(fh, 'readinto'):
        if not fh.readinto(buffer):
            raise IOError(errno.EAGAIN, "not enough bytes available")
    else:
        buffer[:] = os.read(fh, SIGINFO_SIZE)
    return info
