
from __future__ import absolute_import
from __future__ import division
from functools import partial
from socket import AF_UNSPEC
import errno

from . import _cache_manager
from . import _pool
from . import libnl
from .link import _nl_link_cache, _link_index_to_name


def iter_routes():
    with _pool.socket() as sock:
        with _nl_route_cache(sock) as route_cache:
                route = libnl.nl_cache_get_first(route_cache)
                while route:
                    yield _route_info(route, link_cache=link_cache)
                    route = libnl.nl_cache_get_next(route)


def _route_info(route, link_cache=None):
    destination = libnl.rtnl_route_get_dst(route)
    source = libnl.rtnl_route_get_src(route)
    gateway = _rtnl_route_get_gateway(route)
    data = {
        'source': libnl.nl_addr2str(source) if source else None,
        'family': libnl.nl_af2str(libnl.rtnl_route_get_family(route)),
        'table': libnl.rtnl_route_get_table(route),
        'scope': libnl.rtnl_scope2str(libnl.rtnl_route_get_scope(route))}
    oif_index = _rtnl_route_get_oif(route)
    if oif_index > 0:
        data['oif_index'] = oif_index
        try:
            data['oif'] = _link_index_to_name(oif_index, cache=link_cache)
        except IOError as err:
            if err.errno != errno.ENODEV:
                raise
    return data


def _rtnl_route_alloc_cache(sock):
    return libnl.rtnl_route_alloc_cache(sock, AF_UNSPEC, 0)


def _route_get_next_hop(route):
    if libnl.rtnl_route_get_nnexthops(route) != 1:
        return None
    return libnl.rtnl_route_nexthop_n(route, 0)


def _rtnl_route_get_oif(route):
    hop = _route_get_next_hop(route)
    if hop is None:
        return -1
    else:
        return libnl.rtnl_route_nh_get_ifindex(hop)


def _rtnl_route_get_gateway(route):
    hop = _route_get_next_hop(route)
    if hop is None:
        return None
    else:
        return libnl.rtnl_route_nh_get_gateway(hop)


_nl_route_cache = partial(_cache_manager, _rtnl_route_alloc_cache)
