#!/usr/bin/python3

import os
import re
import sys

import logging
import socket
import struct
import subprocess
import gettext
import time

from gettext import gettext as _

from argparse import ArgumentParser


class Route(object):
    """Gets routing information from the system.
    """

    # auxiliary functions
    def _hex_to_dec(self, string):
        """Returns the integer value of a hexadecimal string s
        """
        return int(string, 16)

    def _num_to_dotted_quad(self, number):
        """Convert long int to dotted quad string
        """
        return socket.inet_ntoa(struct.pack("<L", number))

    def _get_default_gateway_from_proc(self):
        """"Returns the current default gateway, reading that from /proc
        """
        logging.debug("Reading default gateway information from /proc")
        try:
            file = open("/proc/net/route")
            route = file.read()
        except:
            logging.error("Failed to read def gateway from /proc")
            return None
        else:
            h = re.compile("\n(?P<interface>\w+)\s+00000000\s+"
                           "(?P<def_gateway>[\w]+)\s+")
            w = h.search(route)
            if w:
                if w.group("def_gateway"):
                    return (self._num_to_dotted_quad(
                            self._hex_to_dec(w.group("def_gateway"))))
                else:
                    logging.error("Could not find def gateway info in /proc")
                    return None
            else:
                logging.error("Could not find def gateway info in /proc")
                return None

    def _get_default_gateway_from_bin_route(self):
        """Get default gateway from /sbin/route -n
        Called by get_default_gateway
        and is only used if could not get that from /proc
        """
        logging.debug("Reading default gateway information from route binary")
        routebin = subprocess.getstatusoutput("export LANGUAGE=C; "
                                              "/usr/bin/env route -n")

        if routebin[0] == 0:
            h = re.compile("\n0.0.0.0\s+(?P<def_gateway>[\w.]+)\s+")
            w = h.search(routebin[1])
            if w:
                def_gateway = w.group("def_gateway")
                if def_gateway:
                    return def_gateway

        logging.error("Could not find default gateway by running route")
        return None

    def get_hostname(self):
        return socket.gethostname()

    def get_default_gateway(self):
        t1 = self._get_default_gateway_from_proc()
        if not t1:
            t1 = self._get_default_gateway_from_bin_route()

        return t1


def get_host_to_ping(interface=None, verbose=False, default=None):
    #Get list of all IPs from all my interfaces,
    interface_list = subprocess.check_output(["ip", "-o", 'addr', 'show'])

    reg = re.compile('\d: (?P<iface>\w+) +inet (?P<address>[\d\.]+)/'
                     '(?P<netmask>[\d]+) brd (?P<broadcast>[\d\.]+)')
    # Will magically exclude lo because it lacks brd field
    interfaces = reg.findall(interface_list.decode())

    # ping -b the network on each one (one ping only)
    # exclude the ones not specified in iface
    for iface in interfaces:
        if not interface or iface[0] == interface:
            #Use check_output even if I'll discard the output
            #looks cleaner than using .call and redirecting stdout to null
            try:
                (subprocess
                 .check_output(["ping", "-q", "-c", "1", "-b", iface[3]],
                               stderr=subprocess.STDOUT))
            except subprocess.CalledProcessError:
                pass
    # If default host given, ping it as well,
    # to try to get it into the arp table.
    # Needed in case it's not responding to broadcasts.
    if default:
        try:
            subprocess.check_output(["ping", "-q", "-c", "1", default],
                                    stderr=subprocess.STDOUT)
        except subprocess.CalledProcessError:
            pass

    ARP_POPULATE_TRIES = 10
    num_tries = 0

    while num_tries < ARP_POPULATE_TRIES:
        #Get output from arp -a -n to get known IPs
        known_ips = subprocess.check_output(["arp", "-a", "-n"])
        reg = re.compile('\? \((?P<ip>[\d.]+)\) at (?P<mac>[a-f0-9\:]+) '
                         '\[ether\] on (?P<iface>[\w\d]+)')

        #Filter (if needed) IPs not on the specified interface
        pingable_ips = [pingable[0] for pingable in reg.findall(
                        known_ips.decode()) if not interface
                        or pingable[2] == interface]

        # If the default given ip is among the remaining ones,
        # ping that.
        if default and default in pingable_ips:
            if verbose:
                print("Desired ip address %s is reachable, using it" % default)
            return default

        #If not, choose another IP.
        address_to_ping = pingable_ips[0] if len(pingable_ips) else None
        if verbose:
            print("Desired ip address %s is not reachable from %s. "
                  % (default, interface))
            print("using %s instead." % address_to_ping)

        if address_to_ping:
            return address_to_ping

        time.sleep(2)
        num_tries += 1

    # Wait time expired
    return None


def ping(host, interface, count, deadline, verbose=False):
    command = "ping -c %s -w %s %s" % (count, deadline, host)
    if interface:
        command = ("ping -I%s -c %s -w %s %s"
                   % (interface, count, deadline, host))

    reg = re.compile(r"(\d) received")
    packets_received = 0

    output = os.popen(command)
    for line in output.readlines():
        if verbose:
            print(line.rstrip())

        received = re.findall(reg, line)
        if received:
            packets_received = int(received[0])

    return packets_received


def main(args):

    gettext.textdomain("checkbox")

    default_count = 2
    default_delay = 4

    route = Route()

    parser = ArgumentParser()
    parser.add_argument("host",
                        nargs='?',
                        default=route.get_default_gateway(),
                        help="Host to ping")
    parser.add_argument("-c", "--count",
                        default=default_count,
                        type=int,
                        help="Number of packets to send.")
    parser.add_argument("-d", "--deadline",
                        default=default_delay,
                        type=int,
                        help="Timeouts in seconds.")
    parser.add_argument("-v", "--verbose",
                        action='store_true',
                        help="Be verbose.")
    parser.add_argument("-I", "--interface",
                        help="Interface to ping from.")
    args = parser.parse_args()

    #If given host is not pingable, override with something pingable.
    host = get_host_to_ping(interface=args.interface,
                            verbose=args.verbose, default=args.host)

    if args.verbose:
        print("Checking connectivity to %s" % host)
    received_packets = 0
    if host:
        received_packets = ping(host, args.interface, args.count,
                                args.deadline, args.verbose)

    if received_packets == 0:
        print(_("No Internet connection"))
        return 1
    elif received_packets != args.count:
        print(_("Connection established lost a packet"))
        return 1
    else:
        print(_("Internet connection fully established"))
        return 0

if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))
