
from __future__ import absolute_import
from __future__ import division

from ctypes import CDLL, CFUNCTYPE, sizeof, get_errno, byref
from ctypes import c_char, c_char_p, c_int, c_void_p, c_size_t, py_object

from vdsm.common.cache import memoized
from vdsm.network import py2to3

LIBNL = CDLL('libnl-3.so.200', use_errno=True)
LIBNL_ROUTE = CDLL('libnl-route-3.so.200', use_errno=True)



GROUPS = {
}

EVENTS = {
}


class RtKnownTables(object):
    RT_TABLE_UNSPEC = 0
    RT_TABLE_COMPAT = 252
    RT_TABLE_DEFAULT = 253
    RT_TABLE_MAIN = 254
    RT_TABLE_LOCAL = 255
    RT_TABLE_MAX = 0xFFFFFFFF


class IfaceStatus(object):
    IFF_BROADCAST = 1 << 1
    IFF_DEBUG = 1 << 2
    IFF_LOOPBACK = 1 << 3
    IFF_POINTOPOINT = 1 << 4
    IFF_NOTRAILERS = 1 << 5
    IFF_NOARP = 1 << 7
    IFF_PROMISC = 1 << 8
    IFF_ALLMULTI = 1 << 9
    IFF_MASTER = 1 << 10
    IFF_SLAVE = 1 << 11
    IFF_MULTICAST = 1 << 12
    IFF_PORTSEL = 1 << 13
    IFF_AUTOMEDIA = 1 << 14
    IFF_DYNAMIC = 1 << 15
    IFF_LOWER_UP = 1 << 16
    IFF_DORMANT = 1 << 17
    IFF_ECHO = 1 << 18


class NlCbAction(object):


class NlCbKind(object):


class RtnlObjectType(object):
    BASE = 'route'


def nl_geterror(error_code):
    _nl_geterror = _libnl('nl_geterror', c_char_p, c_int)
    error_message = _nl_geterror(error_code)
    return py2to3.to_str(error_message)


def nl_addr2str(addr):
    _nl_addr2str = _libnl(
        'nl_addr2str', c_char_p, c_void_p, c_char_p, c_size_t)
    buf = (c_char * HWADDRSIZE)()
    address = _nl_addr2str(addr, buf, sizeof(buf))
    return py2to3.to_str(address)


def nl_af2str(family):
    _nl_af2str = _libnl('nl_af2str', c_char_p, c_int, c_char_p, c_size_t)
    buf = (c_char * CHARBUFFSIZE)()
    address_family = _nl_af2str(family, buf, sizeof(buf))
    return py2to3.to_str(address_family)


def rtnl_scope2str(scope):
    _rtnl_scope2str = _libnl_route(
        'rtnl_scope2str', c_char_p, c_int, c_char_p, c_size_t)
    buf = (c_char * CHARBUFFSIZE)()
    address_scope = _rtnl_scope2str(scope, buf, sizeof(buf))
    return py2to3.to_str(address_scope)


def nl_socket_alloc():
    _nl_socket_alloc = _libnl('nl_socket_alloc', c_void_p)
    allocated_socket = _nl_socket_alloc()
    if allocated_socket is None:
        raise IOError(get_errno(), 'Failed to allocate socket.')
    return allocated_socket


def nl_connect(socket, protocol):
    _nl_connect = _libnl('nl_connect', c_int, c_void_p, c_int)
    err = _nl_connect(socket, protocol)
    if err:
        raise IOError(-err, nl_geterror(err))


def nl_socket_free(socket):
    _nl_socket_free = _libnl('nl_socket_free', None, c_void_p)
    _nl_socket_free(socket)


def nl_socket_set_buffer_size(socket, rx_buf_size, tx_buf_size):
    _nl_socket_set_buffer_size = _libnl('nl_socket_set_buffer_size',
                                        c_int, c_void_p, c_int, c_int)
    err = _nl_socket_set_buffer_size(socket, rx_buf_size, tx_buf_size)
    if err:
        raise IOError(-err, nl_geterror(err))


def nl_socket_get_fd(socket):
    _nl_socket_get_fd = _libnl('nl_socket_get_fd', c_int, c_void_p)
    file_descriptor = _nl_socket_get_fd(socket)
    if file_descriptor == -1:
        raise IOError(get_errno(), 'Failed to obtain socket file descriptor.')
    return file_descriptor


def nl_socket_add_memberships(socket, *groups):
    _nl_socket_add_memberships = _libnl(
        'nl_socket_add_memberships',
        c_int, c_void_p, *((c_int,) * (len(GROUPS) + 1)))
    err = _nl_socket_add_memberships(socket, *groups)
    if err:
        raise IOError(-err, nl_geterror(err))


def nl_socket_drop_memberships(socket, *groups):
    _nl_socket_drop_memberships = _libnl(
        'nl_socket_drop_memberships',
        c_int, c_void_p, *((c_int,) * (len(GROUPS) + 1)))
    err = _nl_socket_drop_memberships(socket, *groups)
    if err:
        raise IOError(-err, nl_geterror(err))


def nl_socket_modify_cb(socket, cb_type, kind, function, argument):
    _nl_socket_modify_cb = _libnl(
        'nl_socket_modify_cb',
        c_int, c_void_p, c_int, c_int, c_void_p, py_object)
    err = _nl_socket_modify_cb(socket, cb_type, kind, function, argument)
    if err:
        raise IOError(-err, nl_geterror(err))


def prepare_cfunction_for_nl_socket_modify_cb(function):
    c_function = CFUNCTYPE(c_int, c_void_p, c_void_p)(function)
    return c_function


def nl_socket_disable_seq_check(socket):
    _nl_socket_disable_seq_check = _libnl(
        'nl_socket_disable_seq_check', c_void_p, c_void_p)
    _nl_socket_disable_seq_check(socket)


def nl_cache_get_first(cache):
    _nl_cache_get_first = _libnl('nl_cache_get_first', c_void_p, c_void_p)
    return _nl_cache_get_first(cache)


def nl_cache_get_next(element):
    _nl_cache_get_next = _libnl('nl_cache_get_next', c_void_p, c_void_p)
    return _nl_cache_get_next(element)


def nl_cache_free(cache):
    _nl_cache_free = _libnl('nl_cache_free', None, c_void_p)
    _nl_cache_free(cache)


def nl_object_get_type(obj):
    _nl_object_get_type = _libnl('nl_object_get_type', c_char_p, c_void_p)
    object_type = _nl_object_get_type(obj)
    return py2to3.to_str(object_type) if object_type else None


def nl_object_get_msgtype(obj):
    _nl_object_get_msgtype = _libnl('nl_object_get_msgtype', c_int, c_void_p)
    message_type = _nl_object_get_msgtype(obj)
    if message_type == 0:
        raise IOError(get_errno(), 'Failed to obtain message name.')
    return message_type


def nl_msg_parse(message, function, argument):
    _nl_msg_parse = _libnl('nl_msg_parse', c_int, c_void_p, c_void_p, c_void_p)
    err = _nl_msg_parse(message, function, argument)
    if err:
        raise IOError(-err, nl_geterror(err))


def prepare_cfunction_for_nl_msg_parse(function):
    c_function = CFUNCTYPE(None, c_void_p, py_object)(function)
    return c_function


def nl_recvmsgs_default(socket):
    _nl_recvmsgs_default = _libnl('nl_recvmsgs_default', c_int, c_void_p)
    err = _nl_recvmsgs_default(socket)
    if err:
        raise IOError(-err, nl_geterror(err))


def rtnl_addr_alloc_cache(socket):
    _rtnl_addr_alloc_cache = _libnl_route(
        'rtnl_addr_alloc_cache', c_int, c_void_p, c_void_p)
    cache = c_void_p()
    err = _rtnl_addr_alloc_cache(socket, byref(cache))
    if err:
        raise IOError(-err, nl_geterror(err))
    return cache


def rtnl_addr_get_ifindex(rtnl_address):
    _rtnl_addr_get_ifindex = _libnl_route(
        'rtnl_addr_get_ifindex', c_int, c_void_p)
    return _rtnl_addr_get_ifindex(rtnl_address)


def rtnl_addr_get_family(rtnl_address):
    _rtnl_addr_get_family = _libnl_route(
        'rtnl_addr_get_family', c_int, c_void_p)
    return _rtnl_addr_get_family(rtnl_address)


def rtnl_addr_get_prefixlen(rtnl_address):
    _rtnl_addr_get_prefixlen = _libnl_route(
        'rtnl_addr_get_prefixlen', c_int, c_void_p)
    return _rtnl_addr_get_prefixlen(rtnl_address)


def rtnl_addr_get_scope(rtnl_address):
    _rtnl_addr_get_scope = _libnl_route('rtnl_addr_get_scope', c_int, c_void_p)
    return _rtnl_addr_get_scope(rtnl_address)


def rtnl_addr_get_flags(rtnl_address):
    _rtnl_addr_get_flags = _libnl_route('rtnl_addr_get_flags', c_int, c_void_p)
    return _rtnl_addr_get_flags(rtnl_address)


def rtnl_addr_get_local(rtnl_address):
    _rtnl_addr_get_local = _libnl_route(
        'rtnl_addr_get_local', c_void_p, c_void_p)
    return _rtnl_addr_get_local(rtnl_address)


def rtnl_addr_flags2str(flags_bitfield):
    _rtnl_addr_flags2str = _libnl_route(
        'rtnl_addr_flags2str', c_char_p, c_int, c_char_p, c_size_t)
    buf = (c_char * (CHARBUFFSIZE * 2))()
    flags_str = _rtnl_addr_flags2str(flags_bitfield, buf, sizeof(buf))
    return py2to3.to_str(flags_str)


def rtnl_link_alloc_cache(socket, family):
    _rtnl_link_alloc_cache = _libnl_route(
        'rtnl_link_alloc_cache', c_int, c_void_p, c_int, c_void_p)
    cache = c_void_p()
    err = _rtnl_link_alloc_cache(socket, family, byref(cache))
    if err:
        raise IOError(-err, nl_geterror(err))
    return cache


def rtnl_link_is_vlan(link):
    _rtnl_link_is_vlan = _libnl_route('rtnl_link_is_vlan', c_int, c_void_p)
    is_vlan = _rtnl_link_is_vlan(link)
    return bool(is_vlan)


def rtnl_link_vlan_get_id(link):
    _rtnl_link_vlan_get_id = _libnl_route(
        'rtnl_link_vlan_get_id', c_int, c_void_p)
    vlan_id = _rtnl_link_vlan_get_id(link)
    if vlan_id < 0:
        raise IOError(-vlan_id, nl_geterror(vlan_id))
    return vlan_id


def rtnl_link_get_type(link):
    _rtnl_link_get_type = _libnl_route(
        'rtnl_link_get_type', c_char_p, c_void_p)
    link_type = _rtnl_link_get_type(link)
    return py2to3.to_str(link_type) if link_type else None


def rtnl_link_get_kernel(socket, ifindex, ifname):
    _rtnl_link_get_kernel = _libnl_route(
        'rtnl_link_get_kernel', c_int, c_void_p, c_int, c_char_p, c_void_p)
    link = c_void_p()
    b_ifname = py2to3.to_binary(ifname) if ifname else None
    err = _rtnl_link_get_kernel(socket, ifindex, b_ifname, byref(link))
    if err:
        raise IOError(-err, nl_geterror(err))
    return link


def rtnl_link_get_addr(link):
    _rtnl_link_get_addr = _libnl_route(
        'rtnl_link_get_addr', c_void_p, c_void_p)
    return _rtnl_link_get_addr(link)


def rtnl_link_get_flags(link):
    _rtnl_link_get_flags = _libnl_route('rtnl_link_get_flags', c_int, c_void_p)
    return _rtnl_link_get_flags(link)


def rtnl_link_get_ifindex(link):
    _rtnl_link_get_ifindex = _libnl_route(
        'rtnl_link_get_ifindex', c_int, c_void_p)
    ifindex = _rtnl_link_get_ifindex(link)
    if ifindex == 0:
        return None
    return ifindex


def rtnl_link_get_link(link):
    _rtnl_link_get_link = _libnl_route('rtnl_link_get_link', c_int, c_void_p)
    underlying_ifindex = _rtnl_link_get_link(link)
    if underlying_ifindex == 0:
        return None
    return underlying_ifindex


def rtnl_link_get_master(link):
    _rtnl_link_get_master = _libnl_route(
        'rtnl_link_get_master', c_int, c_void_p)
    master_ifindex = _rtnl_link_get_master(link)
    if master_ifindex == 0:
        return None
    return master_ifindex


def rtnl_link_get_mtu(link):
    _rtnl_link_get_mtu = _libnl_route('rtnl_link_get_mtu', c_int, c_void_p)
    mtu = _rtnl_link_get_mtu(link)
    if mtu == 0:
        return None
    return mtu


def rtnl_link_get_name(link):
    _rtnl_link_get_name = _libnl_route(
        'rtnl_link_get_name', c_char_p, c_void_p)
    name = _rtnl_link_get_name(link)
    return py2to3.to_str(name) if name else None


def rtnl_link_get_operstate(link):
    _rtnl_link_get_operstate = _libnl_route(
        'rtnl_link_get_operstate', c_int, c_void_p)
    return _rtnl_link_get_operstate(link)


def rtnl_link_get_qdisc(link):
    _rtnl_link_get_qdisc = _libnl_route(
        'rtnl_link_get_qdisc', c_char_p, c_void_p)
    qdisc = _rtnl_link_get_qdisc(link)
    return py2to3.to_str(qdisc) if qdisc else None


def rtnl_link_get_by_name(cache, name):
    _rtnl_link_get_by_name = _libnl_route(
        'rtnl_link_get_by_name', c_void_p, c_void_p, c_char_p)
    return _rtnl_link_get_by_name(cache, name)


def rtnl_link_i2name(cache, ifindex):
    _rtnl_link_i2name = _libnl_route(
        'rtnl_link_i2name', c_char_p, c_void_p, c_int, c_char_p, c_size_t)
    buf = (c_char * CHARBUFFSIZE)()
    name = _rtnl_link_i2name(cache, ifindex, buf, sizeof(buf))
    return py2to3.to_str(name) if name else None


def rtnl_link_operstate2str(operstate_code):
    _rtnl_link_operstate2str = _libnl_route(
        'rtnl_link_operstate2str', c_char_p, c_int, c_char_p, c_size_t)
    buf = (c_char * CHARBUFFSIZE)()
    operstate = _rtnl_link_operstate2str(operstate_code, buf, sizeof(buf))
    return py2to3.to_str(operstate)


def rtnl_link_put(link):
    _rtnl_link_put = _libnl_route('rtnl_link_put', None, c_void_p)
    _rtnl_link_put(link)


def rtnl_route_alloc_cache(socket, family, flags):
    _rtnl_route_alloc_cache = _libnl_route(
        'rtnl_route_alloc_cache', c_int, c_void_p, c_int, c_int, c_void_p)
    cache = c_void_p()
    err = _rtnl_route_alloc_cache(socket, family, flags, byref(cache))
    if err:
        raise IOError(-err, nl_geterror(err))
    return cache


def rtnl_route_get_nnexthops(route):
    _rtnl_route_get_nnexthops = _libnl_route(
        'rtnl_route_get_nnexthops', c_int, c_void_p)
    return _rtnl_route_get_nnexthops(route)


def rtnl_route_nexthop_n(route, index):
    _rtnl_route_get_nexthop_n = _libnl_route(
        'rtnl_route_nexthop_n', c_void_p, c_void_p, c_int)
    return _rtnl_route_get_nexthop_n(route, index)


def rtnl_route_get_dst(route):
    _rtnl_route_get_dst = _libnl_route(
        'rtnl_route_get_dst', c_void_p, c_void_p)
    return _rtnl_route_get_dst(route)


def rtnl_route_get_src(route):
    _rtnl_route_get_src = _libnl_route(
        'rtnl_route_get_src', c_void_p, c_void_p)
    return _rtnl_route_get_src(route)


def rtnl_route_get_iif(route):
    _rtnl_route_get_iif = _libnl_route('rtnl_route_get_iif', c_int, c_void_p)
    return _rtnl_route_get_iif(route)


def rtnl_route_get_table(route):
    _rtnl_route_get_table = _libnl_route(
        'rtnl_route_get_table', c_int, c_void_p)
    return _rtnl_route_get_table(route)


def rtnl_route_get_scope(route):
    _rtnl_route_get_scope = _libnl_route(
        'rtnl_route_get_scope', c_int, c_void_p)
    scope_code = _rtnl_route_get_scope(route)
    return scope_code


def rtnl_route_get_family(route):
    _rtnl_route_get_family = _libnl_route(
        'rtnl_route_get_family', c_int, c_void_p)
    return _rtnl_route_get_family(route)


def rtnl_route_nh_get_ifindex(next_hop):
    _rtnl_route_nh_get_ifindex = _libnl_route(
        'rtnl_route_nh_get_ifindex', c_int, c_void_p)
    return _rtnl_route_nh_get_ifindex(next_hop)


def rtnl_route_nh_get_gateway(next_hop):
    _rtnl_route_nh_get_gateway = _libnl_route(
        'rtnl_route_nh_get_gateway', c_void_p, c_void_p)
    return _rtnl_route_nh_get_gateway(next_hop)


def c_object_argument(argument):
    return py_object(argument)


@memoized
def _libnl(function_name, return_type, *arguments):
    return CFUNCTYPE(return_type, *arguments)((function_name, LIBNL))


@memoized
def _libnl_route(function_name, return_type, *arguments):
    return CFUNCTYPE(return_type, *arguments)((function_name, LIBNL_ROUTE))
